refactor: 重构文件树组件,优化AgentPanel和SkillsManager组件的文件展示逻辑
This commit is contained in:
parent
bfd22c760f
commit
2020438152
@ -55,44 +55,29 @@
|
||||
<div v-if="activeTab === 'files'" class="files-display">
|
||||
<div v-if="!fileCount" class="empty">暂无文件</div>
|
||||
<div v-else class="file-tree-container">
|
||||
<a-tree
|
||||
<FileTreeComponent
|
||||
v-model:expandedKeys="expandedKeys"
|
||||
:tree-data="fileTreeData"
|
||||
:show-icon="true"
|
||||
block-node
|
||||
:show-line="false"
|
||||
@select="onFileSelect"
|
||||
>
|
||||
<template #icon="{ data, expanded }">
|
||||
<template v-if="data.isLeaf">
|
||||
<component
|
||||
:is="getFileIcon(data.key)"
|
||||
:style="{ color: getFileIconColor(data.key), fontSize: '16px' }"
|
||||
/>
|
||||
</template>
|
||||
<template v-else>
|
||||
<FolderOpen v-if="expanded" :size="18" class="folder-icon open" />
|
||||
<Folder v-else :size="18" class="folder-icon" />
|
||||
</template>
|
||||
</template>
|
||||
<template #title="{ data }">
|
||||
<div class="tree-node-wrapper" @click="toggleFolder(data)">
|
||||
<div class="tree-node-name" :title="data.title">
|
||||
<span class="name-start">{{ data.nameStart || data.title }}</span>
|
||||
<span class="name-end" v-if="data.nameEnd">{{ data.nameEnd }}</span>
|
||||
</div>
|
||||
<div v-if="data.isLeaf" class="node-actions" @click.stop>
|
||||
<button
|
||||
class="tree-action-btn tree-download-btn"
|
||||
@click.stop="downloadFile(data.fileData)"
|
||||
title="下载文件"
|
||||
>
|
||||
<Download :size="14" />
|
||||
</button>
|
||||
</div>
|
||||
<template #title="{ node }">
|
||||
<div class="tree-node-name" :title="node.title">
|
||||
<span class="name-start">{{ node.nameStart || node.title }}</span>
|
||||
<span class="name-end" v-if="node.nameEnd">{{ node.nameEnd }}</span>
|
||||
</div>
|
||||
</template>
|
||||
</a-tree>
|
||||
<template #actions="{ node }">
|
||||
<div v-if="node.isLeaf" class="node-actions-container">
|
||||
<button
|
||||
class="tree-action-btn tree-download-btn"
|
||||
@click.stop="downloadFile(node.fileData)"
|
||||
title="下载文件"
|
||||
>
|
||||
<Download :size="14" />
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
</FileTreeComponent>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -163,6 +148,7 @@ import { MdPreview } from 'md-editor-v3'
|
||||
import 'md-editor-v3/lib/preview.css'
|
||||
import { useThemeStore } from '@/stores/theme'
|
||||
import { getFileIcon, getFileIconColor, formatFileSize } from '@/utils/file_utils'
|
||||
import FileTreeComponent from '@/components/FileTreeComponent.vue'
|
||||
|
||||
const props = defineProps({
|
||||
agentState: {
|
||||
@ -384,17 +370,6 @@ const truncateFilename = (name) => {
|
||||
|
||||
const fileTreeData = computed(() => buildTreeData(normalizedFiles.value))
|
||||
|
||||
const toggleFolder = (data) => {
|
||||
if (data.isLeaf) return
|
||||
const key = data.key
|
||||
const index = expandedKeys.value.indexOf(key)
|
||||
if (index > -1) {
|
||||
expandedKeys.value = expandedKeys.value.filter((k) => k !== key)
|
||||
} else {
|
||||
expandedKeys.value = [...expandedKeys.value, key]
|
||||
}
|
||||
}
|
||||
|
||||
const onFileSelect = (selectedKeys, { node }) => {
|
||||
if (node.isLeaf) {
|
||||
if (node.fileData) {
|
||||
@ -945,15 +920,6 @@ const stopResize = () => {
|
||||
margin: 0 -4px;
|
||||
}
|
||||
|
||||
.tree-node-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
height: 28px;
|
||||
padding-right: 8px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.tree-node-name {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@ -963,7 +929,6 @@ const stopResize = () => {
|
||||
-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
||||
font-size: 14px;
|
||||
color: var(--gray-800);
|
||||
line-height: 28px;
|
||||
}
|
||||
|
||||
.name-start {
|
||||
@ -977,26 +942,14 @@ const stopResize = () => {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.folder-icon {
|
||||
color: #dcb67a;
|
||||
fill: #dcb67a;
|
||||
fill-opacity: 0.2;
|
||||
|
||||
&.open {
|
||||
color: #dcb67a;
|
||||
}
|
||||
}
|
||||
|
||||
.node-actions {
|
||||
.node-actions-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
padding-left: 4px;
|
||||
gap: 2px;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.tree-action-btn {
|
||||
display: none;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 24px;
|
||||
@ -1012,89 +965,6 @@ const stopResize = () => {
|
||||
color: var(--main-600);
|
||||
}
|
||||
|
||||
.tree-delete-btn:hover {
|
||||
color: var(--color-error-500);
|
||||
}
|
||||
|
||||
/* Specific Ant Design Tree Overrides */
|
||||
.file-tree-container :deep(.ant-tree) {
|
||||
font-family: inherit;
|
||||
font-size: 14px;
|
||||
overflow: hidden;
|
||||
|
||||
.ant-tree-treenode {
|
||||
width: 100%;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.ant-tree-node-content-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
transition: none;
|
||||
border-radius: 0;
|
||||
padding: 0 8px 0 0;
|
||||
line-height: 28px;
|
||||
flex: 1;
|
||||
position: relative;
|
||||
padding: 0 6px;
|
||||
border-radius: 6px;
|
||||
gap: 6px;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--gray-50);
|
||||
|
||||
.tree-action-btn {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
&.ant-tree-node-selected {
|
||||
background-color: var(--gray-100);
|
||||
}
|
||||
}
|
||||
|
||||
/* Icon Vertical Alignment */
|
||||
.ant-tree-iconEle {
|
||||
line-height: 30px;
|
||||
height: 30px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 22px;
|
||||
}
|
||||
|
||||
/* Ensure Title Fills Space */
|
||||
.ant-tree-title {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
/* Switcher (Arrow) */
|
||||
.ant-tree-switcher {
|
||||
width: 24px;
|
||||
height: 30px;
|
||||
line-height: 30px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
.ant-tree-switcher-icon {
|
||||
font-size: 10px;
|
||||
color: var(--gray-500);
|
||||
}
|
||||
}
|
||||
|
||||
.ant-tree-indent-unit {
|
||||
width: 18px;
|
||||
}
|
||||
|
||||
/* 隐藏文件夹展开/折叠箭头 */
|
||||
.ant-tree-switcher {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* 附件列表专用样式 */
|
||||
.attachment-tree :deep(.ant-tree-node-content-wrapper) {
|
||||
border: 1px solid var(--gray-200);
|
||||
|
||||
240
web/src/components/FileTreeComponent.vue
Normal file
240
web/src/components/FileTreeComponent.vue
Normal file
@ -0,0 +1,240 @@
|
||||
<template>
|
||||
<div class="file-tree-wrapper" :class="treeClass">
|
||||
<a-tree
|
||||
:selected-keys="selectedKeys"
|
||||
:expanded-keys="expandedKeys"
|
||||
:tree-data="treeData"
|
||||
:show-icon="showIcon"
|
||||
:block-node="blockNode"
|
||||
:show-line="showLine"
|
||||
class="custom-file-tree"
|
||||
@update:selected-keys="handleSelectedUpdate"
|
||||
@update:expanded-keys="handleExpandedUpdate"
|
||||
@select="handleSelect"
|
||||
>
|
||||
<!-- Custom Icon Slot -->
|
||||
<template #icon="{ data, expanded }">
|
||||
<slot name="icon" :node="data" :expanded="expanded">
|
||||
<template v-if="data.isLeaf">
|
||||
<component
|
||||
v-if="useFileIcons"
|
||||
:is="getFileIcon(data.key)"
|
||||
:style="{ color: getFileIconColor(data.key), fontSize: '16px' }"
|
||||
/>
|
||||
<FileText v-else :size="16" class="file-icon" />
|
||||
</template>
|
||||
<template v-else>
|
||||
<FolderOpen v-if="expanded" :size="18" class="folder-icon open" />
|
||||
<Folder v-else :size="18" class="folder-icon" />
|
||||
</template>
|
||||
</slot>
|
||||
</template>
|
||||
|
||||
<!-- Custom Title Slot -->
|
||||
<template #title="{ data }">
|
||||
<div class="tree-node-wrapper" @click="handleNodeClick(data)">
|
||||
<div class="tree-node-content">
|
||||
<slot name="title" :node="data">
|
||||
<span class="node-title-text" :title="data.title">{{ data.title }}</span>
|
||||
</slot>
|
||||
</div>
|
||||
<div class="node-actions" @click.stop v-if="$slots.actions">
|
||||
<slot name="actions" :node="data"></slot>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</a-tree>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { Folder, FolderOpen, FileText } from 'lucide-vue-next'
|
||||
import { getFileIcon, getFileIconColor } from '@/utils/file_utils'
|
||||
|
||||
const props = defineProps({
|
||||
treeData: {
|
||||
type: Array,
|
||||
required: true,
|
||||
default: () => []
|
||||
},
|
||||
selectedKeys: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
expandedKeys: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
showIcon: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
blockNode: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
showLine: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
treeClass: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
useFileIcons: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
})
|
||||
|
||||
const emit = defineEmits(['update:selectedKeys', 'update:expandedKeys', 'select', 'nodeClick', 'toggleFolder'])
|
||||
|
||||
const handleSelectedUpdate = (keys) => {
|
||||
emit('update:selectedKeys', keys)
|
||||
}
|
||||
|
||||
const handleExpandedUpdate = (keys) => {
|
||||
emit('update:expandedKeys', keys)
|
||||
}
|
||||
|
||||
const handleSelect = (selectedKeys, info) => {
|
||||
emit('select', selectedKeys, info)
|
||||
}
|
||||
|
||||
const handleNodeClick = (data) => {
|
||||
emit('nodeClick', data)
|
||||
|
||||
const isFolder = data.isLeaf === false || (data.children && Array.isArray(data.children))
|
||||
|
||||
if (isFolder) {
|
||||
const key = data.key
|
||||
const newExpandedKeys = [...props.expandedKeys]
|
||||
const index = newExpandedKeys.indexOf(key)
|
||||
|
||||
if (index > -1) {
|
||||
newExpandedKeys.splice(index, 1)
|
||||
} else {
|
||||
newExpandedKeys.push(key)
|
||||
}
|
||||
|
||||
emit('update:expandedKeys', newExpandedKeys)
|
||||
emit('toggleFolder', data, newExpandedKeys.indexOf(key) > -1)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.file-tree-wrapper {
|
||||
width: 100%;
|
||||
|
||||
/* 统一节点容器 */
|
||||
:deep(.ant-tree-treenode) {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
padding: 0 4px;
|
||||
height: 32px;
|
||||
|
||||
/* 隐藏切换器 */
|
||||
.ant-tree-switcher {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* 缩进单元 */
|
||||
.ant-tree-indent {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
align-self: stretch;
|
||||
&-unit {
|
||||
width: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
/* 内容区域容器 */
|
||||
.ant-tree-node-content-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
height: 32px;
|
||||
line-height: 32px;
|
||||
padding: 0 4px;
|
||||
border-radius: 4px;
|
||||
transition: background-color 0.2s;
|
||||
|
||||
/* 图标容器 */
|
||||
.ant-tree-iconEle {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 24px;
|
||||
height: 100%;
|
||||
margin-right: 4px;
|
||||
flex-shrink: 0;
|
||||
|
||||
.anticon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
/* 标题文字容器 */
|
||||
.ant-tree-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: var(--gray-50);
|
||||
}
|
||||
|
||||
&.ant-tree-node-selected {
|
||||
background-color: var(--gray-100);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.tree-node-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.tree-node-content {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.node-title-text {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
font-size: 13px;
|
||||
color: var(--gray-800);
|
||||
}
|
||||
|
||||
.folder-icon {
|
||||
color: #dcb67a;
|
||||
fill: #dcb67a;
|
||||
fill-opacity: 0.2;
|
||||
}
|
||||
|
||||
.node-actions {
|
||||
display: none;
|
||||
align-items: center;
|
||||
padding-left: 8px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.ant-tree-node-content-wrapper:hover .node-actions {
|
||||
display: flex;
|
||||
}
|
||||
</style>
|
||||
@ -104,12 +104,11 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="tree-content">
|
||||
<a-tree
|
||||
<FileTreeComponent
|
||||
v-model:selectedKeys="selectedTreeKeys"
|
||||
v-model:expandedKeys="expandedKeys"
|
||||
:tree-data="treeData"
|
||||
:selected-keys="selectedTreeKeys"
|
||||
default-expand-all
|
||||
@select="handleTreeSelect"
|
||||
block-node
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@ -216,6 +215,7 @@ import {
|
||||
} from 'lucide-vue-next'
|
||||
import { skillApi } from '@/apis/skill_api'
|
||||
import HeaderComponent from '@/components/HeaderComponent.vue'
|
||||
import FileTreeComponent from '@/components/FileTreeComponent.vue'
|
||||
|
||||
dayjs.extend(relativeTime)
|
||||
dayjs.locale('zh-cn')
|
||||
@ -232,6 +232,7 @@ const skills = ref([])
|
||||
const currentSkill = ref(null)
|
||||
const treeData = ref([])
|
||||
const selectedTreeKeys = ref([])
|
||||
const expandedKeys = ref([])
|
||||
const selectedPath = ref('')
|
||||
const selectedIsDir = ref(false)
|
||||
const fileContent = ref('')
|
||||
@ -268,16 +269,34 @@ const resetFileState = () => {
|
||||
selectedPath.value = ''
|
||||
selectedIsDir.value = false
|
||||
selectedTreeKeys.value = []
|
||||
expandedKeys.value = []
|
||||
fileContent.value = ''
|
||||
originalFileContent.value = ''
|
||||
}
|
||||
|
||||
const expandAllKeys = (nodes) => {
|
||||
let keys = []
|
||||
nodes.forEach(node => {
|
||||
if (node.is_dir) {
|
||||
keys.push(node.key)
|
||||
if (node.children) {
|
||||
keys = keys.concat(expandAllKeys(node.children))
|
||||
}
|
||||
}
|
||||
})
|
||||
return keys
|
||||
}
|
||||
|
||||
const fetchSkills = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const result = await skillApi.listSkills()
|
||||
skills.value = result?.data || []
|
||||
if (currentSkill.value) {
|
||||
|
||||
// 默认选中第一个技能并加载 SKILL.md
|
||||
if (!currentSkill.value && skills.value.length > 0) {
|
||||
await selectSkill(skills.value[0])
|
||||
} else if (currentSkill.value) {
|
||||
const latest = skills.value.find(i => i.slug === currentSkill.value.slug)
|
||||
if (latest) {
|
||||
currentSkill.value = latest
|
||||
@ -317,7 +336,9 @@ const reloadTree = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const result = await skillApi.getSkillTree(currentSkill.value.slug)
|
||||
treeData.value = normalizeTree(result?.data || [])
|
||||
const normalized = normalizeTree(result?.data || [])
|
||||
treeData.value = normalized
|
||||
expandedKeys.value = expandAllKeys(normalized)
|
||||
} catch {
|
||||
message.error('加载目录树失败')
|
||||
} finally {
|
||||
@ -325,11 +346,30 @@ const reloadTree = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
const loadSkillFile = async (slug, path = 'SKILL.md') => {
|
||||
try {
|
||||
const fileResult = await skillApi.getSkillFile(slug, path)
|
||||
const content = fileResult?.data?.content || ''
|
||||
fileContent.value = content
|
||||
originalFileContent.value = content
|
||||
selectedPath.value = path
|
||||
selectedIsDir.value = false
|
||||
selectedTreeKeys.value = [path]
|
||||
} catch {
|
||||
// 文件不存在时忽略
|
||||
}
|
||||
}
|
||||
|
||||
const selectSkill = async (record) => {
|
||||
currentSkill.value = record
|
||||
syncDependencyFormFromSkill(record)
|
||||
resetFileState()
|
||||
await reloadTree()
|
||||
|
||||
// 并行执行:加载树结构和获取 SKILL.md
|
||||
const [treeResult] = await Promise.all([
|
||||
reloadTree(),
|
||||
loadSkillFile(record.slug)
|
||||
])
|
||||
}
|
||||
|
||||
const handleTreeSelect = async (keys, info) => {
|
||||
@ -541,7 +581,7 @@ onMounted(fetchSkills)
|
||||
|
||||
.search-input {
|
||||
:deep(.ant-input) {
|
||||
height: 32px;
|
||||
height: 24px;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -617,9 +657,9 @@ onMounted(fetchSkills)
|
||||
}
|
||||
|
||||
.panel-top-bar {
|
||||
padding: 12px 24px;
|
||||
padding: 10px 16px 0 16px;
|
||||
box-sizing: border-box;
|
||||
border-bottom: 1px solid @border-color;
|
||||
// border-bottom: 1px solid @border-color;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
@ -651,7 +691,7 @@ onMounted(fetchSkills)
|
||||
|
||||
:deep(.ant-tabs-nav) {
|
||||
margin: 0;
|
||||
padding: 0 24px;
|
||||
padding: 0 16px;
|
||||
border-bottom: 1px solid @border-color;
|
||||
flex-shrink: 0;
|
||||
&::before { border-bottom: none; }
|
||||
@ -686,11 +726,11 @@ onMounted(fetchSkills)
|
||||
align-items: center;
|
||||
border-bottom: 1px solid @border-color;
|
||||
background-color: var(--gray-50);
|
||||
.label { font-size: 11px; font-weight: 600; color: var(--gray-400); text-transform: uppercase; letter-spacing: 0.5px; }
|
||||
.label { font-size: 11px; font-weight: 600; color: var(--gray-500); text-transform: uppercase; letter-spacing: 0.5px; }
|
||||
.tree-actions {
|
||||
display: flex; gap: 4px;
|
||||
button {
|
||||
background: none; border: none; padding: 2px; cursor: pointer; color: var(--gray-400);
|
||||
background: none; border: none; padding: 2px; cursor: pointer; color: var(--gray-500);
|
||||
display: flex; align-items: center;
|
||||
&:hover { color: var(--gray-900); }
|
||||
}
|
||||
@ -699,11 +739,6 @@ onMounted(fetchSkills)
|
||||
|
||||
.tree-content {
|
||||
flex: 1; overflow-y: auto; padding: 8px;
|
||||
:deep(.ant-tree) {
|
||||
background: transparent;
|
||||
.ant-tree-node-content-wrapper { border-radius: 4px; font-size: 13px; padding: 4px 8px; }
|
||||
.ant-tree-node-selected { background-color: var(--gray-200) !important; font-weight: 500; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -731,24 +766,25 @@ onMounted(fetchSkills)
|
||||
}
|
||||
|
||||
.editor-main {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
background-color: var(--gray-0);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
background-color: var(--gray-0);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.editor-main :deep(.ant-empty) {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.editor-main :deep(.ant-empty) {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.editor-main :deep(textarea) {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.editor-main :deep(textarea) {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
}
|
||||
.pure-editor {
|
||||
width: 100%; height: 100%; border: none; resize: none; padding: 20px;
|
||||
font-family: 'Fira Code', 'Monaco', monospace; font-size: 13px; line-height: 1.6;
|
||||
|
||||
@ -107,12 +107,6 @@ const mainList = computed(() => {
|
||||
path: '/database',
|
||||
icon: LibraryBig,
|
||||
activeIcon: LibraryBig
|
||||
},
|
||||
{
|
||||
name: 'Dashboard',
|
||||
path: '/dashboard',
|
||||
icon: BarChart3,
|
||||
activeIcon: BarChart3
|
||||
}
|
||||
]
|
||||
|
||||
@ -125,6 +119,13 @@ const mainList = computed(() => {
|
||||
})
|
||||
}
|
||||
|
||||
items.push({
|
||||
name: 'Dashboard',
|
||||
path: '/dashboard',
|
||||
icon: BarChart3,
|
||||
activeIcon: BarChart3
|
||||
})
|
||||
|
||||
return items
|
||||
})
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user