Files
ai-doc-template-system/frontend/src/components/TemplateEdit/LeftMenu.vue
T
2026-07-01 20:36:04 +08:00

248 lines
4.8 KiB
Vue

<script setup lang="ts">
import { reactive, ref } from "vue";
import {
AppstoreOutlined,
DatabaseOutlined,
DownOutlined,
FileTextOutlined,
SettingOutlined,
} from "@ant-design/icons-vue";
type MenuChild = {
key: string;
label: string;
status?: "done" | "empty";
};
type MenuItem = {
key: string;
label: string;
icon: "file" | "app" | "database" | "setting";
children?: MenuChild[];
};
const menuItems: MenuItem[] = [
{
key: "template",
label: "模板管理",
icon: "file",
children: [
{ key: "template-edit", label: "模板标注", status: "done" },
{ key: "template-center", label: "模板中心", status: "empty" },
],
},
{
key: "assets",
label: "资料与配置",
icon: "database",
children: [
{ key: "data-source", label: "数据源", status: "empty" },
{ key: "system-config", label: "系统配置", status: "empty" },
],
},
{
key: "workspace",
label: "工作台",
icon: "app",
},
];
const openKeys = reactive<Record<string, boolean>>({
template: true,
assets: true,
});
const activeKey = ref("template-edit");
function toggleMenu(key: string) {
openKeys[key] = !openKeys[key];
}
function selectItem(key: string) {
activeKey.value = key;
}
</script>
<template>
<aside class="left-menu">
<div class="brand">
<div class="brand-mark">AI</div>
<span>AI 文档模板系统</span>
</div>
<nav class="menu-list">
<div v-for="item in menuItems" :key="item.key" class="menu-group">
<button
class="menu-item"
:class="{ active: activeKey === item.key }"
type="button"
@click="item.children ? toggleMenu(item.key) : selectItem(item.key)"
>
<FileTextOutlined v-if="item.icon === 'file'" />
<AppstoreOutlined v-else-if="item.icon === 'app'" />
<DatabaseOutlined v-else-if="item.icon === 'database'" />
<SettingOutlined v-else />
<span>{{ item.label }}</span>
<DownOutlined
v-if="item.children"
class="menu-arrow"
:class="{ open: openKeys[item.key] }"
/>
</button>
<div v-if="item.children" class="sub-menu" :class="{ open: openKeys[item.key] }">
<button
v-for="child in item.children"
:key="child.key"
class="sub-menu-item"
:class="{ active: activeKey === child.key }"
type="button"
@click="selectItem(child.key)"
>
<span class="sub-dot" :class="child.status"></span>
<span>{{ child.label }}</span>
</button>
</div>
</div>
</nav>
</aside>
</template>
<style scoped>
.left-menu {
min-width: 0;
border-right: 1px solid var(--c-border);
background: var(--c-surface);
}
.brand {
display: flex;
align-items: center;
gap: 8px;
height: 52px;
padding: 0 16px;
border-bottom: 1px solid var(--c-border);
font-weight: 600;
}
.brand-mark {
display: grid;
width: 26px;
height: 26px;
place-items: center;
border-radius: 5px;
color: #fff;
background: var(--c-primary);
font-size: 13px;
font-weight: 700;
}
.menu-list {
padding: 8px 0;
}
.menu-group {
margin-bottom: 2px;
}
.menu-item {
position: relative;
display: flex;
align-items: center;
width: 100%;
gap: 8px;
padding: 8px 16px;
border: 0;
color: var(--c-text-secondary);
background: transparent;
cursor: pointer;
text-align: left;
}
.menu-item span {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.menu-item:hover {
color: var(--c-text);
background: var(--c-surface-soft);
}
.menu-item.active {
color: var(--c-primary);
background: var(--c-primary-soft);
font-weight: 500;
}
.menu-item.active::before {
position: absolute;
top: 4px;
bottom: 4px;
left: 0;
width: 3px;
border-radius: 0 2px 2px 0;
background: var(--c-primary);
content: "";
}
.menu-arrow {
margin-left: auto;
color: var(--c-text-muted);
font-size: 12px;
transition: transform 0.18s ease;
}
.menu-arrow.open {
transform: rotate(180deg);
}
.sub-menu {
max-height: 0;
overflow: hidden;
transition: max-height 0.22s ease;
}
.sub-menu.open {
max-height: 220px;
}
.sub-menu-item {
display: flex;
align-items: center;
width: 100%;
gap: 8px;
padding: 6px 16px 6px 32px;
border: 0;
color: var(--c-text-muted);
background: transparent;
cursor: pointer;
text-align: left;
}
.sub-menu-item:hover {
color: var(--c-text);
background: var(--c-surface-soft);
}
.sub-menu-item.active {
color: var(--c-primary);
font-weight: 500;
}
.sub-dot {
width: 6px;
height: 6px;
border-radius: 50%;
background: var(--c-text-muted);
}
.sub-dot.done {
background: var(--c-success);
}
.sub-dot.empty {
background: var(--c-border);
}
</style>