ForcePilot/web/src/layouts/AppLayout.vue

518 lines
11 KiB
Vue
Raw Normal View History

2024-07-14 16:42:38 +08:00
<script setup>
import { ref, reactive, onMounted, useTemplateRef, computed } from 'vue'
2024-07-14 16:42:38 +08:00
import { RouterLink, RouterView, useRoute } from 'vue-router'
import {
2024-07-27 14:32:57 +08:00
GithubOutlined,
2024-11-23 11:14:26 +08:00
ExclamationCircleOutlined,
2024-07-14 16:42:38 +08:00
} from '@ant-design/icons-vue'
import { Bot, Waypoints, LibraryBig, Settings } from 'lucide-vue-next';
import { onLongPress } from '@vueuse/core'
2024-07-27 14:32:57 +08:00
import { useConfigStore } from '@/stores/config'
import { useDatabaseStore } from '@/stores/database'
import { useInfoStore } from '@/stores/info'
2025-05-02 23:56:59 +08:00
import UserInfoComponent from '@/components/UserInfoComponent.vue'
import DebugComponent from '@/components/DebugComponent.vue'
const configStore = useConfigStore()
const databaseStore = useDatabaseStore()
const infoStore = useInfoStore()
2024-09-24 11:58:28 +08:00
const layoutSettings = reactive({
showDebug: false,
useTopBar: false, // 是否使用顶栏
})
2024-08-25 20:29:24 +08:00
2025-02-26 12:37:06 +08:00
// Add state for GitHub stars
const githubStars = ref(0)
const isLoadingStars = ref(false)
// Add state for debug modal
const showDebugModal = ref(false)
const htmlRefHook = useTemplateRef('htmlRefHook')
// Setup long press for debug modal
onLongPress(
htmlRefHook,
() => {
console.log('long press')
showDebugModal.value = true
},
{
delay: 1000, // 1秒长按
modifiers: {
prevent: true
}
}
)
// Handle debug modal close
const handleDebugModalClose = () => {
showDebugModal.value = false
}
const getRemoteConfig = () => {
2024-10-24 13:09:59 +08:00
configStore.refreshConfig()
}
const getRemoteDatabase = () => {
databaseStore.getDatabaseInfo()
}
2025-02-26 12:37:06 +08:00
// Fetch GitHub stars count
const fetchGithubStars = async () => {
try {
isLoadingStars.value = true
2025-05-02 23:56:59 +08:00
// 公共API可以直接使用fetch
2025-02-26 12:37:06 +08:00
const response = await fetch('https://api.github.com/repos/xerrors/Yuxi-Know')
const data = await response.json()
githubStars.value = data.stargazers_count
} catch (error) {
2025-05-02 23:56:59 +08:00
console.error('获取GitHub stars失败:', error)
2025-02-26 12:37:06 +08:00
} finally {
isLoadingStars.value = false
}
}
onMounted(async () => {
// 加载信息配置
await infoStore.loadInfoConfig()
// 加载其他配置
getRemoteConfig()
getRemoteDatabase()
2025-02-26 12:37:06 +08:00
fetchGithubStars() // Fetch GitHub stars on mount
})
2024-07-14 16:42:38 +08:00
// 打印当前页面的路由信息,使用 vue3 的 setup composition API
const route = useRoute()
console.log(route)
2025-03-25 05:40:07 +08:00
// 下面是导航菜单部分,添加智能体项
const mainList = [{
2025-05-04 21:04:01 +08:00
name: '智能体',
path: '/agent',
icon: Bot,
activeIcon: Bot,
2025-03-25 05:40:07 +08:00
}, {
name: '图谱',
path: '/graph',
icon: Waypoints,
activeIcon: Waypoints,
2025-03-25 05:40:07 +08:00
}, {
name: '知识库',
path: '/database',
icon: LibraryBig,
activeIcon: LibraryBig,
2025-03-25 05:40:07 +08:00
}
]
2024-07-14 16:42:38 +08:00
</script>
<template>
2024-09-24 11:58:28 +08:00
<div class="app-layout" :class="{ 'use-top-bar': layoutSettings.useTopBar }">
<div class="header" :class="{ 'top-bar': layoutSettings.useTopBar }">
2024-11-14 20:07:49 +08:00
<div class="logo circle">
2024-09-24 11:58:28 +08:00
<router-link to="/">
<img :src="infoStore.organization.avatar">
2024-09-24 11:58:28 +08:00
</router-link>
2024-07-14 16:42:38 +08:00
</div>
<div class="nav">
2025-03-25 05:40:07 +08:00
<!-- 使用mainList渲染导航项 -->
<RouterLink
v-for="(item, index) in mainList"
:key="index"
:to="item.path"
v-show="!item.hidden"
class="nav-item"
active-class="active">
<component class="icon" :is="route.path.startsWith(item.path) ? item.activeIcon : item.icon" size="22"/>
2025-03-25 05:40:07 +08:00
<span class="text">{{item.name}}</span>
2024-09-01 18:10:50 +08:00
</RouterLink>
2024-07-14 16:42:38 +08:00
</div>
<div
ref="htmlRefHook"
class="fill debug-trigger"
></div>
2025-05-02 23:56:59 +08:00
2024-07-27 14:32:57 +08:00
<div class="github nav-item">
2025-04-01 00:39:54 +08:00
<a-tooltip placement="right">
<template #title>欢迎 Star</template>
2025-04-01 00:39:54 +08:00
<a href="https://github.com/xerrors/Yuxi-Know" target="_blank" class="github-link">
<GithubOutlined class="icon" style="color: #222;"/>
<span v-if="githubStars > 0" class="github-stars">
2025-05-04 21:04:01 +08:00
<span class="star-count">{{ (githubStars / 1000).toFixed(1) }}k</span>
2025-04-01 00:39:54 +08:00
</span>
</a>
</a-tooltip>
</div>
2025-05-04 21:04:01 +08:00
<!-- <div class="nav-item api-docs">
2025-04-01 00:39:54 +08:00
<a-tooltip placement="right">
<template #title>接口文档 {{ apiDocsUrl }}</template>
<a :href="apiDocsUrl" target="_blank" class="github-link">
<ApiOutlined class="icon" style="color: #222;"/>
</a>
</a-tooltip>
2025-05-04 21:04:01 +08:00
</div> -->
<!-- 用户信息组件 -->
<div class="nav-item user-info">
<a-tooltip placement="right">
<template #title>用户信息</template>
<UserInfoComponent />
</a-tooltip>
</div>
2025-03-25 05:40:07 +08:00
<RouterLink class="nav-item setting" to="/setting" active-class="active">
2025-04-01 00:39:54 +08:00
<a-tooltip placement="right">
<template #title>设置</template>
<Settings />
2025-04-01 00:39:54 +08:00
</a-tooltip>
2024-07-14 16:42:38 +08:00
</RouterLink>
</div>
2024-07-27 14:32:57 +08:00
<div class="header-mobile">
<RouterLink to="/chat" class="nav-item" active-class="active">对话</RouterLink>
<RouterLink to="/database" class="nav-item" active-class="active">知识</RouterLink>
<RouterLink to="/setting" class="nav-item" active-class="active">设置</RouterLink>
</div>
2024-09-14 02:46:21 +08:00
<router-view v-slot="{ Component, route }" id="app-router-view">
<keep-alive v-if="route.meta.keepAlive !== false">
2024-07-14 16:42:38 +08:00
<component :is="Component" />
</keep-alive>
2024-09-14 02:46:21 +08:00
<component :is="Component" v-else />
2024-07-14 16:42:38 +08:00
</router-view>
<!-- Debug Modal -->
<a-modal
v-model:open="showDebugModal"
title="调试面板"
width="90%"
:footer="null"
@cancel="handleDebugModalClose"
:maskClosable="true"
:destroyOnClose="true"
class="debug-modal"
>
<DebugComponent />
</a-modal>
2024-07-14 16:42:38 +08:00
</div>
</template>
<style lang="less" scoped>
@import '@/assets/css/main.css';
2024-07-14 16:42:38 +08:00
2025-04-15 12:39:46 +08:00
:root {
--header-width: 60px;
}
2024-07-14 16:42:38 +08:00
.app-layout {
display: flex;
flex-direction: row;
width: 100%;
height: 100vh;
min-width: var(--min-width);
2024-07-27 14:32:57 +08:00
.header-mobile {
display: none;
}
2024-08-25 20:29:24 +08:00
.debug-panel {
position: absolute;
z-index: 100;
right: 0;
2024-09-01 21:14:23 +08:00
bottom: 50px;
border-radius: 20px 0 0 20px;
2024-08-25 20:29:24 +08:00
cursor: pointer;
}
2024-07-14 16:42:38 +08:00
}
div.header, #app-router-view {
height: 100%;
max-width: 100%;
2024-09-14 02:46:21 +08:00
user-select: none;
2024-07-14 16:42:38 +08:00
}
#app-router-view {
flex: 1 1 auto;
overflow-y: auto;
}
.header {
display: flex;
flex-direction: column;
2025-04-15 12:39:46 +08:00
flex: 0 0 var(--header-width);
2024-07-14 16:42:38 +08:00
justify-content: flex-start;
align-items: center;
background-color: var(--main-10);
2024-07-14 16:42:38 +08:00
height: 100%;
2025-04-15 12:39:46 +08:00
width: var(--header-width);
2024-09-28 00:41:02 +08:00
border-right: 1px solid var(--gray-300);
2024-07-14 16:42:38 +08:00
.nav {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
position: relative;
// height: 45px;
gap: 16px;
}
// 添加debug触发器样式
.debug-trigger {
position: relative;
height: 100%;
width: 100%;
min-height: 20px;
flex-grow: 1;
}
.logo {
width: 40px;
height: 40px;
margin: 8px 0 14px 0;
2024-07-14 16:42:38 +08:00
img {
width: 100%;
height: 100%;
2024-11-23 11:14:26 +08:00
border-radius: 4px; // 50% for circle
2024-07-14 16:42:38 +08:00
}
& > a {
text-decoration: none;
font-size: 24px;
font-weight: bold;
color: #333;
}
}
2024-07-16 18:14:27 +08:00
.nav-item {
2024-09-14 02:46:21 +08:00
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
2024-09-25 13:46:51 +08:00
width: 52px;
2024-09-14 02:46:21 +08:00
padding: 4px;
padding-top: 10px;
border: 1px solid transparent;
border-radius: 8px;
background-color: transparent;
color: #222;
font-size: 20px;
transition: background-color 0.2s ease-in-out;
2025-04-15 12:39:46 +08:00
margin: 0;
2024-09-14 02:46:21 +08:00
text-decoration: none;
cursor: pointer;
&.github {
padding: 10px 12px;
2024-09-24 11:58:28 +08:00
&:hover {
background-color: transparent;
border: 1px solid transparent;
}
2025-02-26 12:37:06 +08:00
.github-link {
display: flex;
flex-direction: column;
align-items: center;
color: inherit;
}
.github-stars {
display: flex;
align-items: center;
font-size: 12px;
margin-top: 4px;
.star-icon {
color: #f0a742;
font-size: 12px;
margin-right: 2px;
}
.star-count {
font-weight: 600;
}
}
2024-09-14 02:46:21 +08:00
}
2025-04-01 00:39:54 +08:00
&.api-docs {
padding: 10px 12px;
}
&.active {
text-shadow: 0 0 15px var(--main-300);
font-weight: bold;
color: var(--main-color);
}
2024-11-23 11:14:26 +08:00
&.warning {
color: red;
}
&:hover {
color: var(--main-color);
2024-09-14 02:46:21 +08:00
}
.text {
font-size: 12px;
margin-top: 4px;
text-align: center;
}
2024-07-14 16:42:38 +08:00
}
2024-07-27 14:32:57 +08:00
.setting {
width: auto;
font-size: 20px;
color: #333;
2025-05-04 21:04:01 +08:00
margin-bottom: 8px;
2025-04-15 12:39:46 +08:00
padding: 16px 12px;
2024-07-27 14:32:57 +08:00
&:hover {
cursor: pointer;
}
}
}
2024-07-14 16:42:38 +08:00
2024-07-16 18:14:27 +08:00
@media (max-width: 520px) {
2024-07-14 16:42:38 +08:00
.app-layout {
flex-direction: column-reverse;
2024-07-27 14:32:57 +08:00
div.header {
display: none;
}
2024-07-14 16:42:38 +08:00
2025-02-27 01:37:42 +08:00
.debug-panel {
bottom: 10rem;
}
}
2024-07-27 14:32:57 +08:00
.app-layout div.header-mobile {
display: flex;
2024-07-14 16:42:38 +08:00
flex-direction: row;
width: 100%;
padding: 0 20px;
2024-07-27 14:32:57 +08:00
justify-content: space-around;
2024-07-14 16:42:38 +08:00
align-items: center;
2024-07-16 18:14:27 +08:00
flex: 0 0 60px;
border-right: none;
2025-02-27 01:37:42 +08:00
height: 40px;
2024-07-14 16:42:38 +08:00
.nav-item {
text-decoration: none;
2024-07-27 14:32:57 +08:00
width: 40px;
2024-09-25 13:46:51 +08:00
color: var(--gray-900);
2024-07-27 14:32:57 +08:00
font-size: 1rem;
font-weight: bold;
2024-07-27 14:32:57 +08:00
transition: color 0.1s ease-in-out, font-size 0.1s ease-in-out;
2024-07-27 14:32:57 +08:00
&.active {
color: black;
font-size: 1.1rem;
}
2024-07-14 16:42:38 +08:00
}
}
2024-07-16 18:14:27 +08:00
.app-layout .chat-box::webkit-scrollbar {
width: 0;
}
2024-07-14 16:42:38 +08:00
}
2024-09-24 11:58:28 +08:00
.app-layout.use-top-bar {
flex-direction: column;
}
.header.top-bar {
flex-direction: row;
flex: 0 0 50px;
width: 100%;
height: 50px;
border-right: none;
border-bottom: 1px solid var(--main-40);
background-color: var(--main-20);
2024-09-24 11:58:28 +08:00
padding: 0 20px;
gap: 24px;
.logo {
width: fit-content;
height: 28px;
margin-right: 16px;
display: flex;
align-items: center;
a {
display: flex;
align-items: center;
text-decoration: none;
color: inherit;
}
img {
width: 28px;
height: 28px;
margin-right: 8px;
}
}
.nav {
flex-direction: row;
height: auto;
gap: 20px;
}
.nav-item {
flex-direction: row;
width: auto;
padding: 4px 16px;
margin: 0;
.icon {
margin-right: 8px;
font-size: 15px; // 减小图标大小
border: none;
outline: none;
&:focus, &:active {
border: none;
outline: none;
}
2024-09-24 11:58:28 +08:00
}
.text {
margin-top: 0;
font-size: 15px;
}
&.github, &.setting {
padding: 8px 12px;
.icon {
margin-right: 0;
font-size: 18px;
}
&.active {
color: var(--main-color);
2024-09-24 11:58:28 +08:00
}
}
&.github {
a {
display: flex;
align-items: center;
}
2025-02-26 12:37:06 +08:00
.github-stars {
display: flex;
align-items: center;
margin-left: 6px;
.star-icon {
color: #f0a742;
font-size: 14px;
margin-right: 2px;
}
}
2024-09-24 11:58:28 +08:00
}
}
}
2025-02-26 12:37:06 +08:00
</style>