搭建后臺管理系統(tǒng)的思路

從零開始搭建后臺管理系統(tǒng)
當(dāng)然,這是一個簡易版本的,你可以在這兩個基礎(chǔ)上加以改造
搭建后臺管理系統(tǒng)最基礎(chǔ)的是什么呢?個人的體會是整體的基礎(chǔ)框架,這個是指最基礎(chǔ)的框架,比如根 router-view, 側(cè)邊欄以及側(cè)邊欄的router-view,以及頂部欄,等基礎(chǔ)布局的控制。
root
首先 App.vue 只有展示 rouer-view, 這個就是 root, 所謂的根。
<template>
<router-view></router-view>
</template>
ok 有了根之后,我們需要有一個 layout 頁面,這個頁面來承載我們的側(cè)邊欄,頂部欄
layout 頁面他是兩欄布局的,一欄是我們的側(cè)邊導(dǎo)航欄,
側(cè)邊欄
如何完成這個兩欄布局
可以使用 float 可以使用彈性布局 display: flex 也可以使用定位
側(cè)邊導(dǎo)航欄,可能我們需要來研究 element-ui 的組件 NavMenu 導(dǎo)航菜單
側(cè)邊導(dǎo)航欄需要我們路由的一些信息,比如路由對應(yīng)的組件,就像 router-link 對應(yīng)的 router-view
如果菜單是二級菜單,三級菜單,需要怎么處理
如果需要折疊菜單,需要怎么處理,這里就需要閱讀 折疊菜單組件
也就是說側(cè)邊菜單來其實就是一個個的 router-link
然后擴展菜單項是否有 icon 小圖標,是否有標題存在,那么路由就需要設(shè)置 meta 屬性了
頂部欄
接下來就是頂部欄,頂部導(dǎo)航欄有什么呢?
面包屑 消息通知 下拉菜單 關(guān)閉展開側(cè)邊欄按鈕
面包屑
需要注意什么呢?需要注意是否需要點擊跳轉(zhuǎn)的,定位到那一級菜單的問題
需要研究 Breadcrumb 面包屑
關(guān)閉展開側(cè)邊欄按鈕
需要使用 vuex 來存儲打開與否的這個狀態(tài)值,通過 vuex 來更改狀態(tài)
AppMain.vue
<template>
<section class="app-main">
<!-- 內(nèi)部應(yīng)該顯示子路由頁面信息 -->
<router-view v-slot="{ Component }">
<component :is="Component" />
</router-view>
</section>
</template>
<script>
import { defineComponent } from "vue";
export default defineComponent({
name: "AppMain",
});
</script>
<style lang="scss" scoped>
.app-main {
/*50 = navbar */
min-height: calc(100vh - 50px);
width: 100%;
position: relative;
overflow: hidden;
}
</style>
用來展示側(cè)邊欄的 router-link 對應(yīng)的 router-view
所以,又回到 layout 頁面整體框架如下:
<template>
<div class="app-wrapper">
<!-- 側(cè)邊欄 -->
<side-bar class="sidebar-container"></side-bar>
<!-- 內(nèi)容容器 -->
<div class="main-container">
<!-- 頂部導(dǎo)航欄 -->
<nav-bar />
<!-- 內(nèi)容區(qū) -->
<app-main />
</div>
</div>
</template>
<script setup>
import AppMain from "layout/components/AppMain.vue";
import NavBar from "layout/components/NavBar.vue";
import SideBar from "layout/components/Sidebar/index.vue";
</script>
<style lang="scss" scoped>
@import "styles/mixin.scss";
.app-wrapper {
@include clearfix;
position: relative;
height: 100%;
width: 100%;
}
</style>
請求封裝
token 處理 響應(yīng)處理 請求處理
import axios from 'axios';
import { Message, Msgbox } from 'element3';
import store from 'store/index.js';
// 創(chuàng)建 axios 實例
const service = axios.create({
// 在請求地址前面加上 baseURL
baseURL: import.meta.env.VITE_BASE_API,
// 當(dāng)前發(fā)送跨域請求時攜帶 cookie
withCredentials: true,
timeout: 5000
});
// 請求攔截
service.interceptors.request.use(
(config) => {
// 指定請求令牌
// if (store.getters.token) {
// // 自定義令牌的字段名為X-Token,根據(jù)咱們后臺再做修改
// config.headers["X-Token"] = store.getters.token;
// }
config.headers["X-Token"] = "my token";
return config;
},
(error) => {
// 請求錯誤的統(tǒng)一處理
console.log(error); // for debug
return Promise.reject(error);
}
);
// 響應(yīng)攔截器
service.interceptors.response.use(
/**
* If you want to get http information such as headers or status
* Please return response => response
*/
/**
* 通過判斷狀態(tài)碼統(tǒng)一處理響應(yīng),根據(jù)情況修改
* 同時也可以通過HTTP狀態(tài)碼判斷請求結(jié)果
*/
(response) => {
const res = response.data;
// 如果狀態(tài)碼不是20000則認為有錯誤
if (res.code !== 20000) {
Message.error({
message: res.message || "Error",
duration: 5 * 1000,
});
// 50008: 非法令牌; 50012: 其他客戶端已登入; 50014: 令牌過期;
if (res.code === 50008 || res.code === 50012 || res.code === 50014) {
// 重新登錄
Msgbox.confirm("您已登出, 請重新登錄", "確認", {
confirmButtonText: "重新登錄",
cancelButtonText: "取消",
type: "warning",
}).then(() => {
store.dispatch("user/resetToken").then(() => {
location.reload();
});
});
}
return Promise.reject(new Error(res.message || "Error"));
} else {
return res;
}
},
(error) => {
console.log("err" + error); // for debug
Message({
message: error.message,
type: "error",
duration: 5 * 1000,
});
return Promise.reject(error);
}
);
export default service;
多語言
多語言會用到 vue-i18n 這樣的插件
就需要研究官網(wǎng)文檔了 vue-i18n
有一個 vite 多語言插件
intlify/vite-plugin-vue-i18n
vite.config.js 配置
import path from 'path'
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import vueI18n from '@intlify/vite-plugin-vue-i18n'
export default defineConfig({
plugins: [
vue(), // you need to install `@vitejs/plugin-vue`
vueI18n({
// if you want to use Vue I18n Legacy API, you need to set `compositionOnly: false`
// compositionOnly: false,
// you need to set i18n resource including paths !
include: path.resolve(__dirname, './path/to/src/locales/**')
})
]
})
模板這樣使用多語言
<template>
<form>
<label>{{ t('language') }}</label>
<select v-model="locale">
<option value="en">en</option>
<option value="ja">ja</option>
</select>
</form>
<p>{{ t('hello') }}</p>
</template>
<script>
import { useI18n } from 'vue-i18n'
export default {
name: 'App',
setup() {
const { locale, t } = useI18n({
inheritLocale: true
})
return { locale, t }
}
}
</script>
<i18n>
{
"en": {
"language": "Language",
"hello": "hello, world!"
},
"ja": {
"language": "言語",
"hello": "こんにちは、世界!"
}
}
</i18n>
當(dāng)然,可以在 main.js 引入多語言
import { createApp } from 'vue'
import { createI18n } from 'vue-i18n'
/*
* The i18n resources in the path specified in the plugin `include` option can be read
* as vue-i18n optimized locale messages using the import syntax
*/
import en from './src/locales/en.json'
import ja from './src/locales/ja.yaml'
import fr from './src/locales/fr.json5'
const i18n = createI18n({
locale: 'en',
messages: {
en,
ja,
fr
}
})
const app = createApp()
app.use(i18n).mount('#app)
element3 組件
// 完整引入
import element3 from "element3";
import "element3/lib/theme-chalk/index.css";
export default function (app) {
// 完整引入
app.use(element3);
};
我們要如何組建自己的樣式目錄
var.scss 用于提取顏色值,字體大小值,字體權(quán)重值等 mixin.scss 寫一些公用的樣式
目錄的重定向問題
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import vueJsx from '@vitejs/plugin-vue-jsx';
import { viteMockServe } from 'vite-plugin-mock';
import path from 'path';
import vueI18n from '@intlify/vite-plugin-vue-i18n'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
vue(),
vueJsx(),
viteMockServe({ supportTs: false }),
vueI18n({
// if you want to use Vue I18n Legacy API, you need to set `compositionOnly: false`
// compositionOnly: false,
// you need to set i18n resource including paths !
include: path.resolve(__dirname, './src/locales/**')
})
],
resolve: {
alias: {
"@": path.resolve(__dirname, "src"),
"comps": path.resolve(__dirname, "src/components"),
"api": path.resolve(__dirname, "src/api"),
"views": path.resolve(__dirname, "src/views"),
"styles": path.resolve(__dirname, "src/styles"),
"locales": path.resolve(__dirname, "src/locales"),
"layout": path.resolve(__dirname, "src/layout"),
"utils": path.resolve(__dirname, "src/utils"),
"dirs": path.resolve(__dirname, "src/dirs"),
"plugins": path.resolve(__dirname, "src/plugins"),
"config": path.resolve(__dirname, "src/config"),
"router": path.resolve(__dirname, "src/router"),
"store": path.resolve(__dirname, "src/store"),
"model": path.resolve(__dirname, "src/model")
}
}
});
