优化: UI/UX遗留批(审计/AI命令层/前端组件,会话前基线收尾)

- AuditLog +298(筛选/详情/i18n)+ audit 后端 record/mod

- AI 命令层:generate_image +81 / fetch_url / fetch_search / skills / tool_registry / tools/file / provider / conversation

- 前端组件:AiChat/TopBar/ConversationSidebar/GitChanges/ApprovalPopup/Dashboard/ProjectDetail 等 30+ + composables + i18n

- 诊断文档: aichat历史会话实证诊断-2026-08-04 + project_soft_delete 测试
This commit is contained in:
lxy
2026-08-05 22:15:34 +08:00
parent 28de5d6143
commit e68d283651
49 changed files with 1257 additions and 138 deletions
+1 -1
View File
@@ -274,7 +274,7 @@ const projectsStore = useProjectStore()
// 后端 run_agentic_loop 对 override 兜底校验池内才用,前端预选无副作用。
// 第三批抽离: 默认 model 选中逻辑(modelOverride=enabledModels[0])随 enabledModels 迁移至
// TopBar.vue 的 activeConversationId watch;本组件仅保留编辑态取消。
// 滚动边沿/跟随意图重置(SW-260618-18/B-260618-24)随 MessageList 迁移,由子组件自管。
// 滚动边沿/跟随意图重置(SW-260618-18)随 MessageList 迁移,由子组件自管。
watch(() => store.state.activeConversationId, () => {
if (editingMsgId.value) cancelEdit()
})
+1 -1
View File
@@ -147,7 +147,7 @@ function onPageSizeChange(val: string) {
}
.page-btn:hover:not(:disabled) {
background: rgba(108, 99, 255, 0.06);
background: var(--df-accent-bg);
color: var(--df-text);
}
+2 -2
View File
@@ -123,7 +123,7 @@ function onDismiss() {
border: 0.5px solid var(--df-accent);
border-left: 3px solid var(--df-accent);
border-radius: var(--df-radius-md);
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4), 0 0 0 1px rgba(123, 111, 240, 0.2);
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4), 0 0 0 1px var(--df-accent-soft);
cursor: pointer;
transition: all 0.18s var(--df-ease);
user-select: none;
@@ -155,7 +155,7 @@ function onDismiss() {
min-width: 18px;
height: 18px;
padding: 0 5px;
border-radius: 9px;
border-radius: 8px;
background: var(--df-danger);
color: #fff;
font-size: 10px;
+1 -1
View File
@@ -118,6 +118,6 @@ onMounted(async () => {
.ci-item--success { border-color: var(--df-success); }
.ci-item--failure { border-color: var(--df-danger); }
.ci-item--pending { border-color: var(--df-warning); }
.ci-item-icon { font-size: 10px; font-weight: 700; }
.ci-item-icon { font-size: 10px; font-weight: 500; }
.ci-item:hover { opacity: 0.8; }
</style>
+1 -1
View File
@@ -143,7 +143,7 @@ watch(
.conflict-type-badge {
font-size: 10px;
padding: 2px 6px;
border-radius: 3px;
border-radius: 4px;
}
.badge--file { background: rgba(255, 193, 7, 0.15); color: #ffc107; }
.badge--semantic { background: rgba(220, 53, 69, 0.15); color: #dc3545; }
+1 -1
View File
@@ -549,7 +549,7 @@ body.ai-sidebar-resizing * {
font-size: 10px;
color: var(--df-text-dim);
border: 0.5px solid var(--df-border);
border-radius: 3px;
border-radius: 4px;
padding: 0 4px;
user-select: none;
}
+1 -1
View File
@@ -1001,7 +1001,7 @@ onBeforeUnmount(() => {
white-space: nowrap;
}
.ai-goal-dot { width: 6px; height: 6px; border-radius: 50%; background: var(--df-success); flex-shrink: 0; }
.ai-goal-check { color: var(--df-success); font-weight: bold; flex-shrink: 0; }
.ai-goal-check { color: var(--df-success); font-weight: 500; flex-shrink: 0; }
.ai-goal-remove { border: none; background: transparent; color: var(--df-text-dim); cursor: pointer; padding: 0 2px; font-size: 13px; line-height: 1; flex-shrink: 0; }
.ai-goal-remove:hover {
color: var(--df-danger, #e5484d);
@@ -130,7 +130,7 @@ const displayProjects = computed(() =>
letter-spacing: 0.3px;
text-transform: uppercase;
}
.chip-coding { background: rgba(123,111,240,0.12); color: var(--df-accent); }
.chip-coding { background: var(--df-accent-soft); color: var(--df-accent); }
.chip-testing { background: rgba(240,199,94,0.12); color: var(--df-warning); }
.chip-release { background: rgba(61,219,160,0.12); color: var(--df-success); }
.chip-planning { background: rgba(94,175,240,0.12); color: var(--df-info); }
+135 -16
View File
@@ -180,9 +180,16 @@
</template>
<!-- 编辑态 -->
<template v-else>
<div v-if="candidateIdeas.length > 0" class="related-edit-list">
<div class="related-search" v-if="candidateIdeas.length > 0">
<input
v-model="relatedSearch"
class="related-search-input"
:placeholder="$t('ideas.relatedSearchPlaceholder')"
/>
</div>
<div v-if="filteredCandidates.length > 0" class="related-edit-list">
<label
v-for="idea in candidateIdeas"
v-for="idea in filteredCandidates"
:key="idea.id"
class="related-edit-item"
>
@@ -202,10 +209,15 @@
</template>
</div>
<!-- 传统评分条(score-bar) -->
<!-- 传统评分条(score-bar,从旧评估体系保留,默认折叠以降低信息密度) -->
<div class="detail-section">
<h3>{{ $t('ideas.multiScoreTitle') }}</h3>
<div class="score-bar-chart" v-if="parseScores(idea.scores).length > 0">
<h3>
<button class="section-toggle-btn" @click="scoreBarExpanded = !scoreBarExpanded">
{{ $t('ideas.multiScoreTitle') }}
<span class="section-toggle-arrow" :class="{ 'is-open': scoreBarExpanded }"></span>
</button>
</h3>
<div v-show="scoreBarExpanded" class="score-bar-chart" v-if="parseScores(idea.scores).length > 0">
<div class="score-bar-row" v-for="dim in parseScores(idea.scores)" :key="dim.name">
<span class="score-bar-label">{{ dim.name }}</span>
<div class="score-bar-track">
@@ -220,10 +232,30 @@
<!-- 标签 -->
<div class="detail-section">
<h3>{{ $t('ideas.tagsTitle') }}</h3>
<div class="tag-list" v-if="parseTags(idea.tags).length > 0">
<span class="tag" v-for="tag in parseTags(idea.tags)" :key="tag">{{ tag }}</span>
</div>
<div v-else class="eval-report eval-report--muted">{{ $t('ideas.noTags') }}</div>
<template v-if="tagsEditing">
<input
v-model="tagsEditInput"
class="detail-tags-edit-input"
:placeholder="$t('ideas.tagsPlaceholder')"
@keyup.enter="saveTagsEdit"
/>
<div class="tags-edit-actions">
<button class="btn btn-primary btn-sm" :disabled="savingTags" @click="saveTagsEdit">
{{ savingTags ? $t('common.loading') : $t('common.save') }}
</button>
<button class="btn btn-ghost btn-sm" :disabled="savingTags" @click="cancelTagsEdit">{{ $t('common.cancel') }}</button>
</div>
</template>
<template v-else>
<div class="tag-list" v-if="parseTags(idea.tags).length > 0">
<span class="tag" v-for="tag in parseTags(idea.tags)" :key="tag">{{ tag }}</span>
<button class="btn btn-ghost btn-sm" @click="startTagsEdit">{{ $t('ideas.editTags') }}</button>
</div>
<div v-else class="eval-report eval-report--muted">
{{ $t('ideas.noTags') }}
<button class="btn btn-ghost btn-sm" @click="startTagsEdit">{{ $t('ideas.editTags') }}</button>
</div>
</template>
</div>
<!-- 状态管理:P1- select badge 点击(当前态显色 badge,点击展开下拉选项) -->
@@ -292,6 +324,7 @@ const emit = defineEmits<{
(e: 'status-change', value: IdeaStatus): void
(e: 'update-desc', value: string): void
(e: 'update-related', ids: string[]): void
(e: 'update-tags', tags: string): void
}>()
const { t } = useI18n()
@@ -336,6 +369,8 @@ const descExpanded = ref(false)
// P0- (/):,,
const debateExpanded = ref(false)
// score-bar (,)
const scoreBarExpanded = ref(false)
function measureDesc() {
const el = descRef.value
@@ -348,6 +383,7 @@ function measureDesc() {
watch(() => props.idea.id, () => {
descExpanded.value = false
debateExpanded.value = false
scoreBarExpanded.value = false
void nextTick(measureDesc)
})
watch(() => props.idea.description, () => void nextTick(measureDesc))
@@ -386,6 +422,15 @@ const vClickOutside: Directive<HTMLElement, () => void> = {
}
// ===== (related_ids JSON )=====
//
const relatedSearch = ref('')
const filteredCandidates = computed(() => {
const q = relatedSearch.value.trim().toLowerCase()
if (!q) return candidateIdeas.value
return candidateIdeas.value.filter(
i => (i.title ?? '').toLowerCase().includes(q) || (i.description ?? '').toLowerCase().includes(q),
)
})
// props.idea.related_ids(JSON ,null// [])
const relatedIds = computed<string[]>(() => {
const raw = props.idea.related_ids
@@ -426,6 +471,7 @@ function confirmRelate() {
function cancelRelate() {
relatedEditing.value = false
relatedSearch.value = ''
}
function toggleSelect(id: string) {
@@ -437,6 +483,33 @@ function toggleSelect(id: string) {
}
}
// ===== (show + edit )=====
const tagsEditing = ref(false)
const tagsEditInput = ref('')
const savingTags = ref(false)
function startTagsEdit() {
tagsEditInput.value = parseTags(props.idea.tags).join(', ')
tagsEditing.value = true
}
async function saveTagsEdit() {
if (savingTags.value) return
savingTags.value = true
try {
const tags = tagsEditInput.value.split(',').map(t => t.trim()).filter(Boolean)
emit('update-tags', JSON.stringify(tags))
tagsEditing.value = false
await nextTick()
} finally {
savingTags.value = false
}
}
function cancelTagsEdit() {
tagsEditing.value = false
}
// status i18n key;退
function statusLabelKey(status: IdeaStatus): string {
return props.statusOptions.find(s => s.value === status)?.labelKey ?? status
@@ -639,7 +712,7 @@ watch(() => props.idea.id, loadHistory)
color: var(--df-text-secondary);
line-height: 1.6;
padding: 14px 16px;
background: rgba(108, 99, 255, 0.06);
background: var(--df-accent-bg);
border-left: 3px solid var(--df-accent);
border-radius: 0 8px 8px 0;
}
@@ -884,7 +957,7 @@ watch(() => props.idea.id, loadHistory)
font-weight: 500;
padding: 2px 8px;
border-radius: var(--df-radius-xs);
background: rgba(108, 99, 255, 0.12);
background: var(--df-accent-soft);
color: var(--df-accent);
}
.latest-tag {
@@ -937,7 +1010,7 @@ watch(() => props.idea.id, loadHistory)
.tag {
font-size: 12px; padding: 4px 10px;
border-radius: var(--df-radius-xs);
background: rgba(108, 99, 255, 0.1);
background: var(--df-accent-bg);
color: var(--df-accent);
}
@@ -946,13 +1019,13 @@ watch(() => props.idea.id, loadHistory)
.related-chip {
font-size: 12px; padding: 4px 10px;
border-radius: var(--df-radius-xs);
background: rgba(108, 99, 255, 0.1);
background: var(--df-accent-bg);
color: var(--df-accent);
text-decoration: none;
cursor: pointer;
transition: background 0.15s;
}
.related-chip:hover { background: rgba(108, 99, 255, 0.2); }
.related-chip:hover { background: var(--df-accent-soft); }
.related-chip-missing { cursor: default; opacity: 0.6; }
.related-edit-list {
@@ -971,6 +1044,52 @@ watch(() => props.idea.id, loadHistory)
}
.related-edit-item input { cursor: pointer; }
.related-edit-actions { display: flex; gap: 8px; }
/* 关联编辑搜索框 */
.related-search { margin-bottom: 8px; }
.related-search-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;
outline: none;
}
.related-search-input:focus {
border-color: var(--df-accent);
}
/* ===== 标签编辑 ===== */
.detail-tags-edit-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;
outline: none;
}
.detail-tags-edit-input:focus {
border-color: var(--df-accent);
}
.tags-edit-actions {
display: flex;
gap: 8px;
margin-top: 8px;
}
.tag-list .btn-ghost {
font-size: 11px;
padding: 2px 8px;
}
.eval-report .btn-ghost {
font-size: 11px;
padding: 2px 8px;
margin-left: 6px;
}
/* ===== 按钮已提取到 global.css 全局(.btn/.btn-primary/.btn-ghost/.btn-sm) ===== */
/* IdeaDetail 特有按钮定位 */
@@ -1057,8 +1176,8 @@ watch(() => props.idea.id, loadHistory)
font-size: 13px;
transition: background 0.15s;
}
.status-menu-item:hover { background: rgba(108, 99, 255, 0.08); }
.status-menu-item.active { background: rgba(108, 99, 255, 0.12); font-weight: 500; }
.status-menu-item:hover { background: var(--df-accent-bg); }
.status-menu-item.active { background: var(--df-accent-soft); font-weight: 500; }
/* menu 内 badge 取消 emoji 之外的全局 hover 等副作用,仅作色标 */
.status-menu-item .status-tag { cursor: pointer; }
+1 -1
View File
@@ -368,7 +368,7 @@ watch(renderedContent, () => {
/* ===== 知识内容 Markdown 渲染(B-260615-25,基础样式收敛至全局 ai-md.css)===== */
.detail-content.ai-md { white-space: normal; }
.detail-tags { display: flex; gap: 6px; flex-wrap: wrap; }
.tag { font-size: 11px; padding: 2px 8px; border-radius: var(--df-radius-xs); background: rgba(108,99,255,0.1); color: var(--df-accent); }
.tag { font-size: 11px; padding: 2px 8px; border-radius: var(--df-radius-xs); background: var(--df-accent-bg); color: var(--df-accent); }
.muted { color: var(--df-text-dim); font-size: 12px; }
/* 状态徽章 / 置信度 */
+3 -3
View File
@@ -68,8 +68,8 @@
<!-- 7b:收缩/展开切换按钮(最底部,快捷键 Ctrl+B 同效) -->
<button class="nav-link collapse-toggle-btn" @click="toggleCollapsed" :title="collapsed ? $t('nav.expand') : $t('nav.collapse')">
<span class="nav-link-icon">
<svg v-if="!collapsed" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><polyline points="15 18 9 12 15 6"/></svg>
<svg v-else width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><polyline points="9 18 15 12 9 6"/></svg>
<svg v-if="!collapsed" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="15 18 9 12 15 6"/></svg>
<svg v-else width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="9 18 15 12 9 6"/></svg>
</span>
<span v-if="!collapsed" class="nav-link-text">{{ $t('nav.collapse') }}</span>
<span v-if="!collapsed" class="ai-toggle-shortcut">Ctrl+B</span>
@@ -150,7 +150,7 @@ const secondaryNav = [
{ path: '/knowledge', label: 'nav.knowledge',
svg: '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M4 19.5A2.5 2.5 0 016.5 17H20"/><path d="M6.5 2H20v20H6.5A2.5 2.5 0 014 19.5v-15A2.5 2.5 0 016.5 2z"/></svg>' },
{ path: '/audit', label: 'nav.audit',
svg: '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M9 11l3 3L22 4"/><path d="M21 12v7a2 2 0 01-2 2H5a2 2 0 01-2-2V5a2 2 0 012-2h11"/></svg>' },
svg: '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg>' },
]
</script>
+110 -1
View File
@@ -17,6 +17,22 @@
<div>{{ $t('dependencyGraph.empty') }}</div>
</div>
<!-- 删除依赖确认弹层 -->
<ConfirmDialog :visible="confirmState.visible" :msg="confirmState.msg" :danger-label="confirmState.dangerLabel" @result="answerConfirm" />
<!-- 边删除按钮(HTML 遮罩层):edge hover 时定位到边中点显示,leave 时隐藏
HTML 而非 X6 port 方案,因为 graph.fromJSON 重建所有 edge 会清掉动态加的 port -->
<button
v-if="deleteDepBtnVisible"
class="dep-edge-delete-btn"
:style="deleteDepBtnStyle"
@click="onDeleteDepBtnClick"
type="button"
:title="$t('fileExplorer.deleteCurrentModule')"
>
<svg 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">
@@ -60,12 +76,15 @@
*/
import { onMounted, onBeforeUnmount, ref, watch, markRaw } from 'vue'
import { useRouter } from 'vue-router'
import { useI18n } from 'vue-i18n'
import { Graph, Selection, Snapline, History, Scroller, MiniMap } from '@antv/x6'
import dagre from 'dagre'
import '@antv/x6-vue-shape'
import { Message } from '@arco-design/web-vue'
import { moduleApi, type ProjectModuleRecord, type ModuleDependencyRecord } from '@/api/module'
import { useConfirm } from '@/composables/useConfirm'
import ModuleNode from './ModuleNode.vue'
import ConfirmDialog from '@/components/ConfirmDialog.vue'
const props = defineProps<{
projectId: string
@@ -81,8 +100,15 @@ const containerRef = ref<HTMLElement | null>(null)
const modules = ref<ProjectModuleRecord[]>([])
const dependencies = ref<ModuleDependencyRecord[]>([])
const loading = ref(true)
const { t } = useI18n()
const { confirmDialog, confirmState, answerConfirm } = useConfirm()
let graph: Graph | null = null
// (HTML )
const deleteDepBtnVisible = ref(false)
const deleteDepBtnStyle = ref<{ left: string; top: string }>({ left: '0px', top: '0px' })
let deleteDepTarget: ModuleDependencyRecord | null = null
//
const showAddDep = ref(false)
const depFrom = ref('')
@@ -165,8 +191,9 @@ function renderGraph() {
}
})
// (module_dependencies )
// (module_dependencies );edge id dep.id, id /
const edges = dependencies.value.map(d => ({
id: d.id,
source: d.from_module_id,
target: d.to_module_id,
labels: d.label ? [{ text: d.label }] : [{ text: d.dep_type }],
@@ -206,6 +233,7 @@ function buildGraph() {
})
renderGraph()
bindEdgeDeleteButtons()
}
function fitContent() {
@@ -254,6 +282,65 @@ async function exportPNG() {
}
}
/** 删除依赖(二次确认 + 重绘)。 */
async function confirmRemoveDep(dep: ModuleDependencyRecord) {
const fromName = modules.value.find(m => m.id === dep.from_module_id)?.name ?? dep.from_module_id
const toName = modules.value.find(m => m.id === dep.to_module_id)?.name ?? dep.to_module_id
const ok = await confirmDialog(`确定删除「${fromName}${toName}」的依赖吗?此操作不可撤销。`, t('common.delete'))
if (!ok) return
try {
await moduleApi.removeModuleDependency(dep.id)
Message.info('依赖已删除')
await loadModules()
renderGraph()
bindEdgeDeleteButtons()
} catch (e) {
console.error('[DependencyGraph] 删除依赖失败:', e)
Message.error('删除依赖失败')
}
}
/** 点击遮罩层上的删除按钮触发。 */
function onDeleteDepBtnClick() {
const dep = deleteDepTarget
if (dep) void confirmRemoveDep(dep)
hideEdgeDeleteBtn()
}
/** edge hover 时显示删除按钮(定位到边中点屏幕坐标)。 */
function showEdgeDeleteBtn(edge: any) {
const depId = String(edge.id)
const dep = dependencies.value.find(d => d.id === depId)
if (!dep || !containerRef.value || !graph) return
deleteDepTarget = dep
// (view ) :transform.scale + translate
const bbox = edge.getBBox()
const mx = bbox.x + bbox.width / 2
const my = bbox.y + bbox.height / 2
const ts = graph.translate()
const sc = graph.scale()
const viewX = mx * sc.sx + ts.tx
const viewY = my * sc.sy + ts.ty
deleteDepBtnStyle.value = { left: `${viewX - 8}px`, top: `${viewY - 8}px` }
deleteDepBtnVisible.value = true
}
/** edge leave 时隐藏删除按钮。 */
function hideEdgeDeleteBtn() {
deleteDepBtnVisible.value = false
deleteDepTarget = null
}
/** 给所有 edge 绑 hover 显示删除按钮的监听。 */
function bindEdgeDeleteButtons() {
if (!graph) return
try { graph.off('edge:mouseenter', onEdgeEnter); graph.off('edge:mouseleave', onEdgeLeave) } catch { /* ignore */ }
function onEdgeEnter(e: any) { showEdgeDeleteBtn(e.edge) }
function onEdgeLeave() { hideEdgeDeleteBtn() }
graph.on('edge:mouseenter', onEdgeEnter)
graph.on('edge:mouseleave', onEdgeLeave)
}
/** 依赖类型 → 边颜色 */
function depTypeColor(depType: string): string {
switch (depType) {
@@ -378,4 +465,26 @@ onBeforeUnmount(() => {
.dep-graph__modal-actions {
display: flex; justify-content: flex-end; gap: 8px; margin-top: 16px;
}
/* 边删除按钮(HTML 遮罩层,绝对定位在 .dep-graph__canvas 上) */
.dep-edge-delete-btn {
position: absolute;
width: 16px;
height: 16px;
border: none;
border-radius: 50%;
background: var(--df-danger);
color: #fff;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
z-index: 200;
box-shadow: 0 2px 6px rgba(0,0,0,0.4);
transition: background 0.15s;
pointer-events: auto;
}
.dep-edge-delete-btn:hover {
background: #c24040;
}
</style>
+5 -5
View File
@@ -559,7 +559,7 @@ watch(() => props.moduleId, loadStatus, { immediate: true })
color: var(--df-text-dim);
cursor: pointer;
padding: 2px 6px;
border-radius: 3px;
border-radius: 4px;
display: flex;
align-items: center;
transition: all 0.15s;
@@ -725,7 +725,7 @@ watch(() => props.moduleId, loadStatus, { immediate: true })
.gc-commit-diff-toggle {
background: none; border: 0.5px solid var(--df-border);
color: var(--df-text-dim); cursor: pointer; font-size: 11px;
padding: 2px 8px; border-radius: 3px;
padding: 2px 8px; border-radius: 4px;
}
.gc-commit-diff-toggle:hover { color: var(--df-text); border-color: var(--df-accent); }
.gc-commit-diff .gc-diff-content { flex: 1; min-height: 0; }
@@ -738,7 +738,7 @@ watch(() => props.moduleId, loadStatus, { immediate: true })
transform: translateY(12px); opacity: 0;
}
.gc-file-status { width: 20px; text-align: center; font-size: 11px; font-weight: 700; flex-shrink: 0; }
.gc-file-status { width: 20px; text-align: center; font-size: 11px; font-weight: 500; flex-shrink: 0; }
.gc-st-modified { color: #f0a020; }
.gc-st-added { color: #4caf50; }
.gc-st-deleted { color: #e05050; }
@@ -806,8 +806,8 @@ watch(() => props.moduleId, loadStatus, { immediate: true })
.gc-branch-tag {
font-size: 9px;
padding: 1px 5px;
border-radius: 3px;
background: rgba(108,99,255,0.15);
border-radius: 4px;
background: var(--df-accent-soft);
color: var(--df-accent);
}
.gc-branch-picker-hint {
+1 -1
View File
@@ -101,7 +101,7 @@ const stackList = computed(() => parseJsonArray(props.data?.stack).slice(0, 3))
.module-node__tag {
font-size: 9px;
padding: 1px 6px;
border-radius: 3px;
border-radius: 4px;
background: rgba(83, 120, 232, 0.15);
color: #7c9aff;
}
+1 -1
View File
@@ -133,7 +133,7 @@ watch(() => props.view, () => nextTick(measureDesc))
border-radius: var(--df-radius-sm);
}
.stage-design { background: rgba(100,181,246,0.15); color: var(--df-info); }
.stage-coding { background: rgba(108,99,255,0.15); color: var(--df-accent); }
.stage-coding { background: var(--df-accent-soft); color: var(--df-accent); }
.stage-testing { background: rgba(255,217,61,0.15); color: var(--df-warning); }
.stage-release { background: rgba(100,255,218,0.15); color: var(--df-success); }
+1 -1
View File
@@ -251,7 +251,7 @@ onMounted(loadConnections)
font-weight: 500;
}
.type-mysql { background: rgba(100,181,246,0.15); color: var(--df-info); }
.type-ssh { background: rgba(108,99,255,0.15); color: var(--df-accent); }
.type-ssh { background: var(--df-accent-soft); color: var(--df-accent); }
.type-redis { background: rgba(255,107,107,0.15); color: var(--df-danger); }
.type-mongo { background: rgba(100,255,218,0.15); color: var(--df-success); }
@@ -229,7 +229,7 @@ function cancelEdit() {
.wf-dag-node-name { font-weight: 500; white-space: nowrap; }
.wf-dag-node-type {
font-size: 10px; color: var(--df-text-dim, #888);
background: var(--df-bg-card, #f0f0f0); padding: 0 4px; border-radius: 3px;
background: var(--df-bg-card, #f0f0f0); padding: 0 4px; border-radius: 4px;
}
.wf-dag-node-status { font-size: 11px; }
.wf-dag-node-status-dot {
@@ -270,7 +270,7 @@ function cancelEdit() {
}
.wf-dag-cond-label--uncond { background: transparent; color: var(--df-text-dim, #aaa); }
.wf-dag-cond-label--editable {
cursor: pointer; font-size: 11px; padding: 1px 6px; border-radius: 3px;
cursor: pointer; font-size: 11px; padding: 1px 6px; border-radius: 4px;
transition: background 0.15s;
}
.wf-dag-cond-label--editable:hover { background: rgba(74,144,226,0.18); }
@@ -284,7 +284,7 @@ function cancelEdit() {
.wf-dag-edge-arrow { color: var(--df-text-primary); white-space: nowrap; }
.wf-dag-edge-input {
flex: 1; min-width: 0; font-family: var(--df-font-mono); font-size: 11px;
padding: 2px 6px; border: 0.5px solid var(--df-border); border-radius: 3px;
padding: 2px 6px; border: 0.5px solid var(--df-border); border-radius: 4px;
background: var(--df-bg); color: var(--df-text); outline: none;
}
.wf-dag-edge-input:focus { border-color: var(--df-accent); }