优化: 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:
+18
-1
@@ -5,6 +5,21 @@
|
||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>DevFlow</title>
|
||||
<style>
|
||||
/* 启动期加载占位:纯 CSS spinner,Vue mount 后随 #app 内容替换 */
|
||||
.boot-loading {
|
||||
position: fixed; inset: 0;
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
}
|
||||
.boot-spinner {
|
||||
width: 36px; height: 36px;
|
||||
border: 3px solid rgba(123, 111, 240, 0.2);
|
||||
border-top-color: #7b6ff0;
|
||||
border-radius: 50%;
|
||||
animation: boot-spin 0.8s linear infinite;
|
||||
}
|
||||
@keyframes boot-spin { to { transform: rotate(360deg); } }
|
||||
</style>
|
||||
</head>
|
||||
<body style="background:#0c0e1a">
|
||||
<script>
|
||||
@@ -15,7 +30,9 @@
|
||||
document.documentElement.setAttribute('data-theme', 'light');
|
||||
</script>
|
||||
<script type="module" src="/src/mocks/tauri-shim.ts"></script>
|
||||
<div id="app"></div>
|
||||
<div id="app">
|
||||
<div class="boot-loading"><div class="boot-spinner"></div></div>
|
||||
</div>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+6
-1
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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(() => {
|
||||
|
||||
@@ -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,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>
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 重复定义已删) */
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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 重复定义已删) */
|
||||
|
||||
@@ -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 直引无回退),此处仅留排版 */
|
||||
|
||||
@@ -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 右)。 */
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
@@ -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'
|
||||
}
|
||||
|
||||
@@ -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
@@ -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 {
|
||||
|
||||
@@ -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
@@ -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;
|
||||
|
||||
@@ -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
@@ -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>
|
||||
|
||||
@@ -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
@@ -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
@@ -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; }
|
||||
|
||||
|
||||
@@ -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
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user