优化: 所有剩余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:
lxy
2026-08-02 13:11:06 +08:00
parent caaabf0c15
commit f736f435bc
70 changed files with 2645 additions and 576 deletions
+77 -6
View File
@@ -1,5 +1,5 @@
<template>
<div class="project-card" @click="router.push('/projects/' + project.id)">
<div class="project-card" :class="{ 'project-card--list': view === 'list' }" @click="router.push('/projects/' + project.id)">
<div class="card-top">
<div class="card-title-row">
<h2 class="card-name">{{ project.name }}</h2>
@@ -10,7 +10,15 @@
</div>
</div>
<p class="card-desc">{{ stripMd(project.description) }}</p>
<!-- 描述:max-height + 省略号,展开/收起;list 视图更紧凑(2 行),card 视图宽松(3 行) -->
<div class="card-desc-wrap" :class="{ 'is-expanded': descExpanded }">
<p ref="descRef" class="card-desc">{{ stripMd(project.description) }}</p>
<button
v-if="descOverflow"
class="card-desc-toggle"
@click.stop="descExpanded = !descExpanded"
>{{ descExpanded ? $t('common.collapse') : $t('common.expand') }}</button>
</div>
<!-- 底部信息 -->
<div class="card-footer">
@@ -24,7 +32,7 @@
</div>
<div class="footer-stat">
<span class="stat-icon">🕐</span>
<span>{{ formatDate(project.updated_at) }}</span>
<span>{{ formatDate(project.last_active_at ?? project.updated_at) }}</span>
</div>
</div>
@@ -38,8 +46,9 @@
<script setup lang="ts">
// 项目列表卡片 — 从 Projects.vue 抽出(单卡片渲染 + 导航 + 删除触发)。
// 依赖均为无副作用纯函数 + 共享常量;删除走 emit 交回父组件处理(confirm 弹层 + store 调用)。
// view prop:'list'(紧凑,默认)/'card'(宽松);描述 max-height 截断 + 展开/收起。
import { useRouter } from 'vue-router'
import { ref, onMounted } from 'vue'
import { ref, onMounted, watch, nextTick } from 'vue'
import { formatDate } from '@/utils/time'
import { parseStack } from '@/utils/project'
import { stripMd } from '@/utils/markdown'
@@ -47,7 +56,13 @@ import { moduleApi } from '@/api/module'
import { projectStatusLabel as statusLabel, projectBadgeClass as stageClass } from '@/constants/project'
import type { ProjectRecord } from '@/api/types'
const props = defineProps<{ project: ProjectRecord }>()
const props = withDefaults(defineProps<{
project: ProjectRecord
/** 视图模式:'list'(紧凑,默认)/'card'(宽松);仅影响描述行数阈值与内边距 */
view?: 'list' | 'card'
}>(), {
view: 'list',
})
const emit = defineEmits<{
(e: 'delete', project: ProjectRecord): void
}>()
@@ -61,7 +76,28 @@ onMounted(async () => {
const list = await moduleApi.listProjectModules(props.project.id)
moduleCount.value = list.length
} catch { /* 老项目无工程记录不报错 */ }
// DOM 渲染完成后测描述是否溢出(决定是否显示「展开」)
await nextTick()
measureDesc()
})
// ── 描述截断/展开 ──
// descOverflow=true 才渲染「展开/收起」按钮;阈值由 CSS max-height 控制(-webkit-line-clamp),
// 这里只负责测量实际内容高度是否超出 max-height。
const descRef = ref<HTMLParagraphElement | null>(null)
const descOverflow = ref(false)
const descExpanded = ref(false)
function measureDesc() {
const el = descRef.value
if (!el) return
// scrollHeight > clientHeight 说明被 max-height 截断了
descOverflow.value = el.scrollHeight - el.clientHeight > 1
}
// 描述/视图模式变化后重新测量(描述异步入库或切 view 行数阈值变,按钮显隐需重算)
watch(() => props.project.description, () => nextTick(measureDesc))
watch(() => props.view, () => nextTick(measureDesc))
</script>
<style scoped>
@@ -101,12 +137,47 @@ onMounted(async () => {
.stage-testing { background: rgba(255,217,61,0.15); color: var(--df-warning); }
.stage-release { background: rgba(100,255,218,0.15); color: var(--df-success); }
.card-desc-wrap {
position: relative;
margin-bottom: 16px;
}
.card-desc {
font-size: 13px;
color: var(--df-text-secondary);
line-height: 1.5;
margin-bottom: 16px;
margin: 0;
/* 默认截断:2 行(list 视图紧凑,单屏多张)。展开态取消截断。 */
display: -webkit-box;
-webkit-box-orient: vertical;
overflow: hidden;
-webkit-line-clamp: 2;
}
/* card 视图略宽松:3 行 */
.project-card--list .card-desc { -webkit-line-clamp: 2; }
.project-card:not(.project-card--list) .card-desc { -webkit-line-clamp: 3; }
.card-desc-wrap.is-expanded .card-desc {
-webkit-line-clamp: unset;
display: block;
overflow: visible;
}
/* 展开/收起按钮:右对齐、低调,避免抢卡片点击 */
.card-desc-toggle {
display: inline-block;
margin-top: 4px;
padding: 0;
background: transparent;
border: none;
color: var(--df-accent);
font-size: 12px;
cursor: pointer;
line-height: 1.4;
}
.card-desc-toggle:hover { text-decoration: underline; }
/* list 视图:更紧凑的内边距,单屏容纳更多卡片 */
.project-card--list { padding: calc(var(--df-pad-panel) * 0.7); }
.card-footer {
display: flex;