feat(ui): 添加侧边导航栏并重构顶部导航栏
- 新增 SideNav 组件实现侧边导航功能 - 将原顶部导航栏中的部分功能移至侧边导航栏 - 优化顶部导航栏布局和样式 - 调整底部工具栏的间距和样式
This commit is contained in:
@@ -28,6 +28,7 @@ import ResetConfirmModal from './components/ResetConfirmModal.vue'
|
||||
import SettingsModal from './components/SettingsModal.vue'
|
||||
import SummaryModal from './components/SummaryModal.vue'
|
||||
import TopNav from './components/TopNav.vue'
|
||||
import SideNav from './components/SideNav.vue'
|
||||
import WindowNode from './components/WindowNode.vue'
|
||||
|
||||
// 业务层:统一的状态与动作入口
|
||||
@@ -43,7 +44,7 @@ const isFullscreen = ref(false)
|
||||
|
||||
const toggleFullscreen = () => {
|
||||
if (!document.fullscreenElement) {
|
||||
document.documentElement.requestFullscreen().catch((err) => {
|
||||
document.documentElement.requestFullscreen().catch(err => {
|
||||
console.error(`Error attempting to enable full-screen mode: ${err.message} (${err.name})`)
|
||||
})
|
||||
} else {
|
||||
@@ -150,6 +151,8 @@ const fitToView = () => {
|
||||
@toggle-locale="toggleLocale"
|
||||
/>
|
||||
|
||||
<SideNav :t="t" :locale="locale" :config="config" />
|
||||
|
||||
<div class="flex-grow relative">
|
||||
<VueFlow
|
||||
:default-edge-options="{ type: config.edgeType }"
|
||||
@@ -168,7 +171,7 @@ const fitToView = () => {
|
||||
:gap="24"
|
||||
:size="config.backgroundVariant === BackgroundVariant.Dots ? 1 : 0.5"
|
||||
/>
|
||||
<Controls v-if="config.showControls" :show-fullscreen="false" :show-fit-view="false">
|
||||
<Controls v-if="false" :show-fullscreen="false" :show-fit-view="false">
|
||||
<ControlButton @click="toggleFullscreen" :title="isFullscreen ? t('nav.exitFullscreen') : t('nav.fullscreen')">
|
||||
<component :is="isFullscreen ? Minimize : Maximize" class="w-4 h-4 text-slate-500" />
|
||||
</ControlButton>
|
||||
|
||||
@@ -32,9 +32,11 @@ const emit = defineEmits<{
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="fixed bottom-6 md:bottom-12 left-1/2 -translate-x-1/2 z-50 flex flex-col items-center gap-4 w-full max-w-2xl px-4 md:px-6">
|
||||
<div class="fixed bottom-6 md:bottom-8 left-1/2 -translate-x-1/2 z-50 flex flex-col items-center gap-4 w-full max-w-2xl px-4 md:px-6">
|
||||
<div class="flex items-center gap-2 md:gap-3 w-full">
|
||||
<div class="flex-grow flex items-center gap-2 md:gap-4 bg-slate-50 border border-slate-200 rounded-xl md:rounded-2xl px-3 md:px-5 py-2 md:py-3 focus-within:bg-white focus-within:shadow-xl focus-within:shadow-slate-100 transition-all">
|
||||
<div
|
||||
class="flex-grow flex items-center gap-2 md:gap-4 bg-slate-50 border border-slate-200 rounded-xl md:rounded-2xl px-3 md:px-5 py-2 md:py-3 focus-within:bg-white focus-within:shadow-xl focus-within:shadow-slate-100 transition-all"
|
||||
>
|
||||
<Terminal class="w-4 h-4 md:w-5 h-5 text-slate-400 flex-shrink-0" />
|
||||
<input
|
||||
:value="props.modelValue"
|
||||
@@ -55,7 +57,9 @@ const emit = defineEmits<{
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-2 px-3 py-1 bg-white/60 backdrop-blur-sm border border-slate-200/50 rounded-full text-[10px] font-black tracking-widest uppercase select-none shadow-sm">
|
||||
<div
|
||||
class="flex items-center gap-2 px-3 py-1 bg-white/60 backdrop-blur-sm border border-slate-200/50 rounded-full text-[10px] font-black tracking-widest uppercase select-none shadow-sm"
|
||||
>
|
||||
<a
|
||||
href="https://github.com/liu-ziting/ThinkFlowAI"
|
||||
target="_blank"
|
||||
|
||||
155
src/components/SideNav.vue
Normal file
155
src/components/SideNav.vue
Normal file
@@ -0,0 +1,155 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, ref, onMounted, onUnmounted } from 'vue'
|
||||
import { BackgroundVariant } from '@vue-flow/background'
|
||||
import { ArrowLeftRight, Map, Palette, Layers, Grid } from 'lucide-vue-next'
|
||||
|
||||
const props = defineProps<{
|
||||
t: any
|
||||
locale: string
|
||||
config: any
|
||||
}>()
|
||||
|
||||
const isEdgeTypeMenuOpen = ref(false)
|
||||
const isBackgroundMenuOpen = ref(false)
|
||||
|
||||
const edgeTypeOptions = [
|
||||
{ value: 'default', labelKey: 'nav.edgeTypes.default' },
|
||||
{ value: 'straight', labelKey: 'nav.edgeTypes.straight' },
|
||||
{ value: 'step', labelKey: 'nav.edgeTypes.step' },
|
||||
{ value: 'smoothstep', labelKey: 'nav.edgeTypes.smoothstep' },
|
||||
{ value: 'simplebezier', labelKey: 'nav.edgeTypes.simplebezier' }
|
||||
]
|
||||
|
||||
const backgroundOptions = [
|
||||
{ value: BackgroundVariant.Lines, labelKey: 'nav.lines' },
|
||||
{ value: BackgroundVariant.Dots, labelKey: 'nav.dots' }
|
||||
]
|
||||
|
||||
const closeMenus = () => {
|
||||
isEdgeTypeMenuOpen.value = false
|
||||
isBackgroundMenuOpen.value = false
|
||||
}
|
||||
|
||||
const toggleEdgeTypeMenu = () => {
|
||||
isEdgeTypeMenuOpen.value = !isEdgeTypeMenuOpen.value
|
||||
if (isEdgeTypeMenuOpen.value) isBackgroundMenuOpen.value = false
|
||||
}
|
||||
|
||||
const toggleBackgroundMenu = () => {
|
||||
isBackgroundMenuOpen.value = !isBackgroundMenuOpen.value
|
||||
if (isBackgroundMenuOpen.value) isEdgeTypeMenuOpen.value = false
|
||||
}
|
||||
|
||||
const setEdgeType = (value: string) => {
|
||||
props.config.edgeType = value
|
||||
closeMenus()
|
||||
}
|
||||
|
||||
const setBackgroundVariant = (value: any) => {
|
||||
props.config.backgroundVariant = value
|
||||
closeMenus()
|
||||
}
|
||||
|
||||
const handleDocumentPointerDown = (e: Event) => {
|
||||
const target = e.target as HTMLElement | null
|
||||
if (!target) return
|
||||
if (target.closest('[data-side-menu="true"]')) return
|
||||
closeMenus()
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
document.addEventListener('pointerdown', handleDocumentPointerDown)
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
document.removeEventListener('pointerdown', handleDocumentPointerDown)
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="fixed left-4 top-1/2 -translate-y-1/2 z-40 hidden md:flex flex-col gap-3">
|
||||
<div class="bg-white/80 backdrop-blur-md border border-slate-200 rounded-2xl shadow-xl p-2 flex flex-col gap-2">
|
||||
<!-- 联动拖拽 -->
|
||||
<button
|
||||
@click="props.config.hierarchicalDragging = !props.config.hierarchicalDragging"
|
||||
class="side-btn"
|
||||
:class="props.config.hierarchicalDragging ? 'text-orange-500 bg-orange-50 border-orange-100' : 'text-slate-400 hover:text-slate-600'"
|
||||
:title="props.t('nav.hierarchicalDragging')"
|
||||
>
|
||||
<ArrowLeftRight class="w-5 h-5" />
|
||||
</button>
|
||||
|
||||
<!-- 小地图 -->
|
||||
<button
|
||||
@click="props.config.showMiniMap = !props.config.showMiniMap"
|
||||
class="side-btn"
|
||||
:class="props.config.showMiniMap ? 'text-blue-500 bg-blue-50 border-blue-100' : 'text-slate-400 hover:text-slate-600'"
|
||||
:title="props.t('nav.map')"
|
||||
>
|
||||
<Map class="w-5 h-5" />
|
||||
</button>
|
||||
|
||||
<div class="h-px bg-slate-100 mx-2 my-1"></div>
|
||||
|
||||
<!-- 连线颜色 -->
|
||||
<div class="relative group p-2 flex flex-col items-center gap-1 bg-slate-50 rounded-xl border border-slate-100">
|
||||
<Palette class="w-4 h-4 text-slate-400" />
|
||||
<input type="color" v-model="props.config.edgeColor" class="w-5 h-5 rounded-md cursor-pointer bg-transparent border-none" />
|
||||
<span class="text-[8px] font-black text-slate-400 uppercase">{{ props.t('nav.edge') }}</span>
|
||||
</div>
|
||||
|
||||
<!-- 连线类型 -->
|
||||
<div data-side-menu="true" class="relative">
|
||||
<button @click="toggleEdgeTypeMenu" class="side-btn" :class="isEdgeTypeMenuOpen ? 'bg-slate-100 text-slate-900' : 'text-slate-400'" :title="props.t('nav.edge')">
|
||||
<Layers class="w-5 h-5" />
|
||||
</button>
|
||||
<div v-if="isEdgeTypeMenuOpen" class="absolute left-full ml-3 top-0 bg-white border border-slate-200 rounded-xl shadow-2xl p-1.5 min-w-[120px] z-50 transition-all">
|
||||
<button
|
||||
v-for="opt in edgeTypeOptions"
|
||||
:key="opt.value"
|
||||
class="w-full text-left px-3 py-2 rounded-lg text-[10px] font-black tracking-widest uppercase transition-colors"
|
||||
:class="opt.value === props.config.edgeType ? 'bg-orange-50 text-orange-600' : 'text-slate-600 hover:bg-slate-50'"
|
||||
@click="setEdgeType(opt.value)"
|
||||
>
|
||||
{{ props.t(opt.labelKey) }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 背景类型 -->
|
||||
<div data-side-menu="true" class="relative">
|
||||
<button
|
||||
@click="toggleBackgroundMenu"
|
||||
class="side-btn"
|
||||
:class="isBackgroundMenuOpen ? 'bg-slate-100 text-slate-900' : 'text-slate-400'"
|
||||
:title="props.t('nav.lines')"
|
||||
>
|
||||
<Grid class="w-5 h-5" />
|
||||
</button>
|
||||
<div
|
||||
v-if="isBackgroundMenuOpen"
|
||||
class="absolute left-full ml-3 top-0 bg-white border border-slate-200 rounded-xl shadow-2xl p-1.5 min-w-[100px] z-50 transition-all"
|
||||
>
|
||||
<button
|
||||
v-for="opt in backgroundOptions"
|
||||
:key="String(opt.value)"
|
||||
class="w-full text-left px-3 py-2 rounded-lg text-[10px] font-black tracking-widest uppercase transition-colors"
|
||||
:class="opt.value === props.config.backgroundVariant ? 'bg-orange-50 text-orange-600' : 'text-slate-600 hover:bg-slate-50'"
|
||||
@click="setBackgroundVariant(opt.value)"
|
||||
>
|
||||
{{ props.t(opt.labelKey) }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.side-btn {
|
||||
@apply w-10 h-10 flex items-center justify-center rounded-xl border border-transparent transition-all active:scale-90 relative;
|
||||
}
|
||||
.side-btn:hover {
|
||||
@apply border-slate-200 shadow-sm;
|
||||
}
|
||||
</style>
|
||||
@@ -12,23 +12,7 @@ import { computed, onMounted, onUnmounted, ref } from 'vue'
|
||||
import { BackgroundVariant } from '@vue-flow/background'
|
||||
|
||||
// 图标:所有按钮与状态展示
|
||||
import {
|
||||
ArrowLeftRight,
|
||||
ChevronDown,
|
||||
ChevronUp,
|
||||
Download,
|
||||
Focus,
|
||||
Globe,
|
||||
LayoutDashboard,
|
||||
Map,
|
||||
Menu,
|
||||
Palette,
|
||||
Settings,
|
||||
Sparkles,
|
||||
Target,
|
||||
Trash2,
|
||||
X
|
||||
} from 'lucide-vue-next'
|
||||
import { ChevronDown, ChevronUp, Download, Focus, LayoutDashboard, Menu, Sparkles, Target, X, Trash2, Globe, Settings } from 'lucide-vue-next'
|
||||
|
||||
/**
|
||||
* props:
|
||||
@@ -164,89 +148,6 @@ const callAndClose = (fn: () => void) => {
|
||||
|
||||
<div class="h-4 w-[1px] bg-slate-100 mx-1 flex-shrink-0"></div>
|
||||
|
||||
<button
|
||||
@click="props.config.hierarchicalDragging = !props.config.hierarchicalDragging"
|
||||
class="toolbar-btn border-slate-100 flex-shrink-0"
|
||||
:class="props.config.hierarchicalDragging ? 'text-orange-500 bg-orange-50 border-orange-100' : 'text-slate-400 hover:text-slate-600'"
|
||||
:title="props.t('nav.hierarchicalDragging')"
|
||||
>
|
||||
<ArrowLeftRight class="w-3.5 h-3.5 md:w-4 h-4" />
|
||||
<span>{{ props.t('nav.hierarchicalDragging') }}</span>
|
||||
</button>
|
||||
|
||||
<div class="h-4 w-[1px] bg-slate-100 mx-1 flex-shrink-0"></div>
|
||||
|
||||
<button @click="props.onStartNewSession" class="toolbar-btn text-red-500 hover:bg-red-50 border-red-100 flex-shrink-0" :title="props.t('nav.reset')">
|
||||
<Trash2 class="w-3.5 h-3.5 md:w-4 h-4" />
|
||||
<span>{{ props.t('nav.reset') }}</span>
|
||||
</button>
|
||||
|
||||
<div class="h-4 w-[1px] bg-slate-100 mx-1 flex-shrink-0"></div>
|
||||
|
||||
<div class="flex items-center gap-2 px-2 md:px-3 py-1.5 bg-slate-50 rounded-lg border border-slate-100 flex-shrink-0">
|
||||
<Palette class="w-3 h-3 md:w-3.5 h-3.5 text-slate-400" />
|
||||
<input type="color" v-model="props.config.edgeColor" class="w-3.5 h-3.5 md:w-4 h-4 rounded cursor-pointer bg-transparent border-none" />
|
||||
<span class="text-[9px] md:text-[10px] font-bold text-slate-500 uppercase">{{ props.t('nav.edge') }}</span>
|
||||
</div>
|
||||
|
||||
<div data-edge-type-menu="true" class="relative flex-shrink-0">
|
||||
<button type="button" class="toolbar-select flex items-center gap-2" @click="toggleEdgeTypeMenu">
|
||||
<span>{{ currentEdgeTypeLabel }}</span>
|
||||
<ChevronDown class="w-3 h-3 opacity-60" />
|
||||
</button>
|
||||
<div
|
||||
v-if="isEdgeTypeMenuOpen"
|
||||
class="absolute top-full left-0 mt-2 min-w-[180px] bg-white border border-slate-200 rounded-lg shadow-xl p-1 z-50"
|
||||
>
|
||||
<button
|
||||
v-for="opt in edgeTypeOptions"
|
||||
:key="opt.value"
|
||||
type="button"
|
||||
class="w-full text-left px-3 py-2 rounded-md text-[10px] font-black tracking-widest uppercase transition-colors"
|
||||
:class="opt.value === props.config.edgeType ? 'bg-orange-50 text-orange-600' : 'text-slate-600 hover:bg-slate-50'"
|
||||
@click="setEdgeType(opt.value)"
|
||||
>
|
||||
{{ props.t(opt.labelKey) }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div data-background-menu="true" class="relative flex-shrink-0">
|
||||
<button type="button" class="toolbar-select flex items-center gap-2" @click="toggleBackgroundMenu">
|
||||
<span>{{ currentBackgroundLabel }}</span>
|
||||
<ChevronDown class="w-3 h-3 opacity-60" />
|
||||
</button>
|
||||
<div
|
||||
v-if="isBackgroundMenuOpen"
|
||||
class="absolute top-full left-0 mt-2 min-w-[140px] bg-white border border-slate-200 rounded-lg shadow-xl p-1 z-50"
|
||||
>
|
||||
<button
|
||||
v-for="opt in backgroundOptions"
|
||||
:key="String(opt.value)"
|
||||
type="button"
|
||||
class="w-full text-left px-3 py-2 rounded-md text-[10px] font-black tracking-widest uppercase transition-colors"
|
||||
:class="opt.value === props.config.backgroundVariant ? 'bg-orange-50 text-orange-600' : 'text-slate-600 hover:bg-slate-50'"
|
||||
@click="setBackgroundVariant(opt.value)"
|
||||
>
|
||||
{{ props.t(opt.labelKey) }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="h-4 w-[1px] bg-slate-100 mx-1 flex-shrink-0"></div>
|
||||
|
||||
<button
|
||||
@click="props.config.showMiniMap = !props.config.showMiniMap"
|
||||
class="toolbar-btn border-slate-100 flex-shrink-0"
|
||||
:class="props.config.showMiniMap ? 'text-blue-500 bg-blue-50 border-blue-100' : 'text-slate-400 hover:text-slate-600'"
|
||||
:title="props.t('nav.map')"
|
||||
>
|
||||
<Map class="w-3.5 h-3.5 md:w-4 h-4" />
|
||||
<span>{{ props.t('nav.map') }}</span>
|
||||
</button>
|
||||
|
||||
<div class="h-4 w-[1px] bg-slate-100 mx-1 flex-shrink-0"></div>
|
||||
|
||||
<button @click="props.onGenerateSummary" class="toolbar-btn text-orange-600 hover:bg-orange-50 border-orange-100 flex-shrink-0" :title="props.t('nav.summary')">
|
||||
<Sparkles class="w-3.5 h-3.5 md:w-4 h-4" />
|
||||
<span>{{ props.t('nav.summary') }}</span>
|
||||
@@ -259,9 +160,9 @@ const callAndClose = (fn: () => void) => {
|
||||
|
||||
<div class="h-4 w-[1px] bg-slate-100 mx-1 flex-shrink-0"></div>
|
||||
|
||||
<button @click="props.onOpenSettings" class="toolbar-btn text-slate-600 hover:bg-slate-50 border-slate-100 flex-shrink-0">
|
||||
<Settings class="w-3.5 h-3.5 md:w-4 h-4" />
|
||||
<span>{{ props.t('common.settings') }}</span>
|
||||
<button @click="props.onStartNewSession" class="toolbar-btn text-red-500 hover:bg-red-50 border-red-100 flex-shrink-0" :title="props.t('nav.reset')">
|
||||
<Trash2 class="w-3.5 h-3.5 md:w-4 h-4" />
|
||||
<span>{{ props.t('nav.reset') }}</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -279,12 +180,23 @@ const callAndClose = (fn: () => void) => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-2 md:gap-3 flex-shrink-0">
|
||||
<div class="flex items-center gap-1 md:gap-2 flex-shrink-0">
|
||||
<button
|
||||
@click="props.onOpenSettings"
|
||||
class="p-1.5 md:p-2 hover:bg-slate-100 rounded-md transition-colors text-slate-400 flex items-center gap-1"
|
||||
:title="props.t('common.settings')"
|
||||
>
|
||||
<Settings class="w-3.5 h-3.5 md:w-4 h-4" />
|
||||
<span class="hidden md:inline text-[10px] md:text-xs font-bold">{{ props.t('common.settings') }}</span>
|
||||
</button>
|
||||
|
||||
<div class="h-4 w-[1px] bg-slate-200 mx-1"></div>
|
||||
|
||||
<button
|
||||
@click="emit('toggle-locale')"
|
||||
class="p-1.5 md:p-2 hover:bg-slate-100 rounded-md transition-colors text-slate-400 font-bold text-[10px] md:text-xs flex items-center gap-1"
|
||||
>
|
||||
<Globe class="w-3 h-3 md:w-3.5 h-3.5" /> {{ props.locale === 'zh' ? 'EN' : 'ZH' }}
|
||||
<Globe class="w-3.5 h-3.5 md:w-4 h-4" /> {{ props.locale === 'zh' ? 'EN' : 'ZH' }}
|
||||
</button>
|
||||
</div>
|
||||
</nav>
|
||||
@@ -297,7 +209,10 @@ const callAndClose = (fn: () => void) => {
|
||||
leave-from-class="transform translate-y-0 opacity-100"
|
||||
leave-to-class="transform -translate-y-4 opacity-0"
|
||||
>
|
||||
<div v-if="isToolsExpanded" class="md:hidden absolute top-[57px] left-0 right-0 bg-white/95 backdrop-blur-md border-b border-slate-200 shadow-xl z-40 py-4 px-4 flex flex-wrap gap-3 justify-center">
|
||||
<div
|
||||
v-if="isToolsExpanded"
|
||||
class="md:hidden absolute top-[57px] left-0 right-0 bg-white/95 backdrop-blur-md border-b border-slate-200 shadow-xl z-40 py-4 px-4 flex flex-wrap gap-3 justify-center"
|
||||
>
|
||||
<button @click="callAndClose(props.onFit)" class="toolbar-btn text-blue-500 hover:bg-blue-50 border-blue-100" :title="props.t('nav.fit')">
|
||||
<Focus class="w-4 h-4" />
|
||||
<span>{{ props.t('nav.fit') }}</span>
|
||||
@@ -329,10 +244,7 @@ const callAndClose = (fn: () => void) => {
|
||||
<span>{{ currentEdgeTypeLabel }}</span>
|
||||
<ChevronDown class="w-3 h-3 opacity-60" />
|
||||
</button>
|
||||
<div
|
||||
v-if="isEdgeTypeMenuOpen"
|
||||
class="absolute top-full left-0 mt-2 min-w-[180px] bg-white border border-slate-200 rounded-lg shadow-xl p-1 z-50"
|
||||
>
|
||||
<div v-if="isEdgeTypeMenuOpen" class="absolute top-full left-0 mt-2 min-w-[180px] bg-white border border-slate-200 rounded-lg shadow-xl p-1 z-50">
|
||||
<button
|
||||
v-for="opt in edgeTypeOptions"
|
||||
:key="opt.value"
|
||||
@@ -351,10 +263,7 @@ const callAndClose = (fn: () => void) => {
|
||||
<span>{{ currentBackgroundLabel }}</span>
|
||||
<ChevronDown class="w-3 h-3 opacity-60" />
|
||||
</button>
|
||||
<div
|
||||
v-if="isBackgroundMenuOpen"
|
||||
class="absolute top-full left-0 mt-2 min-w-[140px] bg-white border border-slate-200 rounded-lg shadow-xl p-1 z-50"
|
||||
>
|
||||
<div v-if="isBackgroundMenuOpen" class="absolute top-full left-0 mt-2 min-w-[140px] bg-white border border-slate-200 rounded-lg shadow-xl p-1 z-50">
|
||||
<button
|
||||
v-for="opt in backgroundOptions"
|
||||
:key="String(opt.value)"
|
||||
@@ -387,11 +296,6 @@ const callAndClose = (fn: () => void) => {
|
||||
<Download class="w-4 h-4" />
|
||||
<span>{{ props.t('nav.export') }}</span>
|
||||
</button>
|
||||
|
||||
<button @click="callAndClose(props.onOpenSettings)" class="toolbar-btn text-slate-600 hover:bg-slate-50 border-slate-100">
|
||||
<Settings class="w-4 h-4" />
|
||||
<span>{{ props.t('common.settings') }}</span>
|
||||
</button>
|
||||
</div>
|
||||
</Transition>
|
||||
</template>
|
||||
|
||||
Reference in New Issue
Block a user