技术

vue2 + vue-router + Vuex + axios + elementui + echarts

目录

技术

serve

1.db数据库连接

2.user.js

3.app.js

router

1. permission.js路由拦截

store(vuex状态管理)

 userModule.js

api和utils

 1.request.js请求根路径

 2.SessionStorage.js(数据持久化)

3.接口api文件,Goods的index.js

登录页面

1.LoginView..vue

 Layout布局界面

1. MyContent.vue(动态面包屑)

相关知识点

项目准备

项目搭建

项目初始化

后台服务 (serve文件)

路由配置

设置代理proxy实现跨域

登录

数据可视化

mockjs (模拟数据)

商品管理界面

上传图片

富文本编译

国际化

vuei18n

element国际化

vue-pdf

vue项目实现表格导出Excel表格

项目完整代码:


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_adminicon-default.png?t=M5H6https://gitee.com/zi1726517395/vue2_admin

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐