优化: 所有剩余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:
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user