vue2后台管理系统( vue2 + vue-router + Vuex + axios + elementui + echarts,nodejs后台)
vue2 + vue-router + Vuex + axios + elementui + echarts后台管理系统,包括国际化配置,富文本编译器,mockjs,上传图片,pdf打印,导出Excel表,购物车案例
·
技术
vue2 + vue-router + Vuex + axios + elementui + echarts
目录
serve
1.db数据库连接
// const mysql = require('mysql')
import mysql from 'mysql'
const client = mysql.createConnection({
host: 'localhost', // 数据域名
user: 'root',
password: 'admin123',
database: 'my_db_01',
port: '3306'
})
// 封装数据库操作语句
export function sqlFn (sql, arr, callback) {
client.query(sql, arr, function (error, result) {
if (error) {
console.log('数据库语句错误')
return
}
callback(result)
})
}
2.user.js
import express from 'express'
import { sqlFn } from '../db/index.js'
import Mock from 'mockjs'
import sercet from '../sercet.js'
import jwt from 'jsonwebtoken'
// 图片需要的模块
import fs from 'fs'
import path from 'path'
const router = new express.Router()
/* 配置路由 */
/* 登录接口: */
router.post('/login', (req, res) => {
const { username, password } = req.body
// console.log(username,password);
// 请求数据库
const sql = 'select * from ev_user where username=? and password=?'
const arr = [username, password]
sqlFn(sql, arr, result => {
if (result.length > 0) {
const token = jwt.sign({
username: result[0].username,
id: result[0].id
}, sercet.key, {
expiresIn: 20 * 1
})
res.send({
status: 200,
success: true,
message: '登录成功',
token: 'Bearer ' + token,
data: token
})
} else {
res.send({ status: 404, message: '信息错误' })
}
})
})
// 注册接口
router.post('/register', (req, res) => {
const {
username,
password
} = req.body
const sql = 'insert in ev_users values(null,?,?)'
const arr = [username, password]
sqlFn(sql, arr, (result) => {
if (result.attectedRows > 0) {
res.send({
msg: '注册成功'
})
} else {
res.status(401).json({
error: '用户名密码错误'
})
}
})
})
// 商品列表
router.get('/pojectList', (req, res) => {
const page = parseInt(req.query.pagesize || 1)
const sqlLen = 'select * from sp_goods where goods_id'
sqlFn(sqlLen, null, data => {
const len = data.length
const sql = 'select * from sp_goods order by goods_id desc limit 8 offset ' + (page - 1) * 8
sqlFn(sql, null, result => {
if (result.length > 0) {
res.send({
status: 200,
data: result,
pagesize: 8,
total: len
})
} else {
res.send({
status: 500,
msg: '暂无数据'
})
}
})
})
})
// 商品规格列表
router.get('/specificationsList', (req, res) => {
const page = parseInt(req.query.pagesize || 1)
const sqlLen = 'select * from sp_goods where goods_id'
sqlFn(sqlLen, null, data => {
const len = data.length
const sql = 'select * from sp_attribute order by attr_id desc limit 8 offset ' + (page - 1) * 8
sqlFn(sql, null, result => {
if (result.length > 0) {
res.send({
status: 200,
data: result,
pagesize: 8,
total: len
})
} else {
res.send({
status: 500,
msg: '暂无数据'
})
}
})
})
})
// 类目选择接口,参数cid:id
router.get('/category', (req, res) => {
const data = Mock.mock({
status: 200,
// 生成list字段:数组类型,内容是6个数据
'result|4': [
{
'id|+1': 1, // id自增
'name|+1': [// 一次获取一个数值,累次向下推
'电视',
'大屏',
'OLED',
'空调',
'冷风'
],
'cid|+1': 10001
}
]
})
console.log(req.query)
if (req.query.cid === 4) {
res.send({
status: 500
})
} else {
res.send(data)
}
})
router.post('/upload', (req, res) => {
const oldName = req.files[0].path// 获取名字
// 给新名字加上原来的后缀
const newName = req.files[0].path + path.parse(req.files[0].originalname).ext
fs.renameSync(oldName, newName)// 改图片的名字
res.send({
err: 0,
url:
'http://localhost:1314/upload/' +
req.files[0].filename +
path.parse(req.files[0].originalname).ext// 该图片的预览路径
})
})
// 测试mockjs数据
router.get('/test', (req, res) => {
// 使用mock生成数据
const data = Mock.mock({
info: '我是一个单纯的对象',
status: 200,
// 生成list字段:数组类型,内容是6个数据
'list|6': [
{
'id|+1': 1, // id自增
'flag|1-2': true,
'city|1': { // 获取城市数据
310000: '上海市',
320000: '江苏省',
330000: '浙江省',
340000: '安徽省',
110000: '北京市',
130000: '河北省'
},
'arr|+1': [// 一次获取一个数值,累次向下推
'AMD',
'CMD',
'UMD',
'BMD',
'aaa',
'ccc'
],
desc: '@cword(20,80)', // 随机汉字
imgUrl: '@image'
}
]
})
res.send(data)
})
// module.exports=router
export default router
3.app.js
import express from 'express'
import userRouter from './router/user.js'
// 图片需要的模块
import multer from 'multer'
const app = express()
/* post接受参数配置 配置解析表单数据的中间件 */
app.use(express.json()) // for parsing application/json
app.use(express.urlencoded({ extended: true })) // for parsing application/x-www-form-urlencoded
/* 设置跨域 */
app.use(function (req, res, next) {
res.header('Access-Control-Allow-Origin', '*')
res.header('Access-Control-Allow-Headers', 'Content-Type,Content-Length,Authorization,Accept,X-Requested-with,yourHeaderFeild')
res.header('Access-Control-Allow-Methods', 'PUT,POST,GET,DELETE,OPTIONS')
next()
})
const objMulter = multer({ dest: './public/upload/' })
// 实例化multer,传递的参数对象,dest表示上传文件的存储路径
app.use(objMulter.any())// any表示任意类型的文件
// app.use(objMulter.image())//仅允许上传图片类型
app.use(express.static('./public'))
app.use('/api', userRouter)
/* 配置错误级别中间件,捕获并处理Token认证失败的结果 */
app.use(function (err, req, res, next) {
if (err.name === 'UnauthorizedError') return res.send({ status: 1, message: err.message })
})
app.listen(1314, () => {
console.log('Express serve running at http://localhost:1314')
})
router
1. permission.js路由拦截
import store from '../store/index.js'
import router from './index.js'
// 路由拦截
router.beforeEach((to, from, next) => {
// 判断是否用户是否登录,用循环检查每一个路由元信息有无isLogin等true
if (to.matched.some(ele => ele.meta.isLogin)) {
// 2.判断当前用户是否已经登录
const token = store.state.userModule.userinfo.token
if (token) {
next()
} else {
next('/login')
}
} else {
next() // 不需要登录
}
})
store(vuex状态管理)
userModule.js
// user模块数据
export default {
// 开启命名空间
namespaced: true,
state: () => ({
userinfo: {
username: '',
token: ''
}
}),
getters: {
},
mutations: {
// 设置用户信息
setUser (state, value) {
state.userinfo = value
},
// 清空信息
cleanUser (state) {
state.userinfo.username = ''
state.userinfo.token = ''
}
},
actions: {
}
}
api和utils
1.request.js请求根路径
import axios from 'axios'
const request = axios.create({
// 指定请求的根路径
baseURL: '/api',
timeout: 3000
})
export default request
2.SessionStorage.js(数据持久化)
import store from '../store/index.js'
// 持久化
let userinfo = sessionStorage.getItem('userinfo')
if (userinfo) {
userinfo = JSON.parse(userinfo)
store.commit('userModule/setUser', userinfo)
}
3.接口api文件,Goods的index.js
import request from '@/utils/request.js'
// 获取商品列表
export function getGoodsList (pagesize) {
return request.get('/api/pojectList', {
params: {
pagesize
}
})
}
// 获取类目选择
export function getCategory (cid) {
return request.get('/api/category', {
params: {
cid
}
})
}
登录页面
1.LoginView..vue
<template>
<div class="login animate__animated animate__lightSpeedInRight">
<el-row>
<el-col :lg="15" :md="12" :xs="0" :sm="0" style="min-height:90vh" class="left">
<img class="animate__animated animate__bounce" src="https://pinia.vuejs.org/logo.svg" alt="" style="width:150px">
<span class="animate__animated animate__rubberBand">欢迎光临 Pinia!</span>
<span class="animate__animated animate__rubberBand" style="font-size:25px">后台管理系统</span>
</el-col>
<el-col :lg="9" :md="12" :xs="24" style="min-height:100vh" class="bg-light-50 right ">
<h2>欢迎回来</h2>
<span>账号密码登录</span>
<el-form ref="formRef" :rules="rules" :model="form" status-icon label-width="80px" class="demo-ruleForm">
<el-form-item label="账号" prop="username">
<el-input v-model="form.username">
<i slot="prefix" class="el-input__icon el-icon-user-solid"></i>
</el-input>
</el-form-item>
<el-form-item label="密码" prop="password">
<el-input v-model="form.password" type="password" show-password>
<i slot="prefix" class="el-input__icon el-icon-lock"></i>
</el-input>
</el-form-item>
<el-form-item>
<el-button :plain="true" type="primary" @click="submitForm('formRef')">提交</el-button>
<el-button type="primary" @click="resetForm('formRef')">重置</el-button>
</el-form-item>
</el-form>
</el-col>
</el-row>
</div>
</template>
<script>
import { login } from '@/api/Mangaer/index.js'
import { mapMutations } from 'vuex'
import jwt from 'jwt-decode'
export default {
data () {
return {
info: '',
form: {
username: 'pinia',
password: 123456
},
rules: {
username: [
{ required: true, message: '用户名不能为空', trigger: 'blur' }
],
password: [
{ required: true, message: '密码不能为空', trigger: 'blur' }
]
}
}
},
methods: {
// 辅助函数触发方法setUser
...mapMutations('userModule', ['setUser']),
open2 () {
this.$message({
message: '登录成功!',
type: 'success'
})
},
open4 () {
this.$message.error('账号或者密码错误!')
},
submitForm (formName) {
this.$refs[formName].validate((valid) => {
if (valid) {
login(this.form.username, this.form.password).then(res => {
console.log(this.form.username, this.form.password)
if (res.data.status === 200) {
// 登录成功后
/*
1.存储用户信息到vuex
2.跳转路由
3.存储到本地
4.在路由规则下全局路由守卫进行token字段拦截
5.持久化 (在main.js调用store保存方法)
*/
const user = {
// 解析token拿到username
username: jwt(res.data.token).username,
token: res.data.token
}
// 存储到vuex
this.setUser(user)
// 存储到本地
window.sessionStorage.setItem('userinfo', JSON.stringify(user))
// 跳转路由
this.$router.push('/')
// 弹出成功登录消息框
// console.log(res.data)
// console.log(jwt(res.data.token))
this.open2()
// 解析token字段
} else {
// 账号或密码错误
this.open4()
}
})
} else {
console.log('error submit!!')
return false
}
})
},
resetForm (formName) {
this.$refs[formName].resetFields()
}
}
}
</script>
<style lang="less" scoped>
@bgcolor:#111827;
@btncolor:#ffd859;
.login{
.left,.right{
display: flex;
align-items: center;
flex-direction: column;
justify-content: center;
}
.left{
text-align: center;
font-size: 50px;
font-weight: 800;
}
.right{
color: @bgcolor;
font-size: 35px;
font-weight: 800;
span{
font-size: 20px;
font-weight: 500;
}
form{
margin-top: 20px;
margin-right: 50px;
}
button{
margin-left: 10px;
font-weight: 700;
background-color:@bgcolor;
font-size: 16px;
border: 1px solid white;
}
button:hover{
background-color:@btncolor;
color: @bgcolor;
border: 1px solid @bgcolor;
}
}
}
</style>
Layout布局界面
1. MyContent.vue(动态面包屑)
<template>
<div class="cantianer">
<div class="header">
<div class="left">
<i v-if="isCollapse" @click="changeMenu" class="iconfont icon-bofangliebiao"></i>
<i v-if="!isCollapse" @click="changeMenu" class="iconfont icon-381-indent-decrease"></i>
<!-- 动态生成面包屑 -->
<el-breadcrumb separator="/">
<el-breadcrumb-item :to="{ path: item.path }" v-for="item in breadcrumbList" :key="item.path">{{item.meta.title}}</el-breadcrumb-item>
</el-breadcrumb>
</div>
<div class="right">
<el-dropdown @command="clickLang">
<span class="el-dropdown-link" style="color:#fff;">
语言切换<i class="el-icon-arrow-down el-icon--right"></i>
</span>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item command="zh">中文</el-dropdown-item>
<el-dropdown-item command="en">English</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
<div>
欢迎:{{userinfo.username}}
<el-button type="primary" @click="outlogin">退出登录 <i class="el-icon-switch-button"></i></el-button>
</div>
</div>
</div>
<!-- 内容区域,路由出口 -->
<div class="content">
<router-view></router-view>
</div>
</div>
</template>
<script>
import { mapState, mapMutations } from 'vuex'
export default {
props: ['isCollapse'],
// data () {
// return {
// breadcrumbList: this.$route.matched
// }
// },
computed: {
breadcrumbList () {
return this.$route.matched
},
...mapState('userModule', ['userinfo'])
},
methods: {
...mapMutations('userModule', ['cleanUser']),
changeMenu () {
// 点击切换按钮 ,修改父组件数据 isCollapse
this.$emit('changeCollapse')
},
clickLang (command) {
console.log(command)
this.$i18n.locale = command
},
outlogin () {
// 退出登录
// 清空vuex数据
this.cleanUser()
// 清空本地存储
sessionStorage.removeItem('userinfo')
// 返回登录
this.$router.push('/login')
}
},
created () {
console.log(this.$route.matched)
}
}
</script>
相关知识点
项目准备
iconfont图标
项目搭建
- 1.vue create vue2_admin
- 2.vue-router vuex
- 3.axios
- 4.elementui ---(按需见官网)
创建插件可以按需循环注册element.js,在main.js调用这个方法即可
项目初始化
- 1.删除无用的组件
- 2.css初始化
reset.css(淘宝方案),在main.js导入
- 3.引入iconfont图标
先下载,再在main.js引入
后台服务 (serve文件)
- 1.npm i express -S
- 2.npm i mysql -S
- 3.jwt 生成token jsonwebtoken
解析token: 安装jwt-decode
- 4.mockjs ---模拟数据
路由配置
页面布局配置 同级登录页面
设置代理proxy实现跨域
登录
数据可视化
echarts5
1.安装 npm install echarts -S
2.使用方式
1.导入echarts在组件内使用
2.导入全局 挂载原型上 全局使用(该项目)
3.开发成vue插件
3.使用main.js
mockjs (模拟数据)
1.安装:npm i mockjs -S
2.引入:
node.js:const Mock =require('mockjs')
前端js:import Mock from 'mockjs'
3.语法:
Mock.mock({
})
商品管理界面
上传图片
1.element-ui upload组件
2.实现后台支持
上传图片接口 post请求
说明:
1.后台安装multer模块,同时引入fs模块
在app.js进行导入注册
import multer from 'multer'
let objMulter = multer({ dest: "./public/upload/" });
//实例化multer,传递的参数对象,dest表示上传文件的存储路径
app.use(objMulter.any())//any表示任意类型的文件
// app.use(objMulter.image())//仅允许上传图片类型
// 静态资源托管
app.use(express.static("./public"));
2.router.js入口文件导入模块
import fs from 'fs'
import path from 'path'
3.上传图片
富文本编译
1.百度编译器(较大)
2.wangEditor(轻量)
wangEditor使用步骤:
1.官网:https://www.wangeditor.com/doc/
2.基本使用
1.安装:npm i wangeditor -S
2.引入模块 :
import E from "wangeditor";//导入组件
3.使用
// 相当于js的变量设置
data() {
return {
editor: null,
}}
//methods里创建调用、或是mounted里面直接生产
this.editor = new E(_this.$refs.editorElem);//获取组件并构造编辑器
this.editor.create(); // 创建富文本实例
3.常用配置
(v5版本)
1.清空编辑器内容
editor.clear()
2.获取内容/获取非格式化的 html
editor.getHtml()
3.获取当前编辑器的纯文本内容
const text = editor.getText()
4.重置编辑器的 HTML 内容。(只能解析 editor.getHtml() 返回的 HTML 格式,不支持自定义 HTML 格式。)
editor.setHtml('<p>hello</p>')
5.配置菜单
1.要配置哪个菜单,首先要知道这个菜单的 key 。执行 editor.getAllMenuKeys() 可获取编辑器所有菜单,从中找到自己想要的菜单 key 即可。
(v4版本)
1.清空内容
editor.txt.clear()
2.获取html内容
editor.txt.html()
3.设置内容
创建编辑器之后,使用 editor.txt.html(...) 设置编辑器内容。
4.配置菜单(编辑器创建之前)
editor.config.menus
使用 editor.config.menus 定义显示哪些菜单和菜单的顺序。
5.配置onchange回调---可获取内容
配置 onchange 函数之后,用户操作(鼠标点击、键盘打字等)导致的内容变化之后,会自动触发 onchange 函数执行。
国际化
vuei18n
vuei18n
1.介绍:Vue I18n 是 Vue.js 的国际化插件。它可以轻松地将一些本地化功能集成到你的 Vue.js 应用程序中。
2. 安装
npm install vue-i18n@8.26.7 -S
main.js导入或者单独的文件
import Vue from 'vue'
import VueI18n from 'vue-i18n'
Vue.use(VueI18n)
3.使用步骤
1. 如果使用模块系统 (例如通过 vue-cli),则需要导入 Vue 和 VueI18n ,然后调用 Vue.use(VueI18n)
import Vue from 'vue'
import VueI18n from 'vue-i18n'
Vue.use(VueI18n)
2.准备翻译的语言环境信息
const messages = {
en: {//英文
home: {
hello: 'hello world',
},
goods:{
}
},
zh: {//中文
home: {
hello: '你好 世界',
},
goods:{
}
}
}
3.通过选项创建 VueI18n 实例
const i18n = new VueI18n({
locale: 'en', // 设置地区
messages, // 设置地区信息
})
4.通过 `i18n` 选项创建 Vue 实例
new Vue({ i18n }).$mount('#app')
5.使用语法
<p>{{ $t("home.hello") }}</p>
element国际化
element国际化
1.导入
import Element from 'element-ui'
2.导入语言环境
import enLocale from 'element-ui/lib/locale/lang/en'
import zhLocale from 'element-ui/lib/locale/lang/zh-CN'
目前 Element 内置了以下语言:
简体中文(zh-CN)
英语(en)
德语(de)
葡萄牙语(pt)
西班牙语(es)
丹麦语(da)
法语(fr)
挪威语(nb-NO)
繁体中文(zh-TW)
意大利语(it)
韩语(ko)
3.配置语言环境
const messages = {
en: {
message: 'hello',
...enLocale // 或者用 Object.assign({ message: 'hello' }, enLocale)
},
zh: {
message: '你好',
...zhLocale // 或者用 Object.assign({ message: '你好' }, zhLocale)
}
}
// Create VueI18n instance with options
const i18n = new VueI18n({
locale: 'en', // set locale
messages, // set locale messages
})
4.配置使用
Vue.use(Element, {
i18n: (key, value) => i18n.t(key, value)
})
vue-pdf
vue-pdf
npm install vue-pdf
<template>
<pdf src="./path/to/static/relativity.pdf"></pdf>
</template>
<script>
import pdf from 'vue-pdf'
export default {
components: {
pdf
}
}
vue项目实现表格导出Excel表格
1.安装
npm i file-saver xlsx -S
npm i script-loader -D
2.在/src目录下新建一个excel(名字也可自取)文件夹,存入Blob.js和Export2Excel.js文件3.在common文件夹里新建js文件夹再新建util.js
项目完整代码:
https://gitee.com/zi1726517395/vue2_adminhttps://gitee.com/zi1726517395/vue2_admin
更多推荐
已为社区贡献3条内容
所有评论(0)