feat: 接入两端用户名登录和独立学习空间 (#2)

This commit is contained in:
ila
2026-09-10 17:39:57 +08:00
parent 596543b0e2
commit 2c8e9b3ca0
82 changed files with 14148 additions and 30 deletions
+18
View File
@@ -0,0 +1,18 @@
<template>
<div class="basic-layout">
<slot name="wrapper" />
</div>
</template>
<script>
export default {
name: 'BasicLayout'
}
</script>
<style lang="scss" scoped>
.basic-layout{
padding: 10px;
box-sizing: border-box;
}
</style>
+3
View File
@@ -0,0 +1,3 @@
<template><section class="app-main"><router-view :key="$store.state.generation" /></section></template>
<script>export default { name: 'AppMain' }</script>
<style scoped>.app-main { min-height: calc(100vh - 56px); width: 100%; position: relative; overflow: hidden; background-color: #f0f2f5; }</style>
+51
View File
@@ -0,0 +1,51 @@
<template>
<div :class="classObj" class="app-wrapper">
<div v-if="device === 'mobile' && sidebar.opened" class="drawer-bg" @click="handleClickOutside" />
<aside class="sidebar-container">
<div class="brand">{{ sidebar.opened ? 'LexGo 管理' : 'LG' }}</div>
<el-menu default-active="/accounts" :collapse="!sidebar.opened" router>
<el-menu-item index="/accounts"><span>账号管理</span></el-menu-item>
</el-menu>
</aside>
<div class="main-container">
<header class="navbar">
<button class="menu-toggle" aria-label="切换导航" @click="$store.dispatch('app/toggleSideBar')"><hamburger :is-active="sidebar.opened" /></button>
<span>账号管理</span>
<div class="right-menu"><span>{{ $store.state.user?.username }}</span><el-button @click="logout">退出登录</el-button></div>
</header>
<app-main />
</div>
</div>
</template>
<script>
import { mapState } from 'vuex'
import ResizeMixin from './mixin/ResizeHandler'
import Hamburger from '../components/Hamburger/index.vue'
import AppMain from './components/AppMain.vue'
import { session } from '../store'
import { ElMessage } from 'element-plus'
export default {
name: 'MainLayout', components: { Hamburger, AppMain }, mixins: [ResizeMixin],
computed: {
...mapState({ sidebar: state => state.app.sidebar, device: state => state.app.device }),
classObj() { return { hideSidebar: !this.sidebar.opened, openSidebar: this.sidebar.opened, withoutAnimation: this.sidebar.withoutAnimation, mobile: this.device === 'mobile' } }
},
watch: { '$store.state.user'(user) { if (!user) this.$router.replace('/login') } },
methods: {
handleClickOutside() { this.$store.dispatch('app/closeSideBar', { withoutAnimation: false }) },
async logout() {
try { await session.logout() } catch { ElMessage.warning('本地已退出;服务器尚未确认退出,原会话可能仍有效。') }
this.$router.replace('/login')
}
}
}
</script>
<style lang="scss" scoped>
@use '@/styles/mixin.scss' as *;
.app-wrapper { @include clearfix; position: relative; height: 100%; width: 100%; &.mobile.openSidebar { position: fixed; top: 0; } }
.drawer-bg { background: #000; opacity: .3; width: 100%; top: 0; height: 100%; position: absolute; z-index: 999; }
.navbar { height: 56px; display: flex; align-items: center; gap: 12px; background: white; padding-right: 16px; box-shadow: 0 1px 4px #00152914; }
.right-menu { margin-left: auto; display: flex; align-items: center; gap: 12px; }
.brand { height: 64px; display: flex; align-items: center; justify-content: center; font-size: 18px; color: white; font-weight: 600; }
.menu-toggle { background: transparent; border: 0; cursor: pointer; }
</style>
+45
View File
@@ -0,0 +1,45 @@
import store from '@/store'
const { body } = document
const WIDTH = 992 // refer to Bootstrap's responsive design
export default {
watch: {
$route() {
if (this.device === 'mobile' && this.sidebar.opened) {
store.dispatch('app/closeSideBar', { withoutAnimation: false })
}
}
},
beforeMount() {
window.addEventListener('resize', this.$_resizeHandler)
},
beforeUnmount() {
window.removeEventListener('resize', this.$_resizeHandler)
},
mounted() {
const isMobile = this.$_isMobile()
if (isMobile) {
store.dispatch('app/toggleDevice', 'mobile')
store.dispatch('app/closeSideBar', { withoutAnimation: true })
}
},
methods: {
// use $_ for mixins properties
// https://vuejs.org/v2/style-guide/index.html#Private-property-names-essential
$_isMobile() {
const rect = body.getBoundingClientRect()
return rect.width - 1 < WIDTH
},
$_resizeHandler() {
if (!document.hidden) {
const isMobile = this.$_isMobile()
store.dispatch('app/toggleDevice', isMobile ? 'mobile' : 'desktop')
if (isMobile) {
store.dispatch('app/closeSideBar', { withoutAnimation: true })
}
}
}
}
}