优化: UI/UX 走查修复与跨模块样式统一治理

- 前四批:暗色 UA 重置+color-scheme、知识详情窄屏双闸、启动链 6 点加固、原生 tooltip 换 CSS
- 统一治理:新建 --df-pad-page/--df-fs-item/--df-on-accent token 与 .df-seg/.modal-box--sm--md/.df-badge--solid/.empty-icon--sm--xs 公共类
- 收编:status-tag 双规格、AuditLog 容器、模态自建块、空态图标四档、加载更多三实现、圆角/字号/mono 栈字面量约 60 处
- 根修:.page-header h1 加压缩保护(治标题竖排);KnowledgeDetail hover token 笔误
This commit is contained in:
lxy
2026-10-25 22:32:29 +08:00
parent 0317873a5b
commit 3caf2d047f
38 changed files with 377 additions and 248 deletions
+6 -1
View File
@@ -213,7 +213,12 @@ async function activateForPopup() {
onMounted(async () => {
// 1. 先把 SQLite 全表 KV 填进 appSettings 缓存(后续 get/useSetting 才能读到真实值)
await appSettings.loadAll()
// 包 try/catch 防 loadAll reject 中断 onMounted 后续初始化(对齐下方各步 try/catch 模式)
try {
await appSettings.loadAll()
} catch (e) {
console.error('启动加载应用设置失败:', e)
}
// 2. 一次性迁移:把旧 localStorage 的 11 个 key 导入 SQLite(仅首次跑,之后 localStorage 清净)
await migrateLegacyLocalStorage()
// 3. 缓存已就绪,初始化主题/locale
+3 -3
View File
@@ -51,7 +51,7 @@ const sizeText = computed(() =>
align-items: center;
gap: 8px;
padding: 6px 10px;
background: var(--df-warning-bg, rgba(240,199,94,0.08));
background: var(--df-warning-bg);
}
.offload-badge {
flex-shrink: 0;
@@ -59,8 +59,8 @@ const sizeText = computed(() =>
font-weight: 600;
padding: 1px 7px;
border-radius: var(--df-radius-sm);
background: var(--df-warning-bg, rgba(240,199,94,0.14));
color: var(--df-warning, #f0c75e);
background: var(--df-warning-bg);
color: var(--df-warning);
}
.offload-meta {
flex-shrink: 0;
+1 -1
View File
@@ -102,7 +102,7 @@ function onPageSizeChange(val: string) {
align-items: center;
flex-wrap: wrap;
gap: 8px;
margin-top: var(--df-gap-grid, 16px);
margin-top: var(--df-gap-grid);
padding: 8px 4px;
font-size: 12px;
color: var(--df-text-dim);
+2 -2
View File
@@ -87,7 +87,7 @@ const shortcuts = computed(() => [
cursor: pointer;
font-size: 12px;
padding: 2px 4px;
border-radius: 4px;
border-radius: var(--df-radius-sm);
}
.ai-shortcuts-close:hover { color: var(--df-text); background: var(--df-bg-card); }
.ai-shortcuts-list {
@@ -109,7 +109,7 @@ const shortcuts = computed(() => [
font-size: 11px;
background: var(--df-bg-card);
border: 0.5px solid var(--df-border);
border-radius: 4px;
border-radius: var(--df-radius-sm);
padding: 2px 8px;
color: var(--df-text);
}
+6
View File
@@ -1213,4 +1213,10 @@ watch(() => props.idea.id, loadHistory)
grid-template-columns: 1fr;
}
}
/* 容器闸:详情栏窄于 480px 时辩论双栏堆叠(父级 .ideas 已声明 container-type) */
@container (max-width: 480px) {
.debate-container {
grid-template-columns: 1fr;
}
}
</style>
+55 -8
View File
@@ -110,6 +110,36 @@
</section>
<!-- ③ 引用(P0 折叠)-->
<section class="detail-section" v-if="relatedItems.length > 0">
<div
class="section-title section-title-toggle"
role="button"
tabindex="0"
:aria-expanded="sectionOpen.related"
@click="sectionOpen.related = !sectionOpen.related"
@keydown.enter.prevent="sectionOpen.related = !sectionOpen.related"
@keydown.space.prevent="sectionOpen.related = !sectionOpen.related"
>
<span>{{ t('knowledge.relatedTitle', { n: relatedItems.length }) }}</span>
<span class="section-chevron">{{ sectionOpen.related ? '▾' : '▸' }}</span>
</div>
<div v-show="sectionOpen.related" class="section-body">
<div class="ref-list">
<button
v-for="rel in relatedItems"
:key="rel.edge.id"
class="ref-item related-item"
@click="emit('open-related', rel.knowledge.id)"
>
<span class="related-dir" aria-hidden="true">{{ rel.direction === 'out' ? '→' : '←' }}</span>
<span class="ref-conv">{{ rel.knowledge.title }}</span>
<span class="related-kind">{{ rel.edge.kind }}</span>
</button>
</div>
</div>
</section>
<!-- ④ 引用(P0 折叠)-->
<section class="detail-section">
<div
class="section-title section-title-toggle"
@@ -138,7 +168,7 @@
</div>
</section>
<!-- ④ 生命周期时间线(P0 折叠:默认收起,回顾性信息通常无需常驻)-->
<!-- ⑤ 生命周期时间线(P0 折叠:默认收起,回顾性信息通常无需常驻)-->
<section class="detail-section">
<div
class="section-title section-title-toggle"
@@ -198,6 +228,7 @@ const emit = defineEmits<{
(e: 'archive'): void
(e: 'restore'): void
(e: 'resubmit'): void
(e: 'open-related', id: string): void
}>()
const { t } = useI18n()
@@ -246,7 +277,10 @@ const refLimit = ref(10)
// ===== 折叠态(P0 溯源/引用/生命周期段折叠 + P1 内容/reasoning 超长折叠)=====
// 段落默认开合:溯源/引用默认展开(常用),生命周期默认收起(回顾性,通常无需常驻)
const sectionOpen = ref({ trace: true, refs: true, lifecycle: false })
const sectionOpen = ref({ trace: true, refs: true, lifecycle: false, related: true })
// 关联知识(阶段1 连边):detail payload 直带,空数组隐藏区块
const relatedItems = computed(() => props.detail?.related ?? [])
// 内容/reasoning 折叠:超阈值(300px / 120px)才显示「展开/收起」按钮
const CONTENT_FOLD_THRESHOLD = 300
@@ -363,7 +397,7 @@ function relativeTime(millisStr: string): string {
// 切换知识时重置引用列表「加载更多」上限 + 折叠态(对齐原 Knowledge.vue selectKnowledge 内 refLimit=10 行为)
watch(() => props.detail.knowledge.id, () => {
refLimit.value = 10
sectionOpen.value = { trace: true, refs: true, lifecycle: false }
sectionOpen.value = { trace: true, refs: true, lifecycle: false, related: true }
contentExpanded.value = false
reasoningExpanded.value = false
measureOverflow()
@@ -381,9 +415,11 @@ watch(renderedContent, () => {
.detail-section:last-child { border-bottom: none; margin-bottom: 0; }
.detail-head { display: flex; justify-content: space-between; align-items: flex-start; gap: 12px; margin-bottom: 12px; min-width: 0; flex-wrap: wrap; }
.detail-title-row { display: flex; align-items: center; gap: 8px; min-width: 0; flex: 1; overflow: hidden; }
/* flex-basis 用 auto(非 1 简写的 0%):basis=0 会让 flex-wrap 换行判定视标题行不占宽,
中间断点带(视口>760 且面板>420)按钮组永远挤进首行,标题被压成一字一行(2026-10-23 用户实证) */
.detail-title-row { display: flex; align-items: center; gap: 8px; min-width: 0; flex: 1 1 auto; overflow: hidden; }
.detail-kind-icon { font-size: 20px; flex-shrink: 0; }
.detail-title { font-size: 18px; font-weight: 500; color: var(--df-text); word-break: break-word; overflow-wrap: break-word; min-width: 0; }
.detail-title { font-size: var(--df-fs-title); font-weight: 500; color: var(--df-text); word-break: break-word; overflow-wrap: break-word; min-width: 0; }
.detail-actions { display: flex; gap: 6px; flex-shrink: 0; flex-wrap: wrap; }
.detail-badges { display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 14px; font-size: 11px; }
@@ -457,6 +493,14 @@ watch(renderedContent, () => {
.ref-query { color: var(--df-text-secondary); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.load-more { align-self: flex-start; margin-top: 4px; }
/* 关联知识(阶段1 连边):复用 ref-item 版式,点击跳对端 */
.related-item { cursor: pointer; text-align: left; border: none; width: 100%; }
.related-item:hover { background: var(--df-hover); }
.related-dir { color: var(--df-text-dim); flex-shrink: 0; }
.related-kind {
margin-left: auto; color: var(--df-text-dim); font-size: 11px; white-space: nowrap;
}
/* 生命周期时间线 */
.timeline { display: flex; flex-direction: column; }
.tl-node { display: flex; gap: 12px; position: relative; padding-bottom: 4px; }
@@ -490,11 +534,14 @@ watch(renderedContent, () => {
@media (max-width: 760px) {
.detail-head { flex-direction: column; align-items: flex-start; }
.detail-actions { width: 100%; }
.detail-title { font-size: 15px; }
/* 15px 字面量归档 --df-fs-* 谱:base 已是 --df-fs-title(16),降档取谱内下一档 --df-fs-body(13),保留防竖线化作用 */
.detail-title { font-size: var(--df-fs-body); }
}
@container (max-width: 420px) {
/* 620 断点带补全(2026-10-23):kn-layout 切单列后详情面板即整宽,标题/按钮改堆叠,
与 420 断点同款;缺此条则 620~420 带内标题行与按钮组同行硬挤(用户窄屏实证) */
@container (max-width: 620px) {
.detail-head { flex-direction: column; align-items: flex-start; }
.detail-actions { width: 100%; }
.detail-title { font-size: 15px; }
.detail-title { font-size: var(--df-fs-body); }
}
</style>
+10 -1
View File
@@ -11,7 +11,7 @@
</svg>
</div>
<span v-if="!collapsed" class="brand-text">DevFlow</span>
<span v-if="!collapsed" class="brand-version">v0.1</span>
<span v-if="!collapsed && appVersion" class="brand-version">v{{ appVersion }}</span>
</div>
<!-- Navigation -->
@@ -87,12 +87,16 @@ import { computed, ref, onMounted, onBeforeUnmount } from 'vue'
import { useAiStore } from '@/stores/ai'
import { useKnowledgeStore } from '@/stores/knowledge'
import { useAppSettingsStore } from '@/stores/appSettings'
import { settingsApi } from '@/api'
import { convStates } from '@/composables/ai/aiShared'
const aiStore = useAiStore()
const knowledgeStore = useKnowledgeStore()
const appSettings = useAppSettingsStore()
// 侧栏品牌区版本号(onMounted 拉真实版本;取失败回退为不显示,禁写死假版本)
const appVersion = ref('')
// ── 侧边栏收缩状态(持久化到 appSettings,小窗口自动收缩) ──
// 展开 220px(文字+图标) / 收缩 56px(仅图标,hover tooltip 显示文字)
// 优先级:用户手动设置 > 小窗口自动收缩(≤768px)
@@ -130,6 +134,11 @@ onMounted(async () => {
window.addEventListener('resize', checkWindowWidth)
// Ctrl+B 切换快捷键
window.addEventListener('keydown', handleCollapseKeydown)
// 真实版本号(编译期注入,与 AboutPanel 同源;失败静默 → 版本号不显示)
try {
const info = await settingsApi.getVersion()
appVersion.value = info.version
} catch { /* 静默:取版本失败不阻断侧栏渲染 */ }
})
onBeforeUnmount(() => {
+3 -2
View File
@@ -189,6 +189,7 @@ async function onInstall() {
top: 36px;
right: 0;
width: 320px;
max-width: calc(100vw - 24px);
padding: 14px 16px;
border: 0.5px solid var(--df-border);
border-radius: var(--df-radius-lg);
@@ -217,7 +218,7 @@ async function onInstall() {
.update-pop-bar {
width: 100%;
height: 6px;
border-radius: 3px;
border-radius: var(--df-radius-xs);
background: var(--df-neutral-bg);
overflow: hidden;
}
@@ -229,7 +230,7 @@ async function onInstall() {
.update-pop-progress-text {
margin-top: 6px;
font-size: 12px;
font-family: var(--df-font-mono, monospace);
font-family: var(--df-font-mono);
color: var(--df-text-dim);
}
.update-pop-error {
+1 -1
View File
@@ -1,7 +1,7 @@
<template>
<!-- 人工审批对话框(从 ProjectDetail.vue 抽离 submitting 保留)-->
<div v-if="visible" class="modal-overlay">
<div class="modal-box" style="width: 500px">
<div class="modal-box modal-box--md">
<h3 class="modal-title">{{ $t('projectDetail.approvalTitle') }}</h3>
<div v-if="pendingApproval">
<h3>{{ pendingApproval.title }}</h3>
+14 -49
View File
@@ -13,7 +13,7 @@
</div>
<div ref="containerRef" class="dep-graph__canvas" />
<div v-if="modules.length === 0 && !loading" class="dep-graph__empty">
<div class="empty-icon">📦</div>
<div class="empty-icon empty-icon--sm">📦</div>
<div>{{ $t('dependencyGraph.empty') }}</div>
</div>
@@ -34,19 +34,19 @@
<svg aria-hidden="true" width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="3" stroke-linecap="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
</button>
<!-- 添加依赖弹窗 -->
<div v-if="showAddDep" class="dep-graph__modal-overlay" @click.self="showAddDep = false">
<div class="dep-graph__modal">
<h3>{{ $t('dependencyGraph.addDepTitle') }}</h3>
<div class="dep-graph__modal-field">
<!-- 添加依赖弹窗(全局 .modal-overlay/.modal-box/.modal-field/.modal-actions 族) -->
<div v-if="showAddDep" class="modal-overlay" @click.self="showAddDep = false">
<div class="modal-box">
<h3 class="modal-title">{{ $t('dependencyGraph.addDepTitle') }}</h3>
<div class="modal-field">
<label>{{ $t('dependencyGraph.sourceLabel') }}</label>
<select v-model="depFrom" class="dep-graph__modal-input">
<select v-model="depFrom">
<option v-for="m in modules" :key="m.id" :value="m.id">{{ m.name }}</option>
</select>
</div>
<div class="dep-graph__modal-field">
<div class="modal-field">
<label>{{ $t('dependencyGraph.targetLabel') }}</label>
<select v-model="depTo" class="dep-graph__modal-input">
<select v-model="depTo">
<!-- G6:已存在 from→to 组合或选中自身作为目标时禁用该候选 -->
<option
v-for="m in modules"
@@ -56,9 +56,9 @@
>{{ m.name }}</option>
</select>
</div>
<div class="dep-graph__modal-field">
<div class="modal-field">
<label>{{ $t('dependencyGraph.depTypeLabel') }}</label>
<select v-model="depType" class="dep-graph__modal-input">
<select v-model="depType">
<option value="library">{{ $t('dependencyGraph.typeLibrary') }}</option>
<option value="api">{{ $t('dependencyGraph.typeApi') }}</option>
<option value="mq">{{ $t('dependencyGraph.typeMq') }}</option>
@@ -66,7 +66,7 @@
<option value="custom">{{ $t('dependencyGraph.typeCustom') }}</option>
</select>
</div>
<div class="dep-graph__modal-actions">
<div class="modal-actions">
<button class="btn btn-ghost btn-sm" @click="showAddDep = false">{{ $t('common.cancel') }}</button>
<button class="btn btn-primary btn-sm" :disabled="!depFrom || !depTo || depFrom === depTo || existingDepKeys.has(`${depFrom}->${depTo}`)" @click="onAddDep">{{ $t('dependencyGraph.confirm') }}</button>
</div>
@@ -536,48 +536,13 @@ onBeforeUnmount(() => {
pointer-events: none;
}
.empty-icon {
font-size: 32px;
opacity: 0.4;
}
/* 空态图标走 global.css .empty-icon.empty-icon--sm(32px 档,scoped 副本已删) */
/* 按钮基础样式 (.btn / .btn-ghost / .btn-sm / .btn-primary) 已收敛至 global.css */
/* 依赖图工具栏紧凑,覆 padding/font-size 避免按钮变大 */
.btn { padding: 4px 12px; font-size: 12px; }
/* 添加依赖弹窗 */
.dep-graph__modal-overlay {
position: fixed;
inset: 0;
background: rgba(0,0,0,0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 100;
}
.dep-graph__modal {
background: var(--df-bg-card);
border: 0.5px solid var(--df-border);
border-radius: var(--df-radius-lg);
padding: 20px;
min-width: 360px;
}
.dep-graph__modal h3 { font-size: 16px; color: var(--df-text); margin-bottom: 16px; }
.dep-graph__modal-field { margin-bottom: 12px; }
.dep-graph__modal-field label {
display: block; font-size: 12px; color: var(--df-text-secondary); margin-bottom: 4px;
}
.dep-graph__modal-input {
width: 100%; box-sizing: border-box;
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;
}
.dep-graph__modal-actions {
display: flex; justify-content: flex-end; gap: 8px; margin-top: 16px;
}
/* 添加依赖弹窗样式走 global.css .modal-overlay/.modal-box/.modal-title/.modal-field/.modal-actions(自建副本已删) */
/* 边删除按钮(HTML 遮罩层,绝对定位在 .dep-graph__canvas 上) */
.dep-edge-delete-btn {
+4 -8
View File
@@ -116,7 +116,7 @@
<span class="spinner"></span>
</div>
<div v-else-if="!currentModuleId" class="module-empty">
<div class="module-empty-icon">📦</div>
<div class="empty-icon empty-icon--xs">📦</div>
<p class="module-empty-hint">{{ $t('projectDetail.modulesEmptyHint') }}</p>
<div class="module-empty-actions">
<button class="btn btn-primary btn-sm" type="button" @click="openAddModule">{{ $t('fileExplorer.addModule') }}</button>
@@ -701,7 +701,7 @@ async function onRemoveModule() {
.crumb {
cursor: pointer;
padding: 2px 6px;
border-radius: 4px;
border-radius: var(--df-radius-sm);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
@@ -789,7 +789,7 @@ async function onRemoveModule() {
min-width: 14px;
height: 14px;
padding: 0 4px;
border-radius: 6px;
border-radius: var(--df-radius);
background: var(--df-accent);
color: #fff;
font-size: 9px;
@@ -835,11 +835,7 @@ async function onRemoveModule() {
height: 100%;
justify-content: center;
}
.module-empty-icon {
font-size: 28px;
opacity: 0.6;
line-height: 1;
}
/* 空态图标走 global .empty-icon.empty-icon--xs(28px 档,scoped 28px 副本已删) */
.module-empty-hint {
margin: 0;
font-size: 12px;
+15 -15
View File
@@ -678,7 +678,7 @@ onUnmounted(() => {
}
.preview-path {
font-family: var(--df-font-mono, 'Cascadia Code', Consolas, monospace);
font-family: var(--df-font-mono);
color: var(--df-text);
overflow: hidden;
text-overflow: ellipsis;
@@ -694,7 +694,7 @@ onUnmounted(() => {
font-size: 10px;
font-weight: 600;
padding: 1px 5px;
border-radius: 8px;
border-radius: var(--df-radius-md);
min-width: 14px;
text-align: center;
}
@@ -743,7 +743,7 @@ onUnmounted(() => {
flex-direction: column;
gap: 6px;
background: var(--df-hover);
border-radius: 8px;
border-radius: var(--df-radius-md);
padding: 12px 16px;
min-width: 280px;
max-width: 90%;
@@ -764,7 +764,7 @@ onUnmounted(() => {
.binary-meta-value {
color: var(--df-text-secondary);
font-family: var(--df-font-mono, Consolas, monospace);
font-family: var(--df-font-mono);
font-size: 12px;
word-break: break-all;
}
@@ -807,7 +807,7 @@ onUnmounted(() => {
.preview-code {
margin: 0;
padding: 14px 16px;
font-family: var(--df-font-mono, 'Cascadia Code', Consolas, 'Courier New', monospace);
font-family: var(--df-font-mono);
font-size: 12px;
line-height: 1.55;
white-space: pre;
@@ -843,7 +843,7 @@ onUnmounted(() => {
.preview-line-num {
padding: 0 10px 0 8px;
font-family: var(--df-font-mono, 'Cascadia Code', Consolas, monospace);
font-family: var(--df-font-mono);
font-size: 12px;
line-height: 1.55;
color: var(--df-text-dim);
@@ -858,7 +858,7 @@ onUnmounted(() => {
/* Diff 视图 — 在自身区域滚动,不溢出到父级(2026-10-18 合并文末重复定义) */
.preview-diff {
font-family: var(--df-font-mono, 'Cascadia Code', Consolas, monospace);
font-family: var(--df-font-mono);
font-size: 12px;
line-height: 1.55;
overflow: auto;
@@ -921,7 +921,7 @@ onUnmounted(() => {
gap: 5px;
padding: 4px 9px;
border: 0.5px solid var(--df-border);
border-radius: 8px;
border-radius: var(--df-radius-md);
background: var(--df-hover);
color: var(--df-text-secondary);
font-size: 12px;
@@ -938,7 +938,7 @@ onUnmounted(() => {
.outline-overlay-count {
background: var(--df-accent);
color: #fff;
border-radius: 7px;
border-radius: var(--df-radius-md); /* 原 7px 档外值,归 8px 档 */
padding: 0 5px;
font-size: 9px;
line-height: 14px;
@@ -946,13 +946,13 @@ onUnmounted(() => {
/* 展开态:悬浮面板(右上角弹出,固定宽 + 限高滚动 + 阴影) */
.preview-outline {
width: 280px;
width: min(280px, 100%);
max-height: 320px;
display: flex;
flex-direction: column;
background: rgba(20, 22, 34, 0.95);
border: 0.5px solid var(--df-border);
border-radius: 10px;
border-radius: var(--df-radius-lg);
box-shadow: var(--df-shadow-md);
overflow: hidden;
}
@@ -981,7 +981,7 @@ onUnmounted(() => {
.preview-outline-count {
background: rgba(255, 255, 255, 0.08);
border-radius: 8px;
border-radius: var(--df-radius-md);
padding: 0 6px;
font-size: 10px;
}
@@ -1015,7 +1015,7 @@ onUnmounted(() => {
.outline-group-count {
background: rgba(255, 255, 255, 0.07);
border-radius: 6px;
border-radius: var(--df-radius);
padding: 0 5px;
font-size: 9px;
}
@@ -1025,12 +1025,12 @@ onUnmounted(() => {
align-items: center;
gap: 6px;
padding: 2px 6px;
border-radius: 6px;
border-radius: var(--df-radius);
font-size: 11px;
cursor: pointer;
white-space: nowrap;
color: var(--df-text-secondary);
font-family: var(--df-font-mono, 'Cascadia Code', Consolas, monospace);
font-family: var(--df-font-mono);
transition: background 0.15s;
}
+3 -3
View File
@@ -446,7 +446,7 @@ watch(
justify-content: center;
width: 16px;
height: 16px;
border-radius: 3px;
border-radius: var(--df-radius-xs);
background: rgba(255, 255, 255, 0.15);
flex-shrink: 0;
}
@@ -481,7 +481,7 @@ watch(
font-size: 10px;
line-height: 1.4;
padding: 1px 5px;
border-radius: 8px;
border-radius: var(--df-radius-md);
background: rgba(150, 150, 150, 0.14);
color: var(--df-text-dim);
}
@@ -498,7 +498,7 @@ watch(
font-size: 10px;
font-weight: 600;
padding: 1px 5px;
border-radius: 8px;
border-radius: var(--df-radius-md);
min-width: 14px;
text-align: center;
}
+17 -21
View File
@@ -125,18 +125,14 @@
</div>
</div>
<!-- 加载更多 -->
<div
<!-- 加载更多(真按钮:键盘语义原生自带,替代原 div+tabindex 伪按钮) -->
<button
v-if="hasMoreCommits && !loadingMore"
class="gc-load-more"
role="button"
tabindex="0"
class="btn btn-ghost btn-sm gc-load-more"
@click="loadMoreCommits"
@keydown.enter.prevent="loadMoreCommits"
@keydown.space.prevent="loadMoreCommits"
>
{{ $t('gitChanges.loadMore') }}
</div>
</button>
<div v-if="loadingMore" class="gc-loading"><span class="spinner"></span></div>
</div>
</div>
@@ -598,7 +594,7 @@ watch(() => props.refreshKey, () => {
color: var(--df-text-dim);
cursor: pointer;
padding: 2px 6px;
border-radius: 4px;
border-radius: var(--df-radius-sm);
display: flex;
align-items: center;
transition: color 0.15s, background 0.15s;
@@ -644,7 +640,7 @@ watch(() => props.refreshKey, () => {
.gc-search-input {
flex: 1; min-width: 0;
background: var(--df-hover); border: 0.5px solid var(--df-border);
border-radius: 4px; height: var(--df-h-input); padding: 0 8px;
border-radius: var(--df-radius-sm); height: var(--df-h-input); padding: 0 8px;
color: var(--df-text); font-size: 12px; outline: none;
}
.gc-search-input:focus { border-color: var(--df-accent); }
@@ -669,7 +665,7 @@ watch(() => props.refreshKey, () => {
font-size: 11px; color: var(--df-text-dim);
}
.gc-commit-hash {
font-family: var(--df-font-mono, Consolas, monospace);
font-family: var(--df-font-mono);
font-size: 11px; color: var(--df-accent); flex-shrink: 0;
}
.gc-commit-author {
@@ -698,7 +694,7 @@ watch(() => props.refreshKey, () => {
border-bottom: 0.5px solid var(--df-border);
}
.gc-commit-detail-hash {
font-family: var(--df-font-mono, Consolas, monospace);
font-family: var(--df-font-mono);
font-size: 11px; color: var(--df-accent); flex-shrink: 0;
}
.gc-commit-detail-author {
@@ -744,12 +740,12 @@ watch(() => props.refreshKey, () => {
.gc-file-path {
flex: 1; min-width: 0;
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
font-family: var(--df-font-mono, Consolas, monospace); font-size: 12px;
font-family: var(--df-font-mono); font-size: 12px;
}
.gc-file-name {
flex: 1; min-width: 0;
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
font-family: var(--df-font-mono, Consolas, monospace); font-size: 12px;
font-family: var(--df-font-mono); font-size: 12px;
}
.gc-file-row {
display: flex; align-items: center; gap: 8px;
@@ -759,12 +755,12 @@ watch(() => props.refreshKey, () => {
.gc-file-row.selected { background: rgba(255,255,255,0.06); }
/* 加载更多 */
/* 加载更多:真按钮形态(.btn.btn-ghost.btn-sm 全局),此处仅留列表内定位 */
.gc-load-more {
text-align: center; padding: 12px; cursor: pointer;
color: var(--df-accent); font-size: 12px;
transition: background 0.1s; flex-shrink: 0;
align-self: center;
margin-top: 4px;
flex-shrink: 0;
}
.gc-load-more:hover { background: var(--df-hover); }
/* 分支列表弹窗 */
.gc-branch-picker {
@@ -799,11 +795,11 @@ watch(() => props.refreshKey, () => {
}
.gc-branch-item.current { color: var(--df-accent); }
.gc-branch-icon { font-size: 10px; flex-shrink: 0; }
.gc-branch-name { font-family: var(--df-font-mono, Consolas, monospace); }
.gc-branch-name { font-family: var(--df-font-mono); }
.gc-branch-tag {
font-size: 9px;
padding: 1px 5px;
border-radius: 4px;
border-radius: var(--df-radius-sm);
background: var(--df-accent-soft);
color: var(--df-accent);
}
@@ -823,7 +819,7 @@ watch(() => props.refreshKey, () => {
}
.gc-skeleton-row {
height: 24px;
border-radius: 4px;
border-radius: var(--df-radius-sm);
background: linear-gradient(90deg, var(--df-hover) 0%, rgba(255,255,255,0.08) 50%, var(--df-hover) 100%);
background-size: 200% 100%;
animation: df-shimmer 1.5s infinite linear; /* 消费 global.css df-shimmer(原本地 gc-shimmer 重复定义已删) */
+2 -2
View File
@@ -68,7 +68,7 @@ const stackList = computed(() => parseJsonArray(data.value?.stack).slice(0, 3))
box-sizing: border-box;
background: var(--df-bg-card);
border: 1px solid var(--df-border-strong);
border-radius: 6px;
border-radius: var(--df-radius);
cursor: pointer;
transition: border-color 0.15s;
}
@@ -113,7 +113,7 @@ const stackList = computed(() => parseJsonArray(data.value?.stack).slice(0, 3))
.module-node__tag {
font-size: 9px;
padding: 1px 6px;
border-radius: 4px;
border-radius: var(--df-radius-sm);
background: var(--df-accent-bg);
color: var(--df-accent);
}
+2 -2
View File
@@ -164,7 +164,7 @@ onBeforeUnmount(() => window.removeEventListener('resize', onWinResize))
align-items: center;
gap: 8px;
}
.card-name { font-size: 17px; font-weight: 500; color: var(--df-text); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.card-name { font-size: var(--df-fs-title); font-weight: 500; color: var(--df-text); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.stage-badge {
font-size: 12px; font-weight: 500;
@@ -241,7 +241,7 @@ onBeforeUnmount(() => window.removeEventListener('resize', onWinResize))
display: inline-block;
width: 18px;
height: 12px;
border-radius: 3px;
border-radius: var(--df-radius-xs);
background: linear-gradient(90deg, var(--df-hover) 0%, rgba(255,255,255,0.08) 50%, var(--df-hover) 100%);
background-size: 200% 100%;
animation: df-shimmer 1.5s infinite linear; /* 消费 global.css df-shimmer(原本地 pc-shimmer 重复定义已删) */
+4 -4
View File
@@ -306,7 +306,7 @@ async function onIntervalChange() {
flex: 0 0 auto;
margin-left: auto;
font-size: 13px;
font-family: var(--df-font-mono, monospace);
font-family: var(--df-font-mono);
color: var(--df-text);
white-space: nowrap;
}
@@ -314,7 +314,7 @@ async function onIntervalChange() {
flex: 0 0 auto;
margin-left: auto;
font-size: 12px;
font-family: var(--df-font-mono, monospace);
font-family: var(--df-font-mono);
color: var(--df-text-dim);
white-space: nowrap;
}
@@ -377,7 +377,7 @@ async function onIntervalChange() {
.update-progress-bar {
width: 100%;
height: 6px;
border-radius: 3px;
border-radius: var(--df-radius-xs);
background: var(--df-neutral-bg);
overflow: hidden;
}
@@ -389,7 +389,7 @@ async function onIntervalChange() {
.update-progress-text {
margin-top: 6px;
font-size: 12px;
font-family: var(--df-font-mono, monospace);
font-family: var(--df-font-mono);
color: var(--df-text-dim);
}
/* 安装错误提示:底色/边/圆角/字号走全局 .df-note--danger(token 直引无回退),此处仅留排版 */
+1 -1
View File
@@ -278,7 +278,7 @@ function onApprovalTimeoutChange() {
width: 100%;
min-width: 0;
box-sizing: border-box;
font-family: var(--df-font-mono, monospace);
font-family: var(--df-font-mono);
}
/* 自定义提示词行:整行堆叠,textarea 独立一行从左到右拉满。
覆盖 SettingRow 默认横排(label 左 + control 右)。 */
+1 -1
View File
@@ -301,7 +301,7 @@ onMounted(load)
.script-safety-input {
width: 280px;
max-width: 100%;
font-family: var(--df-font-mono, monospace);
font-family: var(--df-font-mono);
}
.script-safety-note {
font-size: 11px; color: var(--df-text-dim); margin: 4px 0 0; line-height: 1.5;
+5 -3
View File
@@ -22,8 +22,8 @@
</div>
</div>
<div class="conn-actions">
<button class="btn-link" @click="openConnForm(conn)">{{ $t('common.edit') }}</button>
<button class="btn-link btn-link-danger" @click="removeConn(conn.id)">{{ $t('common.delete') }}</button>
<button class="df-link" @click="openConnForm(conn)">{{ $t('common.edit') }}</button>
<button class="df-link btn-link-danger" @click="removeConn(conn.id)">{{ $t('common.delete') }}</button>
</div>
</div>
</div>
@@ -297,7 +297,9 @@ onMounted(loadConnections)
.conn-icon { font-size: 18px; }
.conn-info { display: flex; flex-direction: column; gap: 2px; }
.conn-name-row { display: flex; align-items: center; gap: 8px; }
.conn-name { font-size: 14px; font-weight: 500; color: var(--df-text); }
.conn-name { font-size: var(--df-fs-item); font-weight: 500; color: var(--df-text); }
/* df-link(components.css 后加载)同特异性覆盖 .btn-link-danger 的 danger 色,就地补回删除确认的红色语义 */
.df-link.btn-link-danger { color: var(--df-danger); }
.conn-type {
font-size: 10px;
padding: 2px 6px;
+1 -1
View File
@@ -149,7 +149,7 @@ async function save() {
max-width: 100%;
min-height: 64px;
resize: vertical;
font-family: var(--df-font-mono, monospace);
font-family: var(--df-font-mono);
}
.feishu-hint {
margin: 0 0 4px;
+1 -1
View File
@@ -55,7 +55,7 @@
<input ref="inputApiKey" v-model="providerForm.apiKey" class="setting-input" :type="showApiKey ? 'text' : 'password'"
:class="{ 'setting-input--invalid': fieldErrors.apiKey }"
:placeholder="$t('settings.phApiKey')" @input="fieldErrors.apiKey = false" />
<button type="button" class="btn-link" :title="$t(showApiKey ? 'settings.apiKeyHide' : 'settings.apiKeyShow')"
<button type="button" class="df-link" :title="$t(showApiKey ? 'settings.apiKeyHide' : 'settings.apiKeyShow')"
@click="showApiKey = !showApiKey">
{{ $t(showApiKey ? 'settings.apiKeyHide' : 'settings.apiKeyShow') }}
</button>
+1 -1
View File
@@ -116,7 +116,7 @@ async function save() {
/* panel/panel-header/btn 系列/setting-input 均在 settings.css 全局,此处仅本面板特有样式 */
.uclaw-settings { display: flex; flex-direction: column; gap: 4px; }
/* 定宽输入补 max-width,窄窗收缩防横向溢出 */
.uclaw-input { width: 320px; max-width: 100%; font-family: var(--df-font-mono, monospace); }
.uclaw-input { width: 320px; max-width: 100%; font-family: var(--df-font-mono); }
/* 原 var(--df-text-tertiary) 为幽灵 token(全 styles 未定义,渲染成继承色),改用已定义的弱化色 */
.uclaw-hint {
font-size: 11px;
@@ -11,9 +11,9 @@
<span v-if="p.enabled === false" class="pool-badge">{{ $t('settings.badgePoolDisabled') }}</span>
</div>
<div class="provider-actions">
<button class="btn-link" @click="emit('set-default', p.id)" v-if="!p.is_default">{{ $t('settings.actionSetDefault') }}</button>
<button class="btn-link" @click="emit('edit', p)">{{ $t('common.edit') }}</button>
<button class="btn-link btn-link-danger" @click="emit('delete', p.id)">{{ $t('common.delete') }}</button>
<button class="df-link" @click="emit('set-default', p.id)" v-if="!p.is_default">{{ $t('settings.actionSetDefault') }}</button>
<button class="df-link" @click="emit('edit', p)">{{ $t('common.edit') }}</button>
<button class="df-link btn-link-danger" @click="emit('delete', p.id)">{{ $t('common.delete') }}</button>
</div>
</div>
<div class="provider-detail">
@@ -163,7 +163,9 @@ defineExpose({
margin-bottom: 10px;
}
.provider-info { display: flex; align-items: center; gap: 8px; }
.provider-name { font-size: 14px; font-weight: 500; color: var(--df-text); }
.provider-name { font-size: var(--df-fs-item); font-weight: 500; color: var(--df-text); }
/* df-link(components.css 后加载)同特异性覆盖 .btn-link-danger 的 danger 色,就地补回删除确认的红色语义 */
.df-link.btn-link-danger { color: var(--df-danger); }
.default-badge {
font-size: 10px;
padding: 1px 6px;
+12 -7
View File
@@ -149,13 +149,18 @@ function cyclePanelMode() {
/** 拉取 provider 列表(用于顶部 bar 展示与切换) + 同步 DB 默认 provider 到 activeProvider */
async function loadProviders() {
const list = await aiApi.listProviders()
state.providers = list
// 重启后 activeProvider=null,从 DB is_default 恢复(后端链路正确,仅前端缺同步)
// 守卫加有效性检查 — savedProvider 指向已删除 provider 时残留无效 id
if (!state.activeProvider || !list.some((p: AiProviderConfig) => p.id === state.activeProvider)) {
const def = list.find((p: AiProviderConfig) => p.is_default)
if (def) state.activeProvider = def.id
try {
const list = await aiApi.listProviders()
state.providers = list
// 重启后 activeProvider=null,从 DB is_default 恢复(后端链路正确,仅前端缺同步)
// 守卫加有效性检查 — savedProvider 指向已删除 provider 时残留无效 id
if (!state.activeProvider || !list.some((p: AiProviderConfig) => p.id === state.activeProvider)) {
const def = list.find((p: AiProviderConfig) => p.is_default)
if (def) state.activeProvider = def.id
}
} finally {
// 成功/失败都置 true:列表已定形,MessageList 空态据此决定是否显示「未配置」引导
state.providersLoaded = true
}
}
+4 -1
View File
@@ -7,7 +7,10 @@ import en from './en'
const getInitialLocale = (): string => {
if (typeof window !== 'undefined') {
try {
return localStorage.getItem('df-language') || 'zh-CN'
const saved = localStorage.getItem('df-language')
// 已设置:尊重用户选择;未设置:navigator.language 探测(zh 开头→zh-CN,否则→en)
if (saved) return saved
return (navigator.language || '').toLowerCase().startsWith('zh') ? 'zh-CN' : 'en'
} catch {
return 'zh-CN'
}
+41
View File
@@ -61,6 +61,14 @@
2026-10-18 配色收口:Material 并行色板(hex/rgba)全清,改语义 token 三件套;
testing(#ff9800)/deferred(#ffc107)原 amber/orange 系按配色审计归警告族;
review/testing/deferred 三态同走 warning,靠徽章文字区分。 */
/* — 审批日志状态(AuditLog 7 态)并入统一族:approved/rejected 与灵感族同色直接复用;
其余 5 态按族三件套补齐(interrupted 中性档同 draft 无边框)。
原 AuditLog.vue scoped 副本已删,此处为唯一真值。 */
.status-pending { background: var(--df-warning-bg); color: var(--df-warning); border: 1px solid var(--df-warning-bd); }
.status-executing { background: var(--df-info-bg); color: var(--df-info); border: 1px solid var(--df-info-bd); }
.status-completed { background: var(--df-success-bg); color: var(--df-success); border: 1px solid var(--df-success-bd); }
.status-failed { background: var(--df-danger-bg); color: var(--df-danger); border: 1px solid var(--df-danger-bd); }
.status-interrupted { background: var(--df-neutral-bg); color: var(--df-text-dim); }
.status-todo { background: var(--df-neutral-bg); color: var(--df-text-dim); }
.status-progress { background: var(--df-info-bg); color: var(--df-info); border: 1px solid var(--df-info-bd); }
.status-review { background: var(--df-warning-bg); color: var(--df-warning); border: 1px solid var(--df-warning-bd); }
@@ -340,6 +348,12 @@
background: var(--df-danger);
color: #fff;
}
/* 热区扩至 24px 级:视觉 16px 不变,::after 四向外扩 4px 兜住触达(缩略图角部难点准) */
.ai-img-preview-x::after {
content: '';
position: absolute;
inset: -4px;
}
/* — AiChat 图标按钮(共享工具类):TopBar header-actions + ConversationSidebar 新建按钮 —
原 AiChat.vue <style scoped> L1039-1072 抽取(非 scoped 全局,供两个 ai/ 子组件共享)。
@@ -458,3 +472,30 @@
color: var(--df-text);
border-bottom-color: var(--df-accent);
}
/* — .df-seg: 分段控件(连体式视图/档位切换) —
蓝本:Projects.vue .view-toggle 连体式提纯;容器底色+圆角+overflow hidden,
按钮 active=accent-soft 无边框(不用描边/浮起 pill,保持扁平连体) */
.df-seg {
display: inline-flex;
background: var(--df-bg-raised);
border: 0.5px solid var(--df-border);
border-radius: var(--df-radius-sm);
overflow: hidden;
}
.df-seg-btn {
min-width: 30px;
padding: 6px 10px;
background: transparent;
border: none;
color: var(--df-text-dim);
font-size: var(--df-fs-item);
line-height: 1;
cursor: pointer;
transition: background 0.15s, color 0.15s;
}
.df-seg-btn:hover { background: var(--df-bg-card); color: var(--df-text); }
.df-seg-btn.active {
background: var(--df-accent-soft);
color: var(--df-accent);
}
+35 -1
View File
@@ -5,6 +5,9 @@
═══════════════════════════════════════════════════════════ */
:root {
/* UA 原生控件(select 弹层/滚动条/input 默认底)跟随暗色主题(2026-10-23) */
color-scheme: dark;
/* — Surface — 2026-10-18 UI 升级:参考 u-agent 管理台 GitHub Dark 四级灰阶(#0d1117→#22272e),
替换原紫调深底,信息密度型界面灰阶更中性、层级更清晰 */
--df-bg: #0d1117;
@@ -25,6 +28,8 @@
--df-accent-hover: #6a5de0;
--df-accent-soft: rgba(123,111,240,0.15);
--df-accent-bg: rgba(123,111,240,0.08);
/* 实底(accent/语义色)之上的前景色:实底钮/实底徽章白字统一档,深浅主题同值 */
--df-on-accent: #ffffff;
/* — Semantic — */
/* 青绿:成功/流程 */
@@ -54,6 +59,7 @@
--df-fs-aux: 11px; /* 辅助文字/列表次级 */
--df-fs-ctrl: 12px; /* 控件文字/小按钮 */
--df-fs-body: 13px; /* 正文基准 */
--df-fs-item: 14px; /* 列表项/卡片主文字 */
--df-fs-title: 16px; /* 页头标题 */
/* — 控件高度档 — */
@@ -95,6 +101,7 @@
--df-gap-grid: 12px; /* 卡片/网格 gap */
--df-gap-head: 10px; /* 面板标题→内容 */
--df-pad-panel: 14px 16px; /* 面板/卡片内边距 */
--df-pad-page: 16px 20px 20px; /* 页面内边距:上/左右/下(视图容器统一档) */
--df-msg-max-width: 90%; /* AI 气泡/工具卡片最大宽度(对齐不撑满) */
/* — Typography: 400/500/600 字重 — */
@@ -114,6 +121,8 @@
/* ═══ 亮色主题 ═══ */
[data-theme="light"] {
color-scheme: light;
--df-bg: #f5f6fa;
--df-bg-raised: #ffffff;
--df-bg-card: #ffffff;
@@ -169,6 +178,17 @@
* { margin: 0; padding: 0; box-sizing: border-box; }
/* — 2026-10-23 暗色适配修复:button UA 样式重置 —
.df-tab 等未声明 background/border 的按钮类在暗色页露出 UA buttonface
浅灰白底 + outset 边框(实证:知识库页顶部 tab、知识详情编辑/拒绝按钮);
UA 按钮字体与文字色不继承,一并收敛。padding 布局不动,由各按钮类自持。 */
button {
font: inherit;
color: inherit;
background: none;
border: none;
}
html, body, #app {
height: 100%;
width: 100%;
@@ -297,7 +317,7 @@ html, body, #app {
height: 46px;
margin-bottom: var(--df-gap-head);
}
.page-header h1 { font-size: var(--df-fs-title); font-weight: 500; color: var(--df-text); }
.page-header h1 { font-size: var(--df-fs-title); font-weight: 500; color: var(--df-text); flex-shrink: 0; white-space: nowrap; }
.header-actions { display: flex; gap: 8px; align-items: center; }
/* — Button(唯一基类,高度档 --df-h-btn;settings.css 副本已删除并轨,样式大重构 2026-10-18)— */
@@ -353,6 +373,9 @@ html, body, #app {
min-width: min(360px, 90vw);
max-width: 90vw;
}
/* 尺寸档:--sm 小确认框/--md 中表单框(基准 360px) */
.modal-box--sm { min-width: min(300px, 90vw); }
.modal-box--md { min-width: min(520px, 90vw); }
.modal-title { font-size: 18px; font-weight: 500; color: var(--df-text); margin-bottom: 16px; }
.modal-field { margin-bottom: 14px; }
.modal-field label { display: block; font-size: 12px; color: var(--df-text-dim); margin-bottom: 4px; }
@@ -378,6 +401,9 @@ html, body, #app {
.empty-state { text-align: center; padding: 60px 20px; font-size: 14px; color: var(--df-text-dim); }
.empty-hint { font-size: 13px; color: var(--df-text-dim); padding: 12px 0; }
.empty-icon { font-size: 48px; margin-bottom: 12px; }
/* 紧凑空态档:--sm 列表内空态 / --xs 卡片内空态(基准 48px 用于整页空态) */
.empty-icon--sm { font-size: 32px; }
.empty-icon--xs { font-size: 28px; }
/* — Back Link(详情页返回链接)— ProjectDetail / TaskDetail 共享 */
.back-link { font-size: 13px; color: var(--df-accent); text-decoration: none; white-space: nowrap; }
@@ -443,6 +469,14 @@ html, body, #app {
.df-badge--danger { background: var(--df-danger-bg); color: var(--df-danger); border: 1px solid var(--df-danger-bd); }
.df-badge--info { background: var(--df-info-bg); color: var(--df-info); border: 1px solid var(--df-info-bd); }
.df-badge--accent { background: var(--df-accent-soft); color: var(--df-accent); border: 1px solid var(--df-accent-bd); }
/* 实底变体(--solid):底=语义实色、字=on-accent 白,强对比强调场景(选中态/危险确认)。
与色彩变体叠加使用(如 .df-badge--danger.df-badge--solid → 实底红);单独使用默认 accent 实底 */
.df-badge--solid { background: var(--df-accent); border: 1px solid transparent; color: var(--df-on-accent); }
.df-badge--success.df-badge--solid { background: var(--df-success); }
.df-badge--warning.df-badge--solid { background: var(--df-warning); }
.df-badge--danger.df-badge--solid { background: var(--df-danger); }
.df-badge--info.df-badge--solid { background: var(--df-info); }
.df-badge--accent.df-badge--solid { background: var(--df-accent); }
/* — 标签(tag,列表项内联小标签) — */
.df-tag {
+2 -2
View File
@@ -565,7 +565,7 @@ function onDragEnd() { isDragging.value = false }
min-width: 16px;
height: 16px;
padding: 0 4px;
border-radius: 8px;
border-radius: var(--df-radius-md);
background: var(--df-danger);
color: #fff;
font-size: 9px;
@@ -656,7 +656,7 @@ function onDragEnd() { isDragging.value = false }
.popup-item-tag {
flex-shrink: 0;
padding: 1px 6px;
border-radius: 8px;
border-radius: var(--df-radius-md);
font-size: 9px;
font-weight: 600;
text-transform: uppercase;
+5 -21
View File
@@ -483,8 +483,7 @@ onMounted(() => {
<style scoped>
.audit-log {
padding: var(--df-pad-page, 24px);
max-width: 1200px;
padding: var(--df-pad-page);
}
/* page-header / btn 等已提取到 global.css 全局 */
@@ -492,11 +491,11 @@ onMounted(() => {
.page-desc {
font-size: 12px;
color: var(--df-text-dim);
margin-bottom: 16px;
margin-bottom: var(--df-gap-page);
}
/* ===== 筛选栏 ===== */
.audit-filters { gap: 8px; margin-bottom: 16px; }
.audit-filters { gap: 8px; margin-bottom: var(--df-gap-page); }
.audit-filter-select {
padding: 6px 10px;
border: 0.5px solid var(--df-border);
@@ -637,22 +636,7 @@ onMounted(() => {
.risk-medium { background: var(--df-warning-bg); color: var(--df-warning); }
.risk-high { background: var(--df-danger-bg); color: var(--df-danger); }
/* 状态 tag */
.status-tag {
display: inline-block;
font-size: 11px;
font-weight: 500;
padding: var(--df-pad-badge);
border-radius: var(--df-radius-sm);
white-space: nowrap;
}
.status-pending { background: var(--df-warning-bg); color: var(--df-warning); }
.status-approved { background: var(--df-success-bg); color: var(--df-success); }
.status-rejected { background: var(--df-danger-bg); color: var(--df-danger); }
.status-executing { background: var(--df-info-bg); color: var(--df-info); }
.status-completed { background: var(--df-success-bg); color: var(--df-success); }
.status-failed { background: var(--df-danger-bg); color: var(--df-danger); }
.status-interrupted { background: var(--df-neutral-bg); color: var(--df-text-dim); }
/* 状态 tag 走 components.css .status-tag 统一族(audit 7 态变体已并入) */
/* 决策者 tag */
.decided-tag {
@@ -673,7 +657,7 @@ onMounted(() => {
align-items: center;
justify-content: center;
gap: 16px;
margin-top: 20px;
margin-top: var(--df-gap-grid);
}
.pager-info {
font-size: 12px;
+7 -1
View File
@@ -109,7 +109,7 @@ onMounted(() => {
</script>
<style scoped>
.dashboard { padding: 16px 20px 20px; }
.dashboard { padding: 16px 20px 20px; container-type: inline-size; }
/* ═══ Header(容器/标题走全局 .page-header,此处仅保留副标题装饰) ═══ */
.dash-subtitle {
@@ -181,6 +181,12 @@ onMounted(() => {
.dash-right > .df-panel { flex: 1 1 280px; min-width: 0; }
}
/* 双闸:@media 管窗口本身窄;@container 管「视口不窄但主区被 AI 面板挤压」场景
(AI 面板开合挤压主区时 .dashboard 实际宽度 < 620px,media query 感知不到)。 */
@container (max-width: 620px) {
.dash-grid { grid-template-columns: 1fr; }
}
/* 宽窗口:右栏展开 */
@media (min-width: 1201px) {
.dash-grid { grid-template-columns: 1fr 340px; }
+25 -3
View File
@@ -53,7 +53,10 @@
<section class="idea-list-panel">
<div class="idea-list">
<!-- 三态:加载/空(错误改走顶部 error-banner,WK-7 不整表替换列表) -->
<div v-if="store.loading" class="empty-state">{{ $t('common.loading') }}</div>
<div v-if="store.loading" class="empty-state page-loading">
<svg aria-hidden="true" class="page-spinner" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M21 12a9 9 0 11-6.219-8.56"/></svg>
{{ $t('common.loading') }}
</div>
<div v-else-if="filteredIdeas.length === 0" class="empty-state">{{ activeFilter === 'archived' ? $t('ideas.archiveBoxEmpty') : $t('ideas.listEmpty') }}</div>
<template v-else>
<div
@@ -554,7 +557,7 @@ watch(() => route.params.id, (id) => {
</script>
<style scoped>
.ideas { padding: 16px 20px 20px; }
.ideas { padding: 16px 20px 20px; container-type: inline-size; }
/* page-header / btn / modal / filter-bar / filter-btn / search-box / empty-state 系列已提取到 global.css 全局 */
@@ -606,7 +609,7 @@ watch(() => route.params.id, (id) => {
.idea-card:hover { background: var(--df-accent-bg); }
.idea-card.selected { background: var(--df-accent-soft); border: 0.5px solid var(--df-accent); }
.idea-title { font-size: 14px; font-weight: 500; color: var(--df-text); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.idea-title { font-size: var(--df-fs-item); font-weight: 500; color: var(--df-text); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.idea-score {
font-size: 13px; font-weight: 500;
@@ -646,6 +649,17 @@ watch(() => route.params.id, (id) => {
}
/* empty-state / empty-icon 已提取到 global.css 全局 */
/* 页级首载:spinner+文字(对齐 AuditLog 首载形态,动画消费 global df-spin) */
.page-loading {
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
padding: 40px;
color: var(--df-text-dim);
}
.page-spinner { animation: df-spin 0.9s linear infinite; flex-shrink: 0; }
/* ===== 响应式 ===== */
@media (max-width: 768px) {
.ideas { padding: 16px; }
@@ -660,6 +674,14 @@ watch(() => route.params.id, (id) => {
}
}
/* 双闸:@media 管窗口本身窄;@container 管「视口不窄但主区被 AI 面板挤压」场景
(AI 面板开合挤压主区时 .ideas 实际宽度 < 620px,media query 感知不到)。 */
@container (max-width: 620px) {
.ideas-layout {
grid-template-columns: 1fr;
}
}
/* ===== 模态框字段样式(modal-overlay/box/field/actions 已提取到 global.css) ===== */
/* Ideas 模态的 select 用 .modal-field select 全局覆盖,无需额外 scoped */
</style>
+4 -4
View File
@@ -111,13 +111,14 @@
@archive="archiveCurrent"
@restore="restoreCurrent"
@resubmit="resubmitCurrent"
@open-related="selectKnowledge"
/>
<!-- 详情加载中(切换知识时清空旧详情,避免残留上一条内容;失败走 toast,不再静默) -->
<div v-else-if="detailLoading" class="detail-empty">{{ $t('common.loading') }}</div>
<div v-else class="detail-empty">
<div class="detail-empty-icon">👈</div>
<div class="empty-icon">👈</div>
<div>{{ $t('knowledge.detailEmptyHint') }}</div>
</div>
</div>
@@ -126,7 +127,7 @@
<!-- ===== 新增知识对话框 ===== -->
<div v-if="showCreateModal" class="modal-overlay" @click.self="closeCreateModal">
<div class="modal-box">
<div class="modal-title">{{ $t('knowledge.createTitle') }}</div>
<h3 class="modal-title">{{ $t('knowledge.createTitle') }}</h3>
<div class="form-row">
<label>{{ $t('knowledge.kindLabel') }}</label>
<select v-model="form.kind" class="form-select">
@@ -552,9 +553,8 @@ onMounted(() => {
.meta-reuse { color: var(--df-info); }
.list-empty { text-align: center; padding: 40px 0; color: var(--df-text-dim); font-size: 13px; }
/* ===== 详情面板空态(详情四段样式已随 KnowledgeDetail 子组件迁移)===== */
/* ===== 详情面板空态(图标走 global .empty-icon 默认 48 档,scoped 40px 字号已删)===== */
.detail-empty { flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: center; color: var(--df-text-dim); gap: 12px; }
.detail-empty-icon { font-size: 40px; }
/* 状态徽章 / 置信度(列表卡片复用,详情由子组件 scoped 自带) */
.kn-card-status { font-size: 11px; font-weight: 500; padding: 2px 8px; border-radius: var(--df-radius-xs); white-space: nowrap; }
+14 -12
View File
@@ -161,7 +161,7 @@
<!-- 空态引导(无工程不隐藏,显卡片 + CTA) -->
<div v-if="modules.length === 0" class="module-empty">
<div class="module-empty-icon">📦</div>
<div class="empty-icon empty-icon--xs">📦</div>
<p class="module-empty-hint">{{ $t('projectDetail.modulesEmptyHint') }}</p>
<div class="module-empty-actions">
<button class="btn btn-primary btn-sm" type="button" @click="openAddModule">{{ $t('fileExplorer.addModule') }}</button>
@@ -1303,11 +1303,7 @@ onBeforeUnmount(() => {
padding: 20px 12px;
text-align: center;
}
.module-empty-icon {
font-size: 28px;
opacity: 0.6;
line-height: 1;
}
/* 空态图标走 global .empty-icon.empty-icon--xs(28px 档,scoped 28px 副本已删) */
.module-empty-hint {
margin: 0;
font-size: 12px;
@@ -1357,7 +1353,7 @@ onBeforeUnmount(() => {
}
.module-path-overview {
font-size: 12px;
font-family: 'SF Mono', 'Fira Code', monospace;
font-family: var(--df-font-mono);
color: var(--df-text-dim);
white-space: nowrap;
overflow: hidden;
@@ -1416,7 +1412,7 @@ onBeforeUnmount(() => {
/* ===== 代码目录 / 技术栈 ===== */
.path-row { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.path-text {
font-size: 12px; font-family: 'SF Mono', 'Fira Code', monospace;
font-size: 12px; font-family: var(--df-font-mono);
color: var(--df-text-secondary); word-break: break-all;
}
.path-ok { color: var(--df-success); font-size: 12px; }
@@ -1493,7 +1489,7 @@ onBeforeUnmount(() => {
</style>
<style scoped>
.project-detail { padding: 16px 20px 20px; display: flex; flex-direction: column; height: 100%; min-height: 0; max-height: 100%; overflow: hidden; }
.project-detail { padding: 16px 20px 20px; display: flex; flex-direction: column; height: 100%; min-height: 0; max-height: 100%; overflow: hidden; container-type: inline-size; }
/* ===== Tab 导航(Batch 10 文件浏览器) ===== */
/* 按钮走全局 .df-tab/.df-tab--active(下划线范式),此处仅归零 button 默认样式;
@@ -1673,7 +1669,7 @@ onBeforeUnmount(() => {
.task-card:hover { background: var(--df-bg-card-hover); }
.task-card:last-child { border-bottom: none; }
.task-top { display: flex; justify-content: space-between; align-items: center; margin-bottom: 6px; }
.task-title { font-size: 14px; font-weight: 500; color: var(--df-text); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.task-title { font-size: var(--df-fs-item); font-weight: 500; color: var(--df-text); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.task-status { font-size: var(--df-fs-aux); padding: var(--df-pad-badge); border-radius: var(--df-radius-xs); }
/* 状态色类已全局化至 components.css;此处仅保留本视图特有的进度覆盖 */
.status-progress { background: var(--df-accent-soft); color: var(--df-accent); }
@@ -1687,7 +1683,7 @@ onBeforeUnmount(() => {
.branch-icon { color: var(--df-accent); font-size: 13px; }
.branch-name {
font-size: var(--df-fs-ctrl);
font-family: 'SF Mono', 'Fira Code', monospace;
font-family: var(--df-font-mono);
color: var(--df-text-secondary);
background: var(--df-accent-bg);
padding: var(--df-pad-chip);
@@ -1702,7 +1698,7 @@ onBeforeUnmount(() => {
/* ===== 日志 ===== */
.log-list {
font-family: 'SF Mono', 'Fira Code', monospace;
font-family: var(--df-font-mono);
font-size: 12px;
max-height: 280px;
overflow-y: auto;
@@ -1747,4 +1743,10 @@ onBeforeUnmount(() => {
}
.log-list { max-height: 200px; }
}
/* 容器闸:详情栏窄于 620px 时双栏堆叠(父级 .project-detail 已声明 container-type) */
@container (max-width: 620px) {
.detail-grid {
grid-template-columns: 1fr;
}
}
</style>
+9 -31
View File
@@ -4,16 +4,16 @@
<h1>{{ $t('projects.title') }}</h1>
<div class="header-actions">
<!-- 视图模式切换(默认 list 紧凑;card 宽松):切换持久化 localStorage -->
<div class="view-toggle" :title="$t('projects.viewModeTitle')">
<div class="df-seg" :title="$t('projects.viewModeTitle')">
<button
class="view-toggle-btn"
class="df-seg-btn"
:class="{ active: viewMode === 'list' }"
:title="$t('projects.viewList')"
:aria-label="$t('projects.viewList')"
@click="viewMode = 'list'"
>☰</button>
<button
class="view-toggle-btn"
class="df-seg-btn"
:class="{ active: viewMode === 'card' }"
:title="$t('projects.viewCard')"
:aria-label="$t('projects.viewCard')"
@@ -73,7 +73,7 @@
<!-- 回收站模态框 -->
<div v-if="showTrashModal" class="modal-overlay" @click.self="showTrashModal = false">
<div class="modal-box" style="width: 520px">
<div class="modal-box modal-box--md">
<h3 class="modal-title">{{ $t('projects.trashTitle') }}</h3>
<div v-if="store.deletedProjects.length === 0" class="empty-state" style="padding: 30px 0">{{ $t('projects.trashEmpty') }}</div>
<div v-else class="trash-list">
@@ -96,7 +96,7 @@
<!-- 归档箱模态框(归档功能 P2,V48;对齐回收站 Modal 模式:列表+恢复,无彻底删除——归档可逆) -->
<div v-if="showArchiveModal" class="modal-overlay" @click.self="showArchiveModal = false">
<div class="modal-box" style="width: 520px">
<div class="modal-box modal-box--md">
<h3 class="modal-title">{{ $t('projects.archiveBoxTitle') }}</h3>
<div v-if="store.archivedProjects.length === 0" class="empty-state" style="padding: 30px 0">{{ $t('projects.archiveBoxEmpty') }}</div>
<div v-else class="trash-list">
@@ -198,8 +198,8 @@
:total="store.projects.length"
/>
<!-- 空状态 -->
<div v-if="!store.loading && store.projects.length === 0" class="empty-state">
<!-- 空状态(加载失败时不显示,避免与上方错误条同屏矛盾) -->
<div v-if="!store.loading && !store.error && store.projects.length === 0" class="empty-state">
{{ $t('projects.empty') }}
</div>
@@ -541,29 +541,7 @@ async function runImport() {
grid-template-columns: 1fr;
}
/* ===== 视图模式切换 ===== */
.view-toggle {
display: inline-flex;
border: 0.5px solid var(--df-border);
border-radius: var(--df-radius-sm);
overflow: hidden;
}
.view-toggle-btn {
min-width: 30px;
padding: 6px 10px;
background: transparent;
border: none;
color: var(--df-text-dim);
font-size: 14px;
line-height: 1;
cursor: pointer;
transition: background 0.15s, color 0.15s;
}
.view-toggle-btn:hover { background: var(--df-bg-card); color: var(--df-text); }
.view-toggle-btn.active {
background: var(--df-accent-soft);
color: var(--df-accent);
}
/* ===== 视图模式切换:走 components.css .df-seg/.df-seg-btn(.active)(原 .view-toggle 自建样式已删) ===== */
/* 进度条族(.card-progress/.progress-*)已随卡片渲染迁至 ProjectCard.vue,本页残留定义已删(2026-10-18 整洁波) */
@@ -604,7 +582,7 @@ async function runImport() {
}
.trash-item:last-child { border-bottom: none; }
.trash-info { display: flex; flex-direction: column; gap: 2px; }
.trash-name { font-size: 14px; color: var(--df-text); font-weight: 500; }
.trash-name { font-size: var(--df-fs-item); color: var(--df-text); font-weight: 500; }
.trash-date { font-size: 11px; color: var(--df-text-dim); }
.trash-actions { display: flex; gap: 8px; }
+7 -1
View File
@@ -826,7 +826,7 @@ onBeforeUnmount(() => {
</script>
<style scoped>
.task-detail { padding: 16px 20px 20px; }
.task-detail { padding: 16px 20px 20px; container-type: inline-size; }
/* page-header / btn / back-link 等已提取到 global.css 全局 */
.header-left { display: flex; align-items: center; gap: 12px; min-width: 0; }
@@ -1149,6 +1149,12 @@ onBeforeUnmount(() => {
.detail-grid { grid-template-columns: 1fr; }
}
/* 双闸:@media 管窗口本身窄;@container 管「视口不窄但主区被 AI 面板挤压」场景
(AI 面板开合挤压主区时 .task-detail 实际宽度 < 620px,media query 感知不到)。 */
@container (max-width: 620px) {
.detail-grid { grid-template-columns: 1fr; }
}
/* 窄屏 header-actions 内推进按钮可能换行,允许并压缩间距(定高改自适应防溢出) */
@media (max-width: 720px) {
.task-detail-header { flex-wrap: wrap; height: auto; padding-top: 8px; padding-bottom: 8px; }
+30 -28
View File
@@ -29,6 +29,13 @@
<option v-for="p in store.projects" :key="p.id" :value="p.id">{{ p.name }}</option>
</select>
</div>
<!-- 列表/看板切换(持久化):紧跟项目选择器右侧,视觉上与项目维度配对 -->
<div class="df-seg">
<button class="df-seg-btn" :class="{ active: viewMode === 'list' }" @click="viewMode = 'list'">{{ $t('tasks.kanban.toggleList') }}</button>
<button class="df-seg-btn" :class="{ active: viewMode === 'kanban' }" @click="viewMode = 'kanban'">{{ $t('tasks.kanban.toggleKanban') }}</button>
<!-- 归档箱 P1:开关态独立于 list/kanban,开启后整页只渲染归档任务 -->
<button class="df-seg-btn" :class="{ active: archiveBoxMode }" @click="toggleArchiveBox">{{ $t('tasks.archiveBox') }}</button>
</div>
<div class="filter-group" v-if="viewMode === 'list'">
<span class="filter-label">{{ $t('tasks.filter.status') }}</span>
<div class="filter-chip-group">
@@ -56,14 +63,7 @@
含折叠父的子任务),看板模式各状态列恒全显仍取全量;原固定 store.tasks.length,
勾选状态芯片后计数不变与可见行数矛盾 -->
<span class="filter-count">{{ $t('common.pagination.total', { n: viewMode === 'list' ? batchScopeIds.length : store.tasks.length }) }}</span>
<!-- 列表/看板切换(持久化) -->
<div class="filter-group view-toggle">
<button class="seg-btn" :class="{ 'is-active': viewMode === 'list' }" @click="viewMode = 'list'">{{ $t('tasks.kanban.toggleList') }}</button>
<button class="seg-btn" :class="{ 'is-active': viewMode === 'kanban' }" @click="viewMode = 'kanban'">{{ $t('tasks.kanban.toggleKanban') }}</button>
<!-- 归档箱 P1:开关态独立于 list/kanban,开启后整页只渲染归档任务 -->
<button class="seg-btn" :class="{ 'is-active': archiveBoxMode }" @click="toggleArchiveBox">{{ $t('tasks.archiveBox') }}</button>
</div>
<!-- 列表模式全选(看板无批量栏,隐藏) -->
<!-- 列表/看板切换已上移至项目选择器最右侧(见上方),右端仅保留全选 -->
<label v-if="viewMode === 'list'" class="select-all" :title="$t('tasks.batch.selectHint')">
<input
type="checkbox"
@@ -77,9 +77,12 @@
<!-- 任务分组列表(列表模式) -->
<div class="task-groups" v-if="viewMode === 'list'">
<div v-if="loading" class="empty-state">{{ $t('common.loading') }}</div>
<div v-if="loading" class="empty-state page-loading">
<svg aria-hidden="true" class="page-spinner" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M21 12a9 9 0 11-6.219-8.56"/></svg>
{{ $t('common.loading') }}
</div>
<div v-else-if="filteredGroups.length === 0" class="empty-state">
<div class="empty-icon">{{ archiveBoxMode ? '📦' : '📋' }}</div>
<div class="empty-icon empty-icon--sm">{{ archiveBoxMode ? '📦' : '📋' }}</div>
<p v-if="archiveBoxMode">{{ $t('tasks.archiveBoxEmpty') }}</p>
<div>{{ $t('tasks.group.empty') }}</div>
<button class="btn btn-primary btn-sm" @click="openCreateModal()">{{ $t('tasks.create') }}</button>
@@ -351,7 +354,7 @@
<!-- 新建任务模态框(统一样式,去内联 style) -->
<div class="modal-overlay" v-if="showCreateModal" @click.self="showCreateModal = false">
<div class="modal-box">
<h3>{{ $t('tasks.modal.title') }}</h3>
<h3 class="modal-title">{{ $t('tasks.modal.title') }}</h3>
<div class="modal-field">
<label>{{ $t('tasks.modal.project') }}</label>
<!-- F-260805:选择父任务后 project 锁定为该父所属项目(disabled),需先选回「无」再切项目 -->
@@ -1046,20 +1049,7 @@ onUnmounted(() => {
font-size: 12px;
}
/* 列表/看板切换分段按钮 */
.view-toggle { margin-left: auto; display: flex; gap: 0; }
.seg-btn {
padding: 4px 12px;
font-size: 12px;
color: var(--df-text-dim);
background: var(--df-bg);
border: 0.5px solid var(--df-border);
cursor: pointer;
transition: background 0.15s, color 0.15s, border-color 0.15s;
}
.seg-btn:first-child { border-radius: var(--df-radius-sm) 0 0 var(--df-radius-sm); }
.seg-btn:last-child { border-radius: 0 var(--df-radius-sm) var(--df-radius-sm) 0; }
.seg-btn.is-active { color: var(--df-accent); background: var(--df-accent-bg); border-color: var(--df-accent); }
/* 列表/看板切换分段按钮走 components.css .df-seg/.df-seg-btn(.active) */
/* 全选 */
.select-all {
@@ -1076,11 +1066,12 @@ onUnmounted(() => {
/* 行勾选 checkbox(点击行跳详情,checkbox 已 @click.stop 拦截) */
.task-checkbox {
flex-shrink: 0;
align-self: flex-start; /* 两行内容时对齐标题行,而非垂直居中悬浮 */
margin: 3px 0 0;
width: 14px;
height: 14px;
accent-color: var(--df-accent);
cursor: pointer;
margin: 0;
}
/* ══ 任务看板 ══ */
@@ -1342,6 +1333,7 @@ onUnmounted(() => {
display: flex;
justify-content: space-between;
align-items: center;
gap: 10px;
padding: 8px 14px;
border-bottom: 0.5px solid var(--df-hover);
transition: background 0.1s;
@@ -1518,8 +1510,18 @@ onUnmounted(() => {
/* 快捷操作失败 toast 样式已统一到全局 global.css .df-toast 族(+--bottom 底部变体) */
/* 空态 */
/* empty-state 已提取到全局 global.css */
.empty-icon { font-size: 32px; opacity: 0.4; }
/* empty-state / empty-icon(--sm) 已提取到全局 global.css */
/* 页级首载:spinner+文字(对齐 AuditLog 首载形态,动画消费 global df-spin) */
.page-loading {
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
padding: 40px;
color: var(--df-text-dim);
}
.page-spinner { animation: df-spin 0.9s linear infinite; flex-shrink: 0; }
/* 模态框 overlay/box/modal-field/modal-actions 已提取到 global.css 全局 */
</style>