最上位コンポーネントの構築
左上の折りたたみとブレッドクラムの実装
左側のメニューを更新して折りたたむ問題を解決します --- 属性default-active
折りたたむとアイコンが消えます: アイコンはスロットの外側に配置されます----要素のメニュー属性: 折りたたみ
プロジェクト\src\layout\index.vue
// 获取路由对象
import { useRoute } from 'vue-router';
let $route = useRoute()
<el-menu :default-active="$route.path" background-color="$base-menu-background" text-color="white"
active-text-color="yellowgreen">
<Menu :menuList="userStore.menuRoutes" />
</el-menu>
上部タブバーの静的コンポーネントのカプセル化: 左側のパンくずリストと右側の設定領域に分割
ブレッドクラムコンポーネント: project\src\layout\tabbar\breadcrumb\index.vue
<template>
<!-- 顶部左侧静态 -->
<el-icon style="margin-right: 10px;">
<Expand />
</el-icon>
<!-- 左侧面包屑 -->
<el-breadcrumb separator-icon="ArrowRight">
<el-breadcrumb-item>权限管理</el-breadcrumb-item>
<el-breadcrumb-item>用户管理</el-breadcrumb-item>
</el-breadcrumb>
</template>
<script setup lang="ts"></script>
<style scoped lang="scss"></style>
右の設定コンポーネント: project\src\layout\tabbar\setting\index.vue
<template>
<el-button size="small" icon="Refresh" circle />
<el-button size="small" icon="FullScreen" circle />
<el-button size="small" icon="Setting" circle />
<img src="/public/logo.png" style="width: 32px;height: 24px;margin: 0 10px;">
<!-- 下拉菜单 -->
<el-dropdown>
<span class="el-dropdown-link">
Admin
<el-icon class="el-icon--right">
<arrow-down />
</el-icon>
</span>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item>退出登入</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</template>
<script setup lang="ts"></script>
<style scoped lang="scss"></style>
プロジェクト\src\layout\tabbar\index.vue
<template>
<div class="tabbar">
<div class="tabbar_left">
<Breadcrumb />
</div>
<div class="tabbar_right">
<Setting />
</div>
</div>
</template>
<script setup lang="ts">
// 引入组件
import Breadcrumb from '@/layout/tabbar/breadcrumb/index.vue'
import Setting from '@/layout/tabbar/setting/index.vue'
</script>
<script lang="ts">
export default {
name: "Tabbar"
}
</script>
<style scoped lang="scss">
.tabbar {
width: 100%;
height: 100%;
display: flex;
justify-content: space-between;
.tabbar_left {
display: flex;
align-items: center;
margin-left: 20px;
}
.tabbar_right {
display: flex;
align-items: center;
}
}
</style>
メニュー折りたたみの実装
他のコンポーネントもfoldに応じて変更する必要があり、パンくずリストコンポーネント内で他のコンポーネントに渡すのが面倒なので、ウェアハウス内での折り畳みと展開を制御する変数foldを定義する方が適切です。
プロジェクト\src\store\modules\setting.ts
//小仓库:layout组件相关配置仓库
import { defineStore } from 'pinia'
const useLayOutSettingStore = defineStore('SettingStore', {
state: () => {
return {
fold: false, //用户控制菜单折叠还是收起控制
}
},
})
export default useLayOutSettingStore
ブレッドクラム コンポーネント: project\src\layout\tabbar\breadcrumb\index.vue (アイコンをクリックしてウェアハウス内のフォールドのステータスを切り替え、他のコンポーネントが読み取ったときに最新の状態を取得します)
// 获取小仓库中的控制折叠展开变量 fold
import useLayOutSettingStore from '@/store/modules/setting';
// 获取仓库
let layOutSettingStore = useLayOutSettingStore()
// 点击折叠展开图标的方法
const changeIcon = () => {
layOutSettingStore.fold = !layOutSettingStore.fold
}
<el-icon style="margin-right: 10px;" @click="changeIcon">
<!-- vue提供的动态展示组件 -->
<component :is="layOutSettingStore.fold ? 'Expand' : 'Fold'">
</component>
</el-icon>
レイアウト コンポーネントは、fold 変数を読み取ります。
プロジェクト\src\layout\index.vue
// 获取小仓库中的控制折叠展开变量 fold
import useLayOutSettingStore from '@/store/modules/setting';
// 获取仓库
let layOutSettingStore = useLayOutSettingStore()
// :collapse="layOutSettingStore.fold ? true : false"
<el-menu :collapse="layOutSettingStore.fold ? true : false" :default-active="$route.path"
background-color="$base-menu-background" active-text-color="yellowgreen">
<Menu :menuList="userStore.menuRoutes" />
</el-menu>
//顶部导航和右侧内容展示区域添加动态类fold
:class="{ fold: layOutSettingStore.fold ? true : false }"
折りたたみに問題があります。折りたたみ時にバグがまだあります。
上部のパンくずリストの動的表示
ルートマッチング情報を取得することで実現
レイアウトコンポーネントはパンくずリストを表示する必要はなく、ホームページを直接表示するため、レイアウトルート上のメタ情報のタイトルとアイコンを削除し、パンくずリストの描画時にv-show判定を行います。
ブレッドクラムをクリックすると、 -- 要素が達成できる -- へのルーティング ジャンプを実行することもできます。
プロジェクト\src\layout\tabbar\breadcrumb\index.vue
// 获取路由
import { useRoute } from 'vue-router';
let $route = useRoute()
<!-- 左侧面包屑 -->
<el-breadcrumb separator-icon="ArrowRight">
<el-breadcrumb-item v-for="(item, index) in $route.matched" :key="index" v-show="item.meta.title" :to="item.path">
<!-- 渲染图标 -->
<el-icon style="margin: 0 2px;">
<component :is="item.meta.icon"></component>
</el-icon>
<!-- 渲染面包屑标题 -->
<span>{
{ item.meta.title }}</span>
</el-breadcrumb-item>
</el-breadcrumb>
上部タブバーの右側での機能実現
リフレッシュ機能の実装
簡単に言うと、ルーティング コンポーネントが破棄されて再構築され、データを取得するためにリクエストが再送信されます。
これには、トップ ナビゲーションとコンテンツ表示領域の間の通信が含まれます。更新された変数は小さなウェアハウスに配置されます。
小さな倉庫:
refresh: false,//控制刷新效果
設定ファイル:project\src\layout\tabbar\setting\index.vue
// 获取仓库中的 刷新变量 refresh
import useLayOutSettingStore from '@/store/modules/setting'
let layOutSettingStore = useLayOutSettingStore()
// 点击刷新的回调
const refreshChange = () => {
layOutSettingStore.refresh = !layOutSettingStore.refresh
}
//刷新按钮
<el-button size="small" icon="Refresh" circle @click="refreshChange" />
Main コンポーネントは更新の変更をリッスンし、変更が発生した場合はリクエストを再送信します。
プロジェクト\src\layout\main\index.vue
import { watch, ref, nextTick } from 'vue'
// 获取仓库中的 刷新变量 refresh
import useLayOutSettingStore from '@/store/modules/setting'
let layOutSettingStore = useLayOutSettingStore()
// 控制组件销毁与创建
let flag = ref(true)
// 监听仓库中的refresh,如果发生变化说明用户点击刷新按钮
watch(() => layOutSettingStore.refresh, () => {
//refresh变化,则销毁组件
flag.value = false
//等待组件完毕重新加载
nextTick(() => {
flag.value = true
})
})
<component :is="Component" v-if="flag" />
フルスクリーン機能実現:dom操作
// 点击实现全屏切换
const fullScreen = () => {
//DOM对象的一个属性:可以用来判断当前是不是全屏模式[全屏:true,不是全屏:false]
let full = document.fullscreenElement;
//切换为全屏模式
if (!full) {
//文档根节点的方法requestFullscreen,实现全屏模式
document.documentElement.requestFullscreen();
} else {
//变为不是全屏模式->退出全屏模式
document.exitFullscreen();
}
}
<el-button size="small" icon="FullScreen" circle @click="fullScreen" />
ビジネスからログアウトする
トークンの理解
ログインが成功すると、ホームページ コンポーネントがハングアップし、トークンを取得して小さな倉庫内のサーバー上でユーザーの関連データを見つけ、それを小さな倉庫に保存するようにユーザーに通知されます。
ホームコンポーネント: project\src\views\home\index.vue (後でルーティング ガードでリクエストを送信できます)
import { onMounted } from 'vue';
//引入用户相关的仓库,获取当前用户的头像、昵称
import useUserStore from '@/store/modules/user';
//获取存储用户信息的仓库对象
let userStore = useUserStore();
//首页挂在完毕通知小仓库发请求拿数据
onMounted(() => {
userStore.getUserInfo()
})
ユーザーの小規模ウェアハウス: project\src\store\modules\user.ts
//记得到type中进行类型定义
username: '',
avatar: ''
// 拿token向服务器请求用户数据
async getUserInfo() {
// 获取用户信息进行存储【头像,用户名】
let res = await reqUserInfo()
if (res.code == 200) {
this.username = res.data.checkUser.username
this.avatar = res.data.checkUser.avatar
} else {
}
}
ユーザーには n 個のリクエストがあるため、トークンを運び、リクエスト インターセプターに入れる方法
プロジェクト\src\utils\request.ts
// 引入用户相关的仓库
import useUserStore from '@/store/modules/user'
// 第二步:request实例添加请求和响应拦截器
request.interceptors.request.use((config) => {
// 获取用户小仓库,拿到登入成功的token数据携带给服务器
let userStore = useUserStore()
if (userStore.token) {
config.headers.token = userStore.token
}
// config 配置对象,有headers属性请求头,经常给服务器端携带公共参数
// 返回配置对象
return config;
})
ユーザー情報を取得したら、タブバーコンポーネントの設定コンポーネントに表示します
プロジェクト\src\layout\tabbar\setting\index.vue
倉庫を入手して展示する
終了およびログイン機能の実装: クリック後にログインページに戻り、ユーザー情報とトークンをクリアします
ログアウトしてログインするには、トークンが無効であることをサーバーに伝えるリクエストを送信する必要があります。次回サーバーにログインすると、新しいトークンが返されます。
プロジェクト\src\layout\tabbar\setting\index.vue
// 退出登入需要进行路由跳转
import { useRouter } from 'vue-router';
// 获取路由器对象
let $router = useRouter()
//退出登录点击回调
const logout = async () => {
//第一件事情:需要向服务器发请求[退出登录接口]******暂时没有
//第二件事情:仓库当中关于用于相关的数据清空[token|username|avatar]
//第三件事情:跳转到登录页面
await userStore.userLogout();
//跳转到登录页面
$router.push({ path: '/login' });
}
<el-dropdown-item @click="logout">退出登入</el-dropdown-item>
ユーザーの小規模ウェアハウス: project\src\store\modules\user.ts
//退出登入
userLogout() {
//目前没有退出接口
//清除数据
this.token = ''
this.username = ''
this.avatar = ''
localStorage.removeItem('TOKEN')
}
まだ解決すべき問題がいくつかあります。ログインに成功した後はログインにジャンプすることができず、ユーザー情報を永続的に保存する必要があります。
ルーティング認証とプログレスバーの実現
プログレスバーは、グローバル ルーティング ガード (事前、事後) を使用して実装できます。
ルーティング コンポーネントがハングアップした後、表示するユーザー情報の取得をリクエストしますが、これは多数のコンポーネントには適していません。これはグローバル ルーティング ガードを通じて実現でき、ルーティング コンポーネントがハングアップしたときにリクエストを送信するだけで十分です。ルーティングジャンプ
ルーティング認証ファイルの作成
注: コンポーネントの外部で小さなウェアハウスを使用すると、エラーが報告されます (同期ステートメントはウェアハウスを取得できません)。また、小さなウェアハウスから取得する必要があるデータには、まず大きなウェアハウスが必要です。
要望: 元々各コンポーネントでユーザー情報を取得するリクエストを送っていたが、フロントルーティングガードにユーザー名があるかどうかを判断できるようになった、あれば他のルーティングコンポーネントに解放できる、ない場合はまずユーザーを取得する情報を提供してからリリースする
これをルーティング ガードに置くと、ホームページ上でユーザー情報を取得するリクエストを送信するときに他のルーティング コンポーネントにジャンプするときのデータ損失の問題も解決できます。ルーティング ガードにユーザー情報がない場合でも、リクエストを送信して取得する
//获取用户相关的小仓库内部token数据,去判断用户是否登录成功
import useUserStore from './store/modules/user'
import pinia from './store'
const userStore = useUserStore(pinia)
project\src\permisstion.ts
//路由鉴权:鉴权,项目当中路由能不能被的权限的设置(某一个路由什么条件下可以访问、什么条件下不可以访问)
import router from '@/router'
import setting from '@/settings'
//@ts-ignore
import nprogress from 'nprogress'
//引入进度条样式
import 'nprogress/nprogress.css'
nprogress.configure({ showSpinner: false })
//获取用户相关的小仓库内部token数据,去判断用户是否登录成功
import useUserStore from './store/modules/user'
import pinia from './store'
const userStore = useUserStore(pinia)
//全局守卫:项目当中任意路由切换都会触发的钩子
//全局前置守卫
router.beforeEach(async (to: any, from: any, next: any) => {
//to:你将要访问那个路由
//from:你从来个路由而来
//next:路由的放行函数
nprogress.start()
//获取token,去判断用户登录、还是未登录
const token = userStore.token
//获取用户名字
const username = userStore.username
//用户登录判断
if (token) {
//登录成功,访问login,不能访问,指向首页
if (to.path == '/login') {
next({ path: '/' })
} else {
//登录成功访问其余六个路由(登录排除)
//有用户信息
if (username) {
//放行
next()
} else {
//如果没有用户信息,在守卫这里发请求获取到了用户信息再放行
try {
//获取用户信息
await userStore.getUserInfo()
//放行
//万一:刷新的时候是异步路由,有可能获取到用户信息、异步路由还没有加载完毕,出现空白的效果
next({ ...to })
} catch (error) {
//token过期:获取不到用户信息了
//用户手动修改本地存储token
//退出登录->用户相关的数据清空
await userStore.userLogout()
next({ path: '/login' })
}
}
}
} else {
//用户未登录判断
if (to.path == '/login') {
next()
} else {
next({ path: '/login' })
}
}
})
//全局后置守卫
router.afterEach((to: any, from: any) => {
document.title = `${setting.title} - ${to.meta.title}`
nprogress.done()
})
//第一个问题:任意路由切换实现进度条业务 ---nprogress
//第二个问题:路由鉴权(路由组件访问权限的设置)
//全部路由组件:登录|404|任意路由|首页|数据大屏|权限管理(三个子路由)|商品管理(四个子路由)
//用户未登录:可以访问login,其余六个路由不能访问(指向login)
//用户登录成功:不可以访问login[指向首页],其余的路由可以访问
メインで紹介しました
//引入路由鉴权文件
import './permisstion'