新增: 初始化 DevFlow 项目仓库
Tauri 2 + Vue 3 + Vite 6 桌面应用,Rust workspace 含 13 个 crate (df-ai / df-storage / df-workflow / df-core / df-execute 等)。 核心能力:AI 聊天 agentic 循环(工具调用+人工审批)、工作流引擎、 任务/想法/项目/阶段管理、可追溯性,及配套前端组件。
This commit is contained in:
930
src/views/Ideas.vue
Normal file
930
src/views/Ideas.vue
Normal file
@@ -0,0 +1,930 @@
|
||||
<template>
|
||||
<div class="ideas">
|
||||
<header class="page-header">
|
||||
<h1>💡 灵感</h1>
|
||||
<div class="header-actions">
|
||||
<button class="btn btn-ghost" @click="refresh">🔄 刷新</button>
|
||||
<button class="btn btn-primary" @click="openCaptureModal">✨ 捕捉灵感</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- 搜索和筛选栏 -->
|
||||
<div class="filter-bar">
|
||||
<div class="search-box">
|
||||
<input
|
||||
v-model="searchQuery"
|
||||
type="text"
|
||||
placeholder="搜索灵感..."
|
||||
@input="filterIdeas"
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
v-for="f in filters"
|
||||
:key="f.key"
|
||||
class="filter-btn"
|
||||
:class="{ active: activeFilter === f.key }"
|
||||
@click="activeFilter = f.key"
|
||||
>
|
||||
{{ f.icon }} {{ f.label }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- 两栏布局 -->
|
||||
<div class="ideas-layout">
|
||||
<!-- 左侧:想法列表 -->
|
||||
<section class="idea-list-panel">
|
||||
<div class="idea-list">
|
||||
<div
|
||||
v-for="idea in filteredIdeas"
|
||||
:key="idea.id"
|
||||
class="idea-card"
|
||||
:class="{ selected: selectedId === idea.id }"
|
||||
@click="selectedId = idea.id"
|
||||
>
|
||||
<div class="idea-card-header">
|
||||
<span class="idea-title">{{ idea.title }}</span>
|
||||
<span class="idea-score" :class="scoreClass(idea.score)">{{ idea.score ?? '-' }}</span>
|
||||
</div>
|
||||
<p class="idea-desc-preview">{{ idea.description?.slice(0, 60) ?? '' }}{{ idea.description && idea.description.length > 60 ? '...' : '' }}</p>
|
||||
<div class="idea-card-footer">
|
||||
<span class="status-tag" :class="'status-' + idea.status">{{ statusLabel(idea.status) }}</span>
|
||||
<span class="idea-date">{{ formatDate(idea.created_at) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 右侧:想法详情 -->
|
||||
<section class="idea-detail-panel" v-if="currentIdea">
|
||||
<div class="detail-header">
|
||||
<h2 class="detail-title">{{ currentIdea.title }}</h2>
|
||||
<span class="status-tag" :class="'status-' + currentIdea.status">{{ statusLabel(currentIdea.status) }}</span>
|
||||
</div>
|
||||
<p class="detail-desc">{{ currentIdea.description }}</p>
|
||||
|
||||
<!-- 对抗式评估 -->
|
||||
<div class="detail-section">
|
||||
<h3>⚖️ 对抗式评估</h3>
|
||||
<div v-if="adversarialEval" class="adversarial-eval">
|
||||
<!-- 正反方观点 -->
|
||||
<div class="debate-container">
|
||||
<div class="debate-column positive">
|
||||
<h4>📈 正方观点</h4>
|
||||
<div class="confidence-bar" :style="{ width: (adversarialEval.positive_strength * 100) + '%' }"></div>
|
||||
<div class="confidence-text">{{ (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>📉 反方观点</h4>
|
||||
<div class="confidence-bar" :style="{ width: (adversarialEval.negative_strength * 100) + '%' }"></div>
|
||||
<div class="confidence-text">{{ (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 分析师结论 -->
|
||||
<div class="analyst-conclusion">
|
||||
<h4>🧠 AI 分析师结论</h4>
|
||||
<div class="assessment-badge" :class="assessmentClass(adversarialEval.recommendation)">
|
||||
{{ assessmentLabel(adversarialEval.recommendation) }}
|
||||
</div>
|
||||
<p class="final-score">综合评分: {{ adversarialEval.final_score.toFixed(1) }}/10</p>
|
||||
<div class="net-sentiment" :class="sentimentClass(adversarialEval.net_sentiment)">
|
||||
整体倾向: {{ adversarialEval.net_sentiment > 0 ? '积极' : '谨慎' }}
|
||||
({{ (adversarialEval.net_sentiment * 100).toFixed(0) }})
|
||||
</div>
|
||||
<p class="summary">{{ adversarialEval.analyst.summary }}</p>
|
||||
</div>
|
||||
|
||||
<!-- 行动建议 -->
|
||||
<div class="action-recommendations">
|
||||
<h4>💡 行动建议</h4>
|
||||
<ul>
|
||||
<li v-for="action in adversarialEval.action_items" :key="action">• {{ action }}</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="eval-report" style="opacity:0.5">
|
||||
<button class="btn-evaluate" @click="evaluateCurrentIdea">🔍 开始对抗评估</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 传统评分雷达图 -->
|
||||
<div class="detail-section">
|
||||
<h3>📊 多维评分</h3>
|
||||
<div class="radar-chart" v-if="parseScores(currentIdea).length > 0">
|
||||
<div class="radar-row" v-for="dim in parseScores(currentIdea)" :key="dim.name">
|
||||
<span class="radar-label">{{ dim.name }}</span>
|
||||
<div class="radar-bar-track">
|
||||
<div class="radar-bar-fill" :style="{ width: dim.score + '%' }" :class="dim.score >= 80 ? 'fill-high' : dim.score >= 60 ? 'fill-mid' : 'fill-low'"></div>
|
||||
</div>
|
||||
<span class="radar-value">{{ dim.score }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="eval-report" style="opacity:0.5">暂无评估</div>
|
||||
</div>
|
||||
|
||||
<!-- 标签 -->
|
||||
<div class="detail-section">
|
||||
<h3>🏷️ 标签</h3>
|
||||
<div class="tag-list" v-if="parseTags(currentIdea).length > 0">
|
||||
<span class="tag" v-for="tag in parseTags(currentIdea)" :key="tag">{{ tag }}</span>
|
||||
</div>
|
||||
<div v-else class="eval-report" style="opacity:0.5">暂无标签</div>
|
||||
</div>
|
||||
|
||||
<!-- 状态管理 -->
|
||||
<div class="detail-section">
|
||||
<h3>📋 状态管理</h3>
|
||||
<div class="status-controls">
|
||||
<select v-model="currentStatus" @change="updateIdeaStatus" class="status-select">
|
||||
<option value="draft">📝 草稿</option>
|
||||
<option value="pending_review">⏳ 待评估</option>
|
||||
<option value="approved">✅ 已批准</option>
|
||||
<option value="promoted">🚀 已立项</option>
|
||||
<option value="rejected">❌ 已拒绝</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 操作 -->
|
||||
<div class="detail-section" style="margin-top:8px">
|
||||
<div class="action-buttons">
|
||||
<button
|
||||
v-if="currentIdea.status === 'approved' && !currentIdea.promoted_to"
|
||||
class="btn btn-primary"
|
||||
@click="promoteToProject"
|
||||
>
|
||||
🚀 立项为项目
|
||||
</button>
|
||||
<button class="btn btn-ghost" @click="deleteCurrentIdea">🗑️ 删除想法</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 未选择 -->
|
||||
<section class="idea-detail-panel idea-empty" v-else>
|
||||
<div class="empty-state">
|
||||
<div class="empty-icon">💡</div>
|
||||
<p>选择一个想法查看详情</p>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<!-- 捕捉想法模态框 -->
|
||||
<div class="modal-overlay" v-if="showCaptureModal" @click.self="showCaptureModal = false">
|
||||
<div class="modal-box">
|
||||
<h3>✨ 捕捉新想法</h3>
|
||||
<label style="font-size:12px;color:var(--df-text-secondary);margin-bottom:4px;display:block">标题</label>
|
||||
<input v-model="newIdeaTitle" placeholder="一句话描述你的想法..." @keyup.enter="confirmCapture" />
|
||||
<label style="font-size:12px;color:var(--df-text-secondary);margin-bottom:4px;display:block">描述</label>
|
||||
<textarea v-model="newIdeaDesc" placeholder="详细说明(可选)..." rows="3" style="resize:vertical"></textarea>
|
||||
<div class="modal-actions">
|
||||
<button class="btn-cancel" @click="showCaptureModal = false">取消</button>
|
||||
<button class="btn-confirm" @click="confirmCapture">确认</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useProjectStore } from '../stores/project'
|
||||
import { formatDate } from '../utils/time'
|
||||
import type { IdeaRecord } from '../api/types'
|
||||
|
||||
const router = useRouter()
|
||||
const store = useProjectStore()
|
||||
|
||||
type FilterKey = 'all' | 'hot' | 'pending' | 'promoted'
|
||||
|
||||
const activeFilter = ref<FilterKey>('all')
|
||||
const selectedId = ref<string | null>(null)
|
||||
const searchQuery = ref('')
|
||||
|
||||
// ── 新建想法模态框 ──
|
||||
const showCaptureModal = ref(false)
|
||||
const newIdeaTitle = ref('')
|
||||
const newIdeaDesc = ref('')
|
||||
|
||||
const filters: { key: FilterKey; label: string; icon: string }[] = [
|
||||
{ key: 'all', label: '全部', icon: '📋' },
|
||||
{ key: 'hot', label: '热门', icon: '🔥' },
|
||||
{ key: 'pending', label: '待评估', icon: '⏳' },
|
||||
{ key: 'promoted', label: '已立项', icon: '🚀' },
|
||||
]
|
||||
|
||||
const filteredIdeas = computed(() => {
|
||||
let ideas = store.ideas
|
||||
|
||||
// 应用状态过滤
|
||||
switch (activeFilter.value) {
|
||||
case 'hot': ideas = ideas.filter(i => (i.score ?? 0) >= 80); break
|
||||
case 'pending': ideas = ideas.filter(i => i.status === 'pending_review' || i.status === 'draft'); break
|
||||
case 'promoted': ideas = ideas.filter(i => i.status === 'promoted'); break
|
||||
default: break // 不过滤
|
||||
}
|
||||
|
||||
// 应用搜索过滤
|
||||
if (searchQuery.value.trim()) {
|
||||
const query = searchQuery.value.toLowerCase().trim()
|
||||
ideas = ideas.filter(i =>
|
||||
i.title.toLowerCase().includes(query) ||
|
||||
(i.description && i.description.toLowerCase().includes(query)) ||
|
||||
(i.tags && JSON.parse(i.tags || '[]').some((tag: string) => tag.toLowerCase().includes(query)))
|
||||
)
|
||||
}
|
||||
|
||||
return ideas
|
||||
})
|
||||
|
||||
function filterIdeas() {
|
||||
// filteredIdeas 是 computed,会自动响应变化
|
||||
}
|
||||
|
||||
const currentIdea = computed(() => {
|
||||
if (!selectedId.value) return null
|
||||
return store.ideas.find(i => i.id === selectedId.value) ?? null
|
||||
})
|
||||
|
||||
const currentStatus = computed(() => {
|
||||
return currentIdea.value?.status || 'draft'
|
||||
})
|
||||
|
||||
function parseTags(idea: IdeaRecord): string[] {
|
||||
if (!idea.tags) return []
|
||||
try {
|
||||
const parsed = JSON.parse(idea.tags)
|
||||
return Array.isArray(parsed) ? parsed : []
|
||||
} catch {
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
interface ScoreDimension { name: string; score: number }
|
||||
|
||||
function parseScores(idea: IdeaRecord): ScoreDimension[] {
|
||||
if (!idea.scores) return []
|
||||
try {
|
||||
const parsed = JSON.parse(idea.scores)
|
||||
if (typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed)) {
|
||||
return Object.entries(parsed).map(([name, score]) => ({
|
||||
name,
|
||||
score: typeof score === 'number' ? score : 0,
|
||||
}))
|
||||
}
|
||||
return []
|
||||
} catch {
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
interface AdversarialEval {
|
||||
positive_strength: number
|
||||
negative_strength: number
|
||||
net_sentiment: number
|
||||
recommendation: string
|
||||
final_score: number
|
||||
summary: string
|
||||
action_items: string[]
|
||||
positive: { thesis: string; evidence: string[] }
|
||||
negative: { thesis: string; evidence: string[] }
|
||||
analyst: { summary: string }
|
||||
}
|
||||
|
||||
// 对抗式评估数据持久化在 ai_analysis JSON 字段中,前端解析渲染
|
||||
const adversarialEval = computed<AdversarialEval | null>(() => {
|
||||
const idea = currentIdea.value
|
||||
if (!idea?.ai_analysis) return null
|
||||
try {
|
||||
return JSON.parse(idea.ai_analysis) as AdversarialEval
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
})
|
||||
|
||||
function assessmentClass(recommendation: string) {
|
||||
return recommendation.toLowerCase().replace(/ /g, '-')
|
||||
}
|
||||
|
||||
function assessmentLabel(recommendation: string) {
|
||||
const map: Record<string, string> = {
|
||||
'immediate action': '🚀 立即行动',
|
||||
'soon': '📅 尽快行动',
|
||||
'with resources': '📦 配置资源后行动',
|
||||
'research more': '🔍 需要更多研究',
|
||||
'monitor': '👁️ 持续监控',
|
||||
'cancel': '❌ 取消想法'
|
||||
}
|
||||
return map[recommendation] ?? recommendation
|
||||
}
|
||||
|
||||
function sentimentClass(sentiment: number) {
|
||||
return sentiment >= 0 ? 'positive' : 'negative'
|
||||
}
|
||||
|
||||
function scoreClass(score: number | null) {
|
||||
const s = score ?? 0
|
||||
if (s >= 80) return 'score-high'
|
||||
if (s >= 60) return 'score-mid'
|
||||
return 'score-low'
|
||||
}
|
||||
|
||||
function statusLabel(status: string) {
|
||||
const map: Record<string, string> = {
|
||||
draft: '📝 草稿',
|
||||
pending_review: '⏳ 待评估',
|
||||
approved: '✅ 已批准',
|
||||
promoted: '🚀 已立项',
|
||||
rejected: '❌ 已拒绝',
|
||||
}
|
||||
return map[status] ?? status
|
||||
}
|
||||
|
||||
// formatDate 由 ../utils/time 提供(统一毫秒字符串解析,根治 Invalid Date)
|
||||
|
||||
function openCaptureModal() {
|
||||
newIdeaTitle.value = ''
|
||||
newIdeaDesc.value = ''
|
||||
showCaptureModal.value = true
|
||||
}
|
||||
|
||||
async function confirmCapture() {
|
||||
if (!newIdeaTitle.value.trim()) return
|
||||
await store.createIdea({
|
||||
title: newIdeaTitle.value.trim(),
|
||||
description: newIdeaDesc.value.trim() || undefined,
|
||||
})
|
||||
showCaptureModal.value = false
|
||||
}
|
||||
|
||||
async function deleteCurrentIdea() {
|
||||
if (!currentIdea.value) return
|
||||
await store.deleteIdea(currentIdea.value.id)
|
||||
selectedId.value = null
|
||||
}
|
||||
|
||||
async function promoteToProject() {
|
||||
if (!currentIdea.value) return
|
||||
|
||||
// 创建新项目,基于想法(store.createProject 第 3 参 = idea_id)
|
||||
const project = await store.createProject(
|
||||
currentIdea.value.title,
|
||||
currentIdea.value.description,
|
||||
currentIdea.value.id,
|
||||
)
|
||||
const projectId = project.id
|
||||
|
||||
// 更新想法状态和晋升信息(store.updateIdea 单字段,分两次调用)
|
||||
await store.updateIdea(currentIdea.value.id, 'status', 'promoted')
|
||||
await store.updateIdea(currentIdea.value.id, 'promoted_to', projectId)
|
||||
|
||||
// 跳转到项目详情
|
||||
router.push(`/projects/${projectId}`)
|
||||
|
||||
// 刷新想法列表
|
||||
await store.loadIdeas()
|
||||
}
|
||||
|
||||
async function refresh() {
|
||||
await store.loadIdeas()
|
||||
}
|
||||
|
||||
async function evaluateCurrentIdea() {
|
||||
if (!currentIdea.value) return
|
||||
|
||||
// 模拟对抗式评估(实际应该调用后端 API)
|
||||
// 这里使用模拟数据展示界面
|
||||
const mockEval: AdversarialEval = {
|
||||
positive_strength: 0.75,
|
||||
negative_strength: 0.65,
|
||||
net_sentiment: 0.1,
|
||||
recommendation: 'with resources',
|
||||
final_score: 6.5,
|
||||
summary: '该想法整体价值评估中等偏上,建议在有条件的情况下执行。主要价值在于技术创新性较强,需要关注风险控制和资源投入。',
|
||||
action_items: [
|
||||
'确认资源预算',
|
||||
'评估ROI',
|
||||
'制定风险预案'
|
||||
],
|
||||
positive: {
|
||||
thesis: '技术创新性强,潜在回报高',
|
||||
evidence: ['技术栈成熟', '市场需求明确', '团队有相关经验'],
|
||||
},
|
||||
negative: {
|
||||
thesis: '资源投入大,存在执行风险',
|
||||
evidence: ['开发周期长', '需要额外人力', '竞品已有类似方案'],
|
||||
},
|
||||
analyst: {
|
||||
summary: '综合正反方观点,建议在资源到位后启动,并设立阶段性验收点控制风险。',
|
||||
},
|
||||
}
|
||||
|
||||
// 更新想法的评估结果(实际应该调用 API)
|
||||
await store.updateIdea(currentIdea.value.id, 'ai_analysis', JSON.stringify(mockEval, null, 2))
|
||||
}
|
||||
|
||||
async function updateIdeaStatus() {
|
||||
if (!currentIdea.value || !currentStatus.value) return
|
||||
|
||||
await store.updateIdea(currentIdea.value.id, 'status', currentStatus.value)
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
await store.loadIdeas()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.ideas { padding: 16px 20px 20px; }
|
||||
|
||||
.page-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: var(--df-gap-page);
|
||||
}
|
||||
.page-header h1 { font-size: 24px; font-weight: 500; color: var(--df-text); }
|
||||
.header-actions { display: flex; gap: 10px; }
|
||||
|
||||
/* ===== 按钮 ===== */
|
||||
.btn {
|
||||
padding: 8px 16px;
|
||||
border: none;
|
||||
border-radius: var(--df-radius-sm);
|
||||
font-size: 13px;
|
||||
cursor: pointer;
|
||||
transition: all 0.15s;
|
||||
}
|
||||
.btn-primary { background: var(--df-accent); color: #fff; }
|
||||
.btn-primary:hover { background: var(--df-accent-hover); }
|
||||
.btn-ghost { background: transparent; color: var(--df-text-secondary); border: 0.5px solid var(--df-border); }
|
||||
.btn-ghost:hover { background: var(--df-bg-card); color: var(--df-text); }
|
||||
|
||||
/* ===== 筛选栏 ===== */
|
||||
.filter-bar {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
margin-bottom: var(--df-gap-page);
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.search-box {
|
||||
flex: 1;
|
||||
max-width: 300px;
|
||||
}
|
||||
|
||||
.search-box input {
|
||||
width: 100%;
|
||||
padding: 6px 12px;
|
||||
border: 0.5px solid var(--df-border);
|
||||
border-radius: var(--df-radius-sm);
|
||||
background: var(--df-bg);
|
||||
color: var(--df-text);
|
||||
font-size: 13px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.search-box input:focus {
|
||||
outline: none;
|
||||
border-color: var(--df-accent);
|
||||
background: var(--df-bg-raised);
|
||||
}
|
||||
.filter-btn {
|
||||
padding: 6px 14px;
|
||||
border: 0.5px solid var(--df-border);
|
||||
border-radius: var(--df-radius-sm);
|
||||
background: transparent;
|
||||
color: var(--df-text-secondary);
|
||||
font-size: 13px;
|
||||
cursor: pointer;
|
||||
transition: all 0.15s;
|
||||
}
|
||||
.filter-btn:hover { background: var(--df-bg-card); color: var(--df-text); }
|
||||
.filter-btn.active {
|
||||
background: var(--df-accent);
|
||||
color: #fff;
|
||||
border-color: var(--df-accent);
|
||||
}
|
||||
|
||||
/* ===== 两栏布局 ===== */
|
||||
.ideas-layout {
|
||||
display: grid;
|
||||
grid-template-columns: 280px 1fr;
|
||||
gap: var(--df-gap-grid);
|
||||
}
|
||||
|
||||
/* ===== 左侧列表 ===== */
|
||||
.idea-list-panel {
|
||||
background: var(--df-bg-card);
|
||||
border: 0.5px solid var(--df-border);
|
||||
border-radius: var(--df-radius-lg);
|
||||
padding: 6px;
|
||||
max-height: calc(100vh - 200px);
|
||||
overflow-y: auto;
|
||||
}
|
||||
.idea-card {
|
||||
padding: 14px;
|
||||
border-radius: var(--df-radius);
|
||||
cursor: pointer;
|
||||
transition: all 0.15s;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
.idea-card:hover { background: rgba(108, 99, 255, 0.06); }
|
||||
.idea-card.selected { background: rgba(108, 99, 255, 0.12); border: 0.5px solid var(--df-accent); }
|
||||
|
||||
.idea-card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
.idea-title { font-size: 14px; font-weight: 500; color: var(--df-text); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
||||
|
||||
.idea-score {
|
||||
font-size: 13px; font-weight: 500;
|
||||
min-width: 32px; height: 26px;
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
border-radius: var(--df-radius-sm);
|
||||
}
|
||||
.score-high { background: rgba(100,255,218,0.15); color: var(--df-success); }
|
||||
.score-mid { background: rgba(255,217,61,0.15); color: var(--df-warning); }
|
||||
.score-low { background: rgba(255,107,107,0.15); color: var(--df-danger); }
|
||||
|
||||
.idea-desc-preview { font-size: 12px; color: var(--df-text-dim); margin-bottom: 8px; line-height: 1.4; }
|
||||
|
||||
.idea-card-footer {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
.idea-date { font-size: 11px; color: var(--df-text-dim); }
|
||||
|
||||
/* ===== 状态标签 ===== */
|
||||
.status-tag {
|
||||
font-size: 11px; font-weight: 500; padding: 2px 8px;
|
||||
border-radius: var(--df-radius-xs);
|
||||
}
|
||||
.status-draft { background: rgba(90,99,128,0.2); color: var(--df-text-dim); }
|
||||
.status-pending_review { background: rgba(100,181,246,0.2); color: var(--df-info); }
|
||||
.status-approved { background: rgba(100,255,218,0.15); color: var(--df-success); }
|
||||
.status-promoted { background: rgba(108,99,255,0.2); color: var(--df-accent); }
|
||||
.status-rejected { background: rgba(255,107,107,0.2); color: var(--df-danger); }
|
||||
|
||||
/* ===== 对抗式评估 ===== */
|
||||
.adversarial-eval {
|
||||
background: var(--df-bg-raised);
|
||||
border: 0.5px solid var(--df-border);
|
||||
border-radius: var(--df-radius);
|
||||
padding: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.debate-container {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 1rem;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.debate-column {
|
||||
padding: 1rem;
|
||||
border-radius: var(--df-radius);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.debate-column.positive {
|
||||
background: rgba(61, 219, 160, 0.08);
|
||||
border: 0.5px solid rgba(61, 219, 160, 0.2);
|
||||
}
|
||||
|
||||
.debate-column.negative {
|
||||
background: rgba(240, 101, 101, 0.08);
|
||||
border: 0.5px solid rgba(240, 101, 101, 0.2);
|
||||
}
|
||||
|
||||
.debate-column h4 {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
margin: 0 0 0.5rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.confidence-bar {
|
||||
height: 4px;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
border-radius: var(--df-radius-xs);
|
||||
margin-bottom: 0.25rem;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.debate-column.positive .confidence-bar::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
height: 100%;
|
||||
background: currentColor;
|
||||
border-radius: var(--df-radius-xs);
|
||||
}
|
||||
|
||||
.debate-column.negative .confidence-bar::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
height: 100%;
|
||||
background: currentColor;
|
||||
border-radius: var(--df-radius-xs);
|
||||
}
|
||||
|
||||
.confidence-text {
|
||||
font-size: 11px;
|
||||
color: var(--df-text-dim);
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.debate-column .thesis {
|
||||
font-size: 12px;
|
||||
color: var(--df-text);
|
||||
margin-bottom: 0.5rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.debate-column ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.debate-column li {
|
||||
font-size: 11px;
|
||||
color: var(--df-text-secondary);
|
||||
margin-bottom: 0.25rem;
|
||||
padding-left: 0.5rem;
|
||||
}
|
||||
|
||||
.analyst-conclusion {
|
||||
background: var(--df-bg-card);
|
||||
border: 0.5px solid var(--df-border);
|
||||
border-radius: var(--df-radius);
|
||||
padding: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.assessment-badge {
|
||||
display: inline-block;
|
||||
padding: 4px 12px;
|
||||
border-radius: 20px;
|
||||
font-size: 11px;
|
||||
font-weight: 500;
|
||||
margin: 0.5rem 0;
|
||||
}
|
||||
|
||||
.assessment-badge.immediate { background: var(--df-success-bg); color: var(--df-success); }
|
||||
.assessment-badge.soon { background: var(--df-info-bg); color: var(--df-info); }
|
||||
.assessment-badge.conditional { background: var(--df-warning-bg); color: var(--df-warning); }
|
||||
.assessment-badge.revised { background: rgba(255, 217, 61, 0.2); color: var(--df-warning); }
|
||||
.assessment-badge.defer { background: var(--df-danger-bg); color: var(--df-danger); }
|
||||
.assessment-badge.cancel { background: var(--df-danger-bg); color: var(--df-danger); }
|
||||
|
||||
.final-score {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: var(--df-text);
|
||||
margin: 0.5rem 0;
|
||||
}
|
||||
|
||||
.net-sentiment {
|
||||
font-size: 12px;
|
||||
padding: 4px 8px;
|
||||
border-radius: var(--df-radius-xs);
|
||||
display: inline-block;
|
||||
margin: 0.5rem 0;
|
||||
}
|
||||
|
||||
.net-sentiment.positive { background: rgba(61, 219, 160, 0.15); color: var(--df-success); }
|
||||
.net-sentiment.negative { background: rgba(240, 101, 101, 0.15); color: var(--df-danger); }
|
||||
|
||||
.summary {
|
||||
font-size: 12px;
|
||||
color: var(--df-text-secondary);
|
||||
line-height: 1.5;
|
||||
margin: 0.5rem 0;
|
||||
}
|
||||
|
||||
.action-recommendations {
|
||||
background: var(--df-bg-raised);
|
||||
border: 0.5px solid var(--df-border);
|
||||
border-radius: var(--df-radius);
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.action-recommendations h4 {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
margin: 0 0 0.5rem;
|
||||
}
|
||||
|
||||
.action-recommendations ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.action-recommendations li {
|
||||
font-size: 12px;
|
||||
color: var(--df-text-secondary);
|
||||
margin-bottom: 0.25rem;
|
||||
padding-left: 0.5rem;
|
||||
}
|
||||
|
||||
.btn-evaluate {
|
||||
background: var(--df-accent);
|
||||
color: #fff;
|
||||
border: none;
|
||||
padding: 8px 16px;
|
||||
border-radius: var(--df-radius);
|
||||
font-size: 13px;
|
||||
cursor: pointer;
|
||||
transition: all 0.15s;
|
||||
}
|
||||
|
||||
.btn-evaluate:hover {
|
||||
background: var(--df-accent-hover);
|
||||
}
|
||||
|
||||
/* ===== 右侧详情 ===== */
|
||||
.idea-detail-panel {
|
||||
background: var(--df-bg-card);
|
||||
border: 0.5px solid var(--df-border);
|
||||
border-radius: var(--df-radius-lg);
|
||||
padding: var(--df-pad-panel);
|
||||
max-height: calc(100vh - 200px);
|
||||
overflow-y: auto;
|
||||
}
|
||||
.idea-empty {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.empty-state { text-align: center; color: var(--df-text-dim); }
|
||||
.empty-icon { font-size: 48px; margin-bottom: 12px; }
|
||||
.empty-state p { font-size: 14px; }
|
||||
|
||||
.detail-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: var(--df-gap-head);
|
||||
}
|
||||
.detail-title { font-size: 20px; font-weight: 500; color: var(--df-text); }
|
||||
|
||||
.detail-desc {
|
||||
font-size: 14px;
|
||||
color: var(--df-text-secondary);
|
||||
line-height: 1.6;
|
||||
margin-bottom: var(--df-gap-page);
|
||||
}
|
||||
|
||||
.detail-section {
|
||||
margin-bottom: var(--df-gap-page);
|
||||
}
|
||||
.detail-section h3 {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: var(--df-text);
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.eval-report {
|
||||
font-size: 13px;
|
||||
color: var(--df-text-secondary);
|
||||
line-height: 1.6;
|
||||
padding: 14px 16px;
|
||||
background: rgba(108, 99, 255, 0.06);
|
||||
border-left: 3px solid var(--df-accent);
|
||||
border-radius: 0 8px 8px 0;
|
||||
}
|
||||
|
||||
/* ===== 雷达图(div 模拟) ===== */
|
||||
.radar-chart {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
.radar-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
.radar-label {
|
||||
font-size: 12px;
|
||||
color: var(--df-text-secondary);
|
||||
min-width: 72px;
|
||||
text-align: right;
|
||||
}
|
||||
.radar-bar-track {
|
||||
flex: 1;
|
||||
height: 8px;
|
||||
background: var(--df-border);
|
||||
border-radius: var(--df-radius-xs);
|
||||
overflow: hidden;
|
||||
}
|
||||
.radar-bar-fill {
|
||||
height: 100%;
|
||||
border-radius: var(--df-radius-xs);
|
||||
transition: width 0.4s;
|
||||
}
|
||||
.fill-high { background: var(--df-success); }
|
||||
.fill-mid { background: var(--df-warning); }
|
||||
.fill-low { background: var(--df-danger); }
|
||||
.radar-value {
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
min-width: 28px;
|
||||
text-align: right;
|
||||
color: var(--df-text);
|
||||
}
|
||||
|
||||
/* ===== 标签 ===== */
|
||||
.tag-list { display: flex; gap: 8px; flex-wrap: wrap; }
|
||||
.tag {
|
||||
font-size: 12px; padding: 4px 10px;
|
||||
border-radius: var(--df-radius-xs);
|
||||
background: rgba(108, 99, 255, 0.1);
|
||||
color: var(--df-accent);
|
||||
}
|
||||
|
||||
/* ===== 删除按钮 ===== */
|
||||
.btn-delete {
|
||||
padding: 6px 14px;
|
||||
border: 0.5px solid rgba(255,107,107,0.3);
|
||||
border-radius: var(--df-radius-sm);
|
||||
background: transparent;
|
||||
color: var(--df-danger);
|
||||
font-size: 12px;
|
||||
cursor: pointer;
|
||||
transition: all 0.15s;
|
||||
}
|
||||
.btn-delete:hover { background: rgba(255,107,107,0.1); }
|
||||
|
||||
/* ===== 状态管理 ===== */
|
||||
.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-select:focus {
|
||||
outline: none;
|
||||
border-color: var(--df-accent);
|
||||
background: var(--df-bg-raised);
|
||||
}
|
||||
|
||||
/* ===== 响应式 ===== */
|
||||
@media (max-width: 900px) {
|
||||
.ideas { padding: 16px; }
|
||||
.ideas-layout {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
.filter-bar {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.search-box {
|
||||
max-width: 100%;
|
||||
order: -1;
|
||||
width: 100%;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
/* ===== 模态框 ===== */
|
||||
.modal-overlay { position: fixed; inset: 0; background: rgba(0,0,0,0.5); display: flex; align-items: center; justify-content: center; z-index: 100; }
|
||||
.modal-box { background: var(--df-bg-card); border: 0.5px solid var(--df-border); border-radius: var(--df-radius-lg); padding: 24px; min-width: 360px; max-width: 90vw; }
|
||||
.modal-box h3 { color: var(--df-text); margin-bottom: var(--df-gap-head); }
|
||||
.modal-box input, .modal-box textarea { width: 100%; padding: 8px 12px; border: 0.5px solid var(--df-border); border-radius: var(--df-radius-sm); background: var(--df-bg); color: var(--df-text); font-size: 13px; margin-bottom: 12px; box-sizing: border-box; font-family: inherit; }
|
||||
.modal-box .modal-actions { display: flex; gap: 8px; justify-content: flex-end; }
|
||||
.modal-box .btn-cancel { padding: 6px 16px; border: 0.5px solid var(--df-border); border-radius: var(--df-radius-sm); background: transparent; color: var(--df-text-secondary); cursor: pointer; }
|
||||
.modal-box .btn-confirm { padding: 6px 16px; border: none; border-radius: var(--df-radius-sm); background: var(--df-accent); color: #fff; cursor: pointer; }
|
||||
</style>
|
||||
Reference in New Issue
Block a user