搭建后台管理系统的思路
从零开始搭建后台管理系统
当然,这是一个简易版本的,你可以在这两个基础上加以改造
搭建后台管理系统最基础的是什么呢?个人的体会是整体的基础框架,这个是指最基础的框架,比如根 router-view
, 侧边栏以及侧边栏的router-view
,以及顶部栏,等基础布局的控制。
root
首先 App.vue 只有展示 rouer-view
, 这个就是 root, 所谓的根。
<template>
<router-view></router-view>
</template>
ok 有了根之后,我们需要有一个 layout 页面,这个页面来承载我们的侧边栏,顶部栏
layout 页面他是两栏布局的,一栏是我们的侧边导航栏,
侧边栏
如何完成这个两栏布局
可以使用 float 可以使用弹性布局 display: flex 也可以使用定位
侧边导航栏,可能我们需要来研究 element-ui 的组件 NavMenu 导航菜单
侧边导航栏需要我们路由的一些信息,比如路由对应的组件,就像 router-link 对应的 router-view
如果菜单是二级菜单,三级菜单,需要怎么处理
如果需要折叠菜单,需要怎么处理,这里就需要阅读 折叠菜单组件
也就是说侧边菜单来其实就是一个个的 router-link
然后扩展菜单项是否有 icon 小图标,是否有标题存在,那么路由就需要设置 meta 属性了
顶部栏
接下来就是顶部栏,顶部导航栏有什么呢?
面包屑 消息通知 下拉菜单 关闭展开侧边栏按钮
面包屑
需要注意什么呢?需要注意是否需要点击跳转的,定位到那一级菜单的问题
需要研究 Breadcrumb 面包屑
关闭展开侧边栏按钮
需要使用 vuex 来存储打开与否的这个状态值,通过 vuex 来更改状态
AppMain.vue
<template>
<section class="app-main">
<!-- 内部应该显示子路由页面信息 -->
<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>
用来展示侧边栏的 router-link 对应的 router-view
所以,又回到 layout 页面整体框架如下:
<template>
<div class="app-wrapper">
<!-- 侧边栏 -->
<side-bar class="sidebar-container"></side-bar>
<!-- 内容容器 -->
<div class="main-container">
<!-- 顶部导航栏 -->
<nav-bar />
<!-- 内容区 -->
<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 处理 响应处理 请求处理
import axios from 'axios';
import { Message, Msgbox } from 'element3';
import store from 'store/index.js';
// 创建 axios 实例
const service = axios.create({
// 在请求地址前面加上 baseURL
baseURL: import.meta.env.VITE_BASE_API,
// 当前发送跨域请求时携带 cookie
withCredentials: true,
timeout: 5000
});
// 请求拦截
service.interceptors.request.use(
(config) => {
// 指定请求令牌
// if (store.getters.token) {
// // 自定义令牌的字段名为X-Token,根据咱们后台再做修改
// config.headers["X-Token"] = store.getters.token;
// }
config.headers["X-Token"] = "my token";
return config;
},
(error) => {
// 请求错误的统一处理
console.log(error); // for debug
return Promise.reject(error);
}
);
// 响应拦截器
service.interceptors.response.use(
/**
* If you want to get http information such as headers or status
* Please return response => response
*/
/**
* 通过判断状态码统一处理响应,根据情况修改
* 同时也可以通过HTTP状态码判断请求结果
*/
(response) => {
const res = response.data;
// 如果状态码不是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 这样的插件
就需要研究官网文档了 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>
当然,可以在 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 用于提取颜色值,字体大小值,字体权重值等 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")
}
}
});