style: 全局 UI 主题皮肤重构与样式模块化

引入 Element Plus 主题定制和主题皮肤 composable,将全局
样式拆分为组件级独立 CSS 文件(侧边栏、顶栏、工作台等),
统一色彩变量和间距规范,重构所有视图和组件样式以适配新
主题系统,优化图表和知识图谱组件视觉表现,提取审计和差
旅报销相关子组件。
This commit is contained in:
caoxiaozhu
2026-05-27 09:17:57 +08:00
parent df49103f23
commit 2dcc72102d
112 changed files with 10983 additions and 8996 deletions

View File

@@ -357,7 +357,7 @@ function observeResize() {
}
.graph-eyebrow {
color: #0f766e;
color: var(--theme-primary-active);
font-size: 12px;
font-weight: 850;
}
@@ -515,7 +515,7 @@ function observeResize() {
}
.inspector-title span {
color: #0f766e;
color: var(--theme-primary-active);
font-size: 12px;
font-weight: 850;
}

View File

@@ -2,8 +2,8 @@
<div class="node-detail-panel">
<section class="detail-section">
<div class="detail-section-head">
<strong>节点说明</strong>
<span>{{ safeNode.type || '实体' }}</span>
<strong>节点摘要</strong>
<span>{{ safeNode.type || '未知类型' }}</span>
</div>
<div v-if="descriptionItems.length" class="description-list">
<p v-for="(description, index) in descriptionItems" :key="index">
@@ -11,7 +11,7 @@
</p>
</div>
<div v-else class="detail-empty compact">
当前节点暂无 LightRAG 描述完成新的归集后会从图谱属性中补充
暂无描述LightRAG 还没有为该节点生成可展示的摘要
</div>
</section>
@@ -22,11 +22,11 @@
</div>
<dl class="property-grid">
<div>
<dt>类型</dt>
<dd>{{ safeNode.type || '实体' }}</dd>
<dt>节点类型</dt>
<dd>{{ safeNode.type || '未知类型' }}</dd>
</div>
<div>
<dt>系数</dt>
<dt>联度</dt>
<dd>{{ safeNode.degree || 0 }}</dd>
</div>
<div>
@@ -53,7 +53,7 @@
<section class="detail-section relation-section">
<div class="detail-section-head">
<strong>关系语境</strong>
<strong>联关</strong>
<span>{{ relationRows.length }} </span>
</div>
<div v-if="relationRows.length" class="relation-detail-list">
@@ -72,7 +72,7 @@
</div>
</button>
</div>
<div v-else class="detail-empty compact">暂无关联关系</div>
<div v-else class="detail-empty compact">暂无关联关系</div>
</section>
</div>
</template>
@@ -139,7 +139,7 @@ const relationRows = computed(() => {
raw: relation,
peerName,
type: String(relation.type || '关联').trim(),
directionLabel: isOutgoing ? '指向' : '来',
directionLabel: isOutgoing ? '指向' : '来',
description: String(relation.description || '').trim(),
keywords: dedupeTextItems(relation.keywords).slice(0, 6)
}
@@ -224,7 +224,7 @@ function formatPropertyKey(key) {
.description-list p {
margin: 0;
padding: 9px;
border: 1px solid #dbeafe;
border: 1px solid color-mix(in srgb, var(--theme-primary) 18%, #ffffff);
border-radius: 8px;
background: #fff;
color: #1e293b;
@@ -288,8 +288,8 @@ function formatPropertyKey(key) {
align-items: center;
padding: 0 7px;
border-radius: 999px;
background: #e0f2fe;
color: #075985;
background: var(--theme-primary-light-9);
color: var(--theme-primary-active);
font-size: 11px;
font-weight: 800;
}
@@ -317,7 +317,7 @@ function formatPropertyKey(key) {
grid-template-columns: auto minmax(0, 1fr) auto;
gap: 7px;
padding: 9px;
border: 1px solid #dbeafe;
border: 1px solid color-mix(in srgb, var(--theme-primary) 18%, #ffffff);
border-radius: 8px;
background: #fff;
cursor: pointer;
@@ -326,8 +326,8 @@ function formatPropertyKey(key) {
}
.relation-detail-list button:hover {
border-color: #60a5fa;
background: #eff6ff;
border-color: color-mix(in srgb, var(--theme-primary) 38%, #ffffff);
background: var(--theme-primary-light-9);
transform: translateY(-1px);
}
@@ -342,7 +342,7 @@ function formatPropertyKey(key) {
}
.relation-detail-list strong {
color: #1d4ed8;
color: var(--theme-primary-active);
}
.relation-detail-list p,
@@ -359,8 +359,8 @@ function formatPropertyKey(key) {
}
.keyword-row span {
background: #ecfdf5;
color: #047857;
background: var(--theme-primary-light-9);
color: var(--theme-primary-active);
}
.detail-empty {

View File

@@ -107,7 +107,7 @@ function formatElapsed(startedAt, finishedAt) {
}
.info-title span {
color: #0f766e;
color: var(--theme-primary-active);
font-size: 12px;
font-weight: 850;
}
@@ -129,8 +129,8 @@ function formatElapsed(startedAt, finishedAt) {
}
.info-title > strong.success {
background: #dcfce7;
color: #166534;
background: var(--success-soft);
color: var(--success-active);
}
.info-title > strong.warning {

View File

@@ -57,7 +57,7 @@ const model = computed(() => buildKnowledgeIngestLogModel(props.run))
}
.eyebrow {
color: #0f766e;
color: var(--theme-primary-active);
font-size: 12px;
font-weight: 800;
}
@@ -113,7 +113,7 @@ const model = computed(() => buildKnowledgeIngestLogModel(props.run))
display: block;
height: 100%;
border-radius: inherit;
background: linear-gradient(90deg, #0f766e, #2563eb);
background: linear-gradient(90deg, var(--theme-primary-active), var(--theme-secondary));
transition: width 0.24s ease;
}

View File

@@ -1,10 +1,12 @@
import { computed, ref, watch } from 'vue'
import { useThemeColors } from '../../composables/useThemeColors.js'
const MAX_VISIBLE_NODES = 72
const MAX_VISIBLE_EDGES = 180
const MAX_RELATION_PREVIEW = 40
const NODE_TONES = {
const BASE_NODE_TONES = {
hub: {
fill: '#2563eb',
stroke: '#dbeafe',
@@ -12,10 +14,10 @@ const NODE_TONES = {
shadow: 'rgba(37, 99, 235, 0.20)'
},
strong: {
fill: '#0f766e',
stroke: '#ccfbf1',
halo: '#5eead4',
shadow: 'rgba(15, 118, 110, 0.18)'
fill: '#3a7ca5',
stroke: '#eaf4fa',
halo: '#d4e8f3',
shadow: 'rgba(58, 124, 165, 0.18)'
},
accent: {
fill: '#d97706',
@@ -40,8 +42,18 @@ const NODE_TONES = {
export function useKnowledgeIngestGraph(props) {
const graphQuery = ref('')
const activeNodeId = ref('')
const themeColors = useThemeColors()
const allRelations = computed(() => normalizeRelations(props.graph?.relations))
const nodeTones = computed(() => ({
...BASE_NODE_TONES,
strong: {
fill: themeColors.value.chartPrimary,
stroke: themeColors.value.primarySoft,
halo: themeColors.value.primarySoft,
shadow: toRgba(themeColors.value.chartPrimary, 0.18)
}
}))
const rankedNodes = computed(() => buildRankedNodes(props.graph, allRelations.value))
const visibleNodes = computed(() => {
const query = graphQuery.value.toLowerCase()
@@ -83,7 +95,7 @@ export function useKnowledgeIngestGraph(props) {
selectedNodeRelations.value.filter((relation) => relation.source === selectedNode.value?.name)
)
const graphData = computed(() => ({
nodes: visibleNodes.value.map((node) => toG6Node(node)),
nodes: visibleNodes.value.map((node) => toG6Node(node, nodeTones.value)),
edges: visibleRelations.value
.map((relation, index) => toG6Edge(relation, index, nodeIdByName.value))
.filter(Boolean)
@@ -179,9 +191,9 @@ function buildRankedNodes(graph, relations) {
})
}
function toG6Node(node) {
function toG6Node(node, nodeTones) {
const tone = resolveNodeTone(node)
const palette = NODE_TONES[tone]
const palette = nodeTones[tone] || nodeTones.normal || BASE_NODE_TONES.normal
const size = clamp(34 + Math.sqrt(Math.max(node.degree, 1)) * 13, 38, node.rank === 1 ? 82 : 70)
const opacity = node.matchesQuery ? 1 : 0.24
@@ -241,6 +253,20 @@ function toG6Node(node) {
}
}
function toRgba(hexColor, alpha) {
const normalized = String(hexColor || '').trim()
const matched = normalized.match(/^#([0-9a-f]{6})$/i)
if (!matched) {
return `rgba(58, 124, 165, ${alpha})`
}
const value = matched[1]
const red = parseInt(value.slice(0, 2), 16)
const middle = parseInt(value.slice(2, 4), 16)
const blue = parseInt(value.slice(4, 6), 16)
return `rgba(${red}, ${middle}, ${blue}, ${alpha})`
}
function toG6Edge(relation, index, nodeIdByName) {
const sourceId = nodeIdByName.get(relation.source)
const targetId = nodeIdByName.get(relation.target)