Files
Yi.Framework/Yi.Ai.Vue3/src/pages/console/index.vue
2026-01-05 15:44:48 +08:00

339 lines
7.5 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script setup lang="ts">
import { Expand, Fold } from '@element-plus/icons-vue';
import { computed, ref } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import { checkPagePermission } from '@/config/permission.ts';
import { useUserStore } from '@/stores';
const route = useRoute();
const router = useRouter();
// 从 URL 中提取邀请码参数
const inviteCodeFromUrl = computed(() => {
return route.query.inviteCode as string | undefined;
});
// 控制侧边栏折叠状态
const isCollapsed = ref(false);
const userStore = useUserStore();
const userName = userStore.userInfo?.user?.userName;
const hasChannelPermission = checkPagePermission('/console/channel', userName);
const hasSystemStatisticsPermission = checkPagePermission('/console/system-statistics', userName);
// 菜单项配置
// 基础菜单项
const baseNavItems = [
{ name: 'user', label: '用户信息', icon: 'User', path: '/console/user' },
{ name: 'apikey', label: 'API密钥', icon: 'Key', path: '/console/apikey' },
{ name: 'recharge-log', label: '充值记录', icon: 'Document', path: '/console/recharge-log' },
{ name: 'usage', label: '用量统计', icon: 'Histogram', path: '/console/usage' },
{ name: 'premium', label: '尊享服务', icon: 'ColdDrink', path: '/console/premium' },
{ name: 'daily-task', label: '每日任务(限时)', icon: 'Trophy', path: '/console/daily-task' },
{ name: 'invite', label: '每周邀请(限时)', icon: 'Present', path: '/console/invite' },
{ name: 'activation', label: '激活码兑换', icon: 'MagicStick', path: '/console/activation' },
];
// 根据权限动态添加菜单项
let navItems = [...baseNavItems];
if (hasChannelPermission) {
navItems.push({ name: 'channel', label: '渠道商管理', icon: 'Setting', path: '/console/channel' });
}
if (hasSystemStatisticsPermission) {
navItems.push({ name: 'system-statistics', label: '系统统计', icon: 'DataAnalysis', path: '/console/system-statistics' });
}
// 当前激活的菜单
const activeNav = computed(() => {
const path = route.path;
const item = navItems.find(item => item.path === path);
return item?.name || 'user';
});
// 切换菜单
function handleNavSelect(menu: typeof navItems[0]) {
router.push(menu.path);
}
// 在移动端默认折叠侧边栏
const isMobile = ref(false);
function checkIsMobile() {
isMobile.value = window.innerWidth <= 768;
// 移动端默认折叠
if (isMobile.value) {
isCollapsed.value = true;
}
}
// 初始检查和监听窗口大小变化
checkIsMobile();
window.addEventListener('resize', checkIsMobile);
</script>
<template>
<div class="console-page" :class="{ 'is-collapsed': isCollapsed }">
<!-- 侧边栏导航 -->
<div class="nav-sidebar" :class="{ 'is-collapsed': isCollapsed }">
<div class="nav-header">
<h2 v-show="!isCollapsed" class="nav-title">
控制台
</h2>
<div class="collapse-btn" @click="isCollapsed = !isCollapsed">
<el-icon>
<Expand v-if="isCollapsed" />
<Fold v-else />
</el-icon>
</div>
</div>
<el-menu
:default-active="activeNav"
class="nav-menu"
:collapse="isCollapsed"
:collapse-transition="false"
>
<el-menu-item
v-for="item in navItems"
:key="item.name"
:index="item.name"
@click="handleNavSelect(item)"
>
<el-icon>
<component :is="item.icon" />
</el-icon>
<template #title>
<span>{{ item.label }}</span>
</template>
</el-menu-item>
</el-menu>
</div>
<!-- 折叠遮罩层移动端 -->
<div
v-if="isMobile && !isCollapsed"
class="sidebar-overlay"
@click="isCollapsed = true"
/>
<!-- 主内容区 -->
<div class="content-main">
<!-- <div v-if="isMobile" class="content-header"> -->
<!-- <div class="mobile-toggle" @click="isCollapsed = false"> -->
<!-- <el-icon><Expand /></el-icon> -->
<!-- <span>控制台</span> -->
<!-- </div> -->
<!-- </div> -->
<router-view v-slot="{ Component }">
<keep-alive>
<component :is="Component" :external-invite-code="inviteCodeFromUrl" />
</keep-alive>
</router-view>
</div>
</div>
</template>
<style scoped lang="scss">
.console-page {
display: flex;
width: 100%;
overflow: auto;
&.is-collapsed {
.content-main {
margin-left: 0;
}
}
}
.nav-sidebar {
width: 240px;
height: 100%;
border-right: var(--header-border);
flex-shrink: 0;
display: flex;
flex-direction: column;
transition: all 0.3s ease;
position: relative;
z-index: 1001;
&.is-collapsed {
width: 64px;
.nav-title {
opacity: 0;
width: 0;
overflow: hidden;
}
}
}
.nav-header {
padding: 20px;
//border-bottom:var(--header-border);
display: flex;
align-items: center;
justify-content: space-between;
min-height: 70px;
box-sizing: border-box;
background: #FFF;
}
.nav-title {
margin: 0;
font-size: 20px;
font-weight: 600;
color: var(--el-text-color-primary);
transition: all 0.3s ease;
white-space: nowrap;
}
.collapse-btn {
display: flex;
align-items: center;
justify-content: center;
width: 32px;
height: 32px;
border-radius: 4px;
cursor: pointer;
color: var(--el-text-color-secondary);
transition: all 0.2s;
flex-shrink: 0;
&:hover {
background-color: var(--el-fill-color-light);
color: var(--el-text-color-primary);
}
.el-icon {
font-size: 18px;
}
}
.nav-menu {
flex: 1;
overflow-y: auto;
overflow-x: hidden;
:deep(.el-menu-item) {
&.is-active {
background-color: var(--el-color-primary-light-9);
color: var(--el-color-primary);
}
span {
transition: opacity 0.3s ease;
}
}
&:not(.el-menu--collapse) {
:deep(.el-menu-item) {
justify-content: flex-start;
}
}
}
.sidebar-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5);
z-index: 1000;
transition: opacity 0.3s ease;
}
.content-main {
flex: 1;
padding: 10px;
overflow-y: auto;
min-width: 0;
transition: margin-left 0.3s ease;
position: relative;
}
.content-header {
display: none;
margin-bottom: 20px;
padding-bottom: 10px;
border-bottom: 1px solid var(--el-border-color);
}
.mobile-toggle {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 8px 16px;
border-radius: 4px;
background-color: var(--el-fill-color-light);
cursor: pointer;
color: var(--el-text-color-primary);
transition: background-color 0.2s;
&:hover {
background-color: var(--el-fill-color);
}
.el-icon {
font-size: 18px;
}
}
// 移动端适配
@media (max-width: 768px) {
.console-page {
position: relative;
}
.nav-sidebar {
position: fixed;
left: 0;
top: 0;
bottom: 0;
z-index: 1001;
transform: translateX(-100%);
transition: transform 0.3s ease;
&.is-collapsed {
transform: translateX(-100%);
width: 240px;
}
&:not(.is-collapsed) {
transform: translateX(0);
}
.collapse-btn {
display: none;
}
}
.content-header {
display: block;
}
.content-main {
padding: 15px;
margin-left: 0 !important;
}
}
// 平板适配
@media (min-width: 769px) and (max-width: 1024px) {
.nav-sidebar {
width: 200px;
&.is-collapsed {
width: 64px;
}
}
.content-main {
padding: 15px;
}
}
</style>