创建项目
此处不做详细描述,请参考:项目创建
添加环境变量
新建.env.development 文件
VUE_APP_BASE_API = http
://localhost:3000
新建 .env.production 文件(根据项目需求,选择性创建)
VUE_APP_BASE_API = https
://api.xxx.xxx
安装基础依赖
yarn add element-ui axios moment
全局过滤器
新增 src/filters/index.js 文件
import moment from "moment";
export function dateFormat(value) {
if (!value) {
return "";
}
moment.locale("zh-cn");
return moment(value).format("LL");
}
export function thousandFormat(num) {
const result = (+num || 0).toString().replace(/^-?\d+/g, m => m.replace(/(?=(?!\b)(\d{3})+$)/g, ","));
return result + "元";
}
注册依赖
在 src/main.js 中
.
.
import
ElementUI
from
"element-ui";
import "element-ui/lib/theme-chalk/index.css";
Vue.config.productionTip = false;
Vue.use(ElementUI);
import * as filters from "./filters";
Object.keys(filters).forEach((key) => {
Vue.filter(key, filters[key]);
});
.
.
封装 axios
新增 src/utils/request.js 文件
import axios from "axios";
import { MessageBox, Message } from "element-ui";
import store from "@/store";
import { getToken } from "@/utils/auth";
const service = axios.create({
baseURL: process.env.VUE_APP_BASE_API,
timeout: 5000,
});
service.interceptors.request.use(
(config) => {
if (store.getters.token) {
config.headers["token"] = getToken();
}
return config;
},
(error) => {
console.log(error);
return Promise.reject(error);
}
);
service.interceptors.response.use(
(response) => {
const res = response.data;
if (res.code !== 20000) {
Message({
message: res.message || "Error",
type: "error",
duration: 5 * 1000,
});
if (res.code === 50008 || res.code === 50012 || res.code === 50014) {
MessageBox.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);
Message({
message: error.message,
type: "error",
duration: 5 * 1000,
});
return Promise.reject(error);
}
);
export default service;
由于上面 request 拦截器中读取 token 是从 cookie 中读的,所以接下来我们要配置 cookie 。
配置 cookie
1、装包
yarn add js-cookie
新增 src/utils/auth.js 文件
import Cookies from "js-cookie";
const TokenKey = "Admin-Token";
export function getToken() {
return Cookies.get(TokenKey);
}
export function setToken(token, remember = false) {
if (remember) {
return Cookies.set(TokenKey, token, { expires: 7 })
}
return Cookies.set(TokenKey, token);
}
export function removeToken() {
return Cookies.remove(TokenKey);
}
CSS 文件
1、新建 src/styles/common.scss 文件
a {
color: #1f99b0;
text-decoration: none;
}
.clear:after {
display: block;
content: "clear";
height: 0;
clear: both;
overflow: hidden;
visibility: hidden;
}
2、新建 src/styles/element-ui.scss 文件
.el-menu-item.is-active {
color: #1f99b0 !important;
}
.el-switch.is-checked .el-switch__core {
border-color: #1f99b0 !important;
background-color: #1f99b0 !important;
}
.el-button--primary {
color: #fff !important;
background-color: #1f99b0 !important;
border-color: #1f99b0 !important;
}
.el-menu-item:focus, .el-menu-item:hover {
background-color: #e1f0f0 !important;
}
.el-submenu__title:hover {
background-color: #e1f0f0 !important;
}
.el-tag {
background-color: #e1f0f0;
border-color: #e1f0f0;
color: #1f99b0;
}
.el-button.el-button--default:focus, .el-button.el-button--default:hover {
color: #1f99b0;
border-color: #dcdef6;
background-color: #e1f0f0;
}
.el-radio__input.is-checked .el-radio__inner {
border-color: #1f99b0;
background: #1f99b0;
}
.el-radio__input.is-checked + .el-radio__label {
color: #1f99b0;
}
.el-checkbox__input.is-checked .el-checkbox__inner, .el-checkbox__input.is-indeterminate .el-checkbox__inner {
background-color: #1f99b0;
border-color: #1f99b0;
}
.el-button--text{
color: #1f99b0;
}
.el-button--text:focus, .el-button--text:hover {
color: #1f99b0;
text-decoration: underline;
}
.el-input.is-active .el-input__inner, .el-input__inner:focus {
border-color: #1f99b0;
outline: 0;
}
.el-cascader-node.in-active-path, .el-cascader-node.is-active, .el-cascader-node.is-selectable.in-checked-path {
color: #1f99b0;
}
.el-radio__inner:hover {
border-color: #1f99b0;
}
3、新建 src/styles/layouts.scss 文件
html, body {
height: 100%;
margin: 0;
}
#app {
height: 100%;
}
.el-container, .el-aside, .el-aside .el-col, .el-menu {
height: 100%;
}
.el-header, .el-footer {
background-color: #e1f0f0;
color: #333;
height: 60px;
line-height: 60px;
}
.el-header {
position: fixed;
top: 0;
width: 100%;
z-index: 2;
.logo {
height: 40px;
margin-top: 10px;
float: left;
}
h1 {
margin: 0 0 0 20px;
font-size: 18px;
font-weight: normal;
float: left;
line-height: 60px;;
}
}
.el-footer {
position: fixed;
bottom: 0;
width: 100%;
z-index: 2;
text-align: center;
}
.el-aside {
position: fixed;
top: 60px;
width: 100%;
z-index: 2;
background-color: #e1f0f0;
color: #333;
line-height: 200px;
}
.el-main {
color: #333;
overflow: visible;
margin: 60px 0 0 200px;
padding-bottom: 80px;
}
4、新建 src/styles/table.scss 文件
.sort-input input{
width: 50px;
text-align: center;
}
.el-table .cell .delete{
margin-left: 10px;
}
5、新建 src/styles/index.scss 文件
@import './layouts.scss';
@import './common.scss';
@import './element-ui.scss';
@import './table.scss';
后台首页
修改 src/views/Home.vue 文件
<template>
<div class="home">
<el-breadcrumb separator-class="el-icon-arrow-right">
<el-breadcrumb-item :to="{ path: '/' }">首页</el-breadcrumb-item>
<el-breadcrumb-item>仪表盘</el-breadcrumb-item>
</el-breadcrumb>
<el-divider></el-divider>
<el-card class="box-card">
<div slot="header" class="clearfix">
长乐未央 <br />
是什么意思?
</div>
<div class="info">
汉时,有长乐宫又有未央宫。"长乐未央"四字,常见于汉宫瓦当。
"长乐"的"乐",最早出自周公旦制礼作"乐"的乐。"乐者,天地之和也"《礼记·乐记》,"乐"的本质是"和"。"未央"意为未尽,没有穷尽。最早出自诗经(庭燎.小雅)夜如何其?夜未央,庭燎之光。
"长乐未央"意为永远快乐,没有穷尽。
</div>
</el-card>
</div>
</template>
<script>
export default {
name: "Home"
};
</script>
<style scoped lang="scss">
.info {
font-size: 14px;
}
</style>
布局模板
1、新建 src/views/layout/index.vue 组件
<template>
<el-container>
<el-header>
<img
src="https://images.clwy.cn/common/logo.png"
alt="长乐未央Logo"
class="logo"
/>
<h1>长乐未央后台管理</h1>
</el-header>
<el-container>
<Sidebar />
<el-container>
<el-main>
<router-view />
</el-main>
<el-footer>
Copyright 2013 - 2022 CLWY Inc. All Rights Reserved.
</el-footer>
</el-container>
</el-container>
</el-container>
</template>
<script>
import Sidebar from "./components/Sidebar";
export default {
name: "Layout",
components: {
Sidebar
}
};
</script>
2、新建 src/views/layout/components/Sidebar/index.vue 组件
<template>
<el-aside width="200px">
<el-col>
<el-menu
:router="true"
default-active="/"
class="el-menu-vertical-demo"
>
<el-menu-item index="/">
<i class="el-icon-pie-chart"></i>
<span slot="title">仪表盘</span>
</el-menu-item>
<el-submenu index="article_manage">
<template slot="title">
<i class="el-icon-document"></i>
<span>新闻管理</span>
</template>
<el-menu-item index="/articles">新闻</el-menu-item>
<el-menu-item index="/articles/trash">回收站</el-menu-item>
</el-submenu>
<el-menu-item index="/users">
<i class="el-icon-user"></i>
<span slot="title">用户管理</span>
</el-menu-item>
<el-menu-item index="/settings">
<i class="el-icon-setting"></i>
<span slot="title">系统设置</span>
</el-menu-item>
</el-menu>
</el-col>
</el-aside>
</template>
2、修改 src/App.vue 文件
<template>
<div id="app">
<router-view />
</div>
</template>
<script>
export default {
name: "App",
};
</script>
3、新增 src/views/error-page/404.vue 和 src/views/error-page/401.vue 页面,代码分别如下:
<template>
<div>
<div class="">
404页面
</div>
<a href="" class="">返回上一页</a>
</div>
</template>
<script>
export default {
name: "Page404",
};
</script>
<template>
<div class="errPage-container">
<el-button icon="el-icon-arrow-left" class="pan-back-btn" @click="back">
返回
</el-button>
<el-row>
<el-col :span="12">
<h1 class="text-jumbo text-ginormous">Oops!</h1>
<h2>你没有权限去该页面</h2>
<ul class="list-unstyled">
<li>或者你可以去:</li>
<li class="link-type">
<router-link to="/"> 回首页 </router-link>
</li>
</ul>
</el-col>
</el-row>
</div>
</template>
<script>
export default {
name: "Page401",
methods: {
back() {
if (this.$route.query.noGoBack) {
this.$router.push({ path: "/" });
} else {
this.$router.go(-1);
}
},
},
};
</script>
<style lang="scss" scoped>
.errPage-container {
width: 800px;
max-width: 100%;
margin: 100px auto;
.pan-back-btn {
background: #008489;
color: #fff;
border: none !important;
}
.pan-gif {
margin: 0 auto;
display: block;
}
.pan-img {
display: block;
margin: 0 auto;
width: 100%;
}
.text-jumbo {
font-size: 60px;
font-weight: 700;
color: #484848;
}
.list-unstyled {
font-size: 14px;
li {
padding-bottom: 5px;
}
a {
color: #008489;
text-decoration: none;
&:hover {
text-decoration: underline;
}
}
}
}
</style>
4、新增 src/views/auth/login.vue 登录页面
<template>
<div class="sign-in">
<el-row>
<el-col :span="12" :offset="6">
<h1>长乐未央后台</h1>
<el-form
:model="loginForm"
:rules="loginRules"
ref="ruleForm"
label-width="100px"
class="demo-ruleForm"
>
<el-form-item label="用户名" prop="username">
<el-input v-model="loginForm.username"></el-input>
</el-form-item>
<el-form-item label="密码" prop="sort">
<el-input v-model="loginForm.password" type="password"></el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleLogin('ruleForm')">
立即登录
</el-button>
<el-button @click="resetForm('ruleForm')">重置</el-button>
</el-form-item>
</el-form>
</el-col>
</el-row>
</div>
</template>
<script>
export default {
data() {
return {
loginForm: {
username: "admin",
password: "123123",
},
loginRules: {
name: [{ required: true, message: "请输入用户名", trigger: "blur" }],
password: [{ required: true, message: "请输入密码", trigger: "blur" }],
},
};
},
methods: {
handleLogin(formName) {
this.$refs[formName].validate(async (valid) => {
if (valid) {
await this.$store.dispatch("auth/login", this.loginForm);
await this.$router.push({ path: "/" });
}
});
},
resetForm(formName) {
this.$refs[formName].resetFields();
},
},
};
</script>
<style scoped lang="scss">
.sign-in {
padding-top: 100px;
}
h1 {
text-align: center;
}
</style>
最后引入 css
1、修改 src/main.js 文件
import Vue from "vue";
import App from "./App.vue";
import router from "./router";
import store from "./store";
import ElementUI from "element-ui";
import "element-ui/lib/theme-chalk/index.css";
import "@/styles/index.scss";
Vue.config.productionTip = false;
Vue.use(ElementUI);
import * as filters from "./filters";
Object.keys(filters).forEach((key) => {
Vue.filter(key, filters[key]);
});
new Vue({
router,
store,
render: (h) => h(App)
}).$mount("#app");
2、修改 src/router/index.js ,将路由修改为懒加载模式
import Vue from "vue";
import VueRouter from "vue-router";
import Layout from "@/views/layout";
Vue.use(VueRouter);
const routes = [
{
path: "/login",
component: () => import("@/views/auth/login")
},
{
path: "/404",
component: () => import("@/views/error-page/404")
},
{
path: "/401",
component: () => import("@/views/error-page/401")
},
{
path: "/",
component: Layout,
children: [
{
path: "",
name: "Home",
component: () => import("@/views/home"),
meta: { title: "首页" }
}
]
}
];
const router = new VueRouter({
mode: "history",
base: process.env.BASE_URL,
routes
});
export default router;
配置代理解决跨域
1、项目根目录下新建 vue.config.js 文件
module.exports = {
devServer: {
proxy: {
'/api': {
target: process.env.VUE_APP_BASE_API,
ws: true,
changeOrigin: true,
pathRewrite: {
'^/api': ''
}
},
}
}
}
2、修改 src/utils/request.js 文件部分代码如下:
.
.
const service = axios.create({
baseURL: "/api",
timeout: 5000,
});
.
.
最终后台首页效果图如下:
|