总结一下运行此项目需要安装的插件:
一、router
二、vuex
三、element ui
前三个可以使用vue ui 进行安装
四、安装less :npm install less -–save-dev
五、安装less-loader(低版本的less-loader):npm install less-loader@5.0.0 --save
六、安装axios:npm install axios --save
七、core-js版本:npm i core-js@3.6.4
八、mokejs:npm install --save mockjs
首先和之前一样把直接创建好的脚手架拿来用
安装三个插件
再次用命令安装axios
npm install axios --save
安装完成后的四个插件
运行后的效果
接下来开始正式写demo
添加公共样式
新建Login页面
<template> <div class="container"> <div class="wrapper"> <h1>WEB ADMIN</h1> <el-form label-width="auto" :model="form" :rules="rules" ref="form" v-loading="isloading" > <el-form-item label="用户" prop="username"> <el-input v-model="form.username" placeholder="请输入用户名"> <i slot="prefix" class="el-input__icon el-icon-user"></i> </el-input> </el-form-item> <el-form-item label="密码" prop="password"> <el-input v-model="form.password" placeholder="请输入密码" show-password > <i slot="prefix" class="el-input__icon el-icon-lock"></i> </el-input> </el-form-item> <el-row> <el-button type="primary" size="mini" @click="login">登录</el-button> <el-button type="success" size="mini" @click="resetForm('form')" >重置</el-button > </el-row> </el-form> </div> </div> </template> <script> export default { name: "Login", data() { return { form: { username: "admin", password: "admin", }, isloading: false, rules: { username: [ { required: true, message: "请输入用户名", trigger: "blur", }, { min: 3, max: 18, message: "长度在 3 到 18 个字符", trigger: "blur", }, ], password: [ { required: true, message: "请输入密码", trigger: "blur", }, { min: 3, max: 18, message: "长度在 3 到 18 个字符", trigger: "blur", }, ], }, }; }, methods: { resetForm(formName) { this.$refs[formName].resetFields(); }, login() { /* this.$axios.post('/login',{ username:this.form.username, password:this.form.password }).then(res=>{ console.log(res); this.$store.commit('setUser',res.data) window.sessionStorage.setItem('token',res.data.token) this.$router.push('/') }) */ this.isloading = true; this.$api .login({ username: this.form.username, password: this.form.password, }) .then((res) => { console.log(res); this.$store.commit("setUser", res.data); window.sessionStorage.setItem("token", res.data.token); this.isloading = false; this.$router.push("/"); this.$message({ message: "恭喜你,登录成功", type: "success", }); }); }, }, }; </script> <style scoped="scoped"> .container { width: 100%; height: 100%; position: absolute; bottom: 0; left: 0; overflow: hidden; background-color: #2b4b6b; } .wrapper { width: 500px; height: 320px; background-color: #fff; margin: 120px auto; } .wrapper { padding: 0 30px; } .wrapper h1 { color: #666; text-align: center; padding: 20px 0 30px 0; } .el-row { float: right; } </style>
页面效果如下:
接下来写NotFound页面
当网址输入错误时将出现404页面
<template> <div class="not-found"> <h1>啊哦!找不到相关页面o(╥﹏╥)o。</h1> <router-link to> <p @click="$router.back(-1)">返回上一级页面</p> </router-link> </div> </template> <script> export default { } </script> <style lang="less" scoped> .not-found { width: 100%; height: 100vh; background-color: #6495ED; display: flex; justify-content: center; align-items: center; h1 { margin: 0; color: #fff; } a { color: #E0FFFF; font-size: 14px; font-family: 'Gill Sans', 'Gill Sans MT', Calibri, 'Trebuchet MS', sans-serif; transform: translateY(10px); } } </style>
引入完毕后安装less-loader插件和less
安装less :
npm install less -–save-dev
安装less-loader(低版本的less-loader):
npm install less-loader@5.0.0 --save
然后运行,如果会报错的话这里给两个参考方案 ,启动成功的话当然是最好的了!
接下来还会有报错建议安装一下这个版本的core-js
npm i core-js@3.6.4
出现下列报错信息就是得重新安装一下element-ui
These dependencies were not found: * element-ui in ./src/plugins/element.js npm install --save element-ui
运行成功后可以试一下这一页能不能打开
显示出来后可以继续进行下面的操作
写四个公共部分
Aside.vue
<template> <div class="container"> <div class="wrapper"> <h1>WEB ADMIN</h1> <el-form label-width="auto" :model="form" :rules="rules" ref="form" v-loading="isloading" > <el-form-item label="用户" prop="username"> <el-input v-model="form.username" placeholder="请输入用户名"> <i slot="prefix" class="el-input__icon el-icon-user"></i> </el-input> </el-form-item> <el-form-item label="密码" prop="password"> <el-input v-model="form.password" placeholder="请输入密码" show-password > <i slot="prefix" class="el-input__icon el-icon-lock"></i> </el-input> </el-form-item> <el-row> <el-button type="primary" size="mini" @click="login">登录</el-button> <el-button type="success" size="mini" @click="resetForm('form')" >重置</el-button > </el-row> </el-form> </div> </div> </template> <script> export default { name: "Login", data() { return { form: { username: "admin", password: "admin", }, isloading: false, rules: { username: [ { required: true, message: "请输入用户名", trigger: "blur", }, { min: 3, max: 18, message: "长度在 3 到 18 个字符", trigger: "blur", }, ], password: [ { required: true, message: "请输入密码", trigger: "blur", }, { min: 3, max: 18, message: "长度在 3 到 18 个字符", trigger: "blur", }, ], }, }; }, methods: { resetForm(formName) { this.$refs[formName].resetFields(); }, login() { /* this.$axios.post('/login',{ username:this.form.username, password:this.form.password }).then(res=>{ console.log(res); this.$store.commit('setUser',res.data) window.sessionStorage.setItem('token',res.data.token) this.$router.push('/') }) */ this.isloading = true; this.$api .login({ username: this.form.username, password: this.form.password, }) .then((res) => { console.log(res); this.$store.commit("setUser", res.data); window.sessionStorage.setItem("token", res.data.token); this.isloading = false; this.$router.push("/"); this.$message({ message: "恭喜你,登录成功", type: "success", }); }); }, }, }; </script> <style scoped="scoped"> .container { width: 100%; height: 100%; position: absolute; bottom: 0; left: 0; overflow: hidden; background-color: #2b4b6b; } .wrapper { width: 500px; height: 320px; background-color: #fff; margin: 120px auto; } .wrapper { padding: 0 30px; } .wrapper h1 { color: #666; text-align: center; padding: 20px 0 30px 0; } .el-row { float: right; } </style>
BreadCrumb.vue
<template> <el-breadcrumb separator-class="el-icon-arrow-right"> <el-breadcrumb-item v-for="(item, idx) in breads" :key="idx" :to="{ path: item.path }"> {{ item.name }} </el-breadcrumb-item> </el-breadcrumb> </template> <script> import { mapState } from 'vuex' export default { computed: { ...mapState(['breads']) } } </script> <style> </style>
Headedr.vue
<template> <div class="header"> <div class="header-left"> <div class="header-left__logo"> <img src="@/assets/logo.png" alt=""> </div> <div class="header-left__title">后台管理系统权限Demo</div> </div> <div class="header-right"> <div class="header-right__info"> <div class="header-right__info-name">{{user.username}}</div> </div> <div class="header-right__logout"> <el-button type="danger" size="20" @click="logout">退出</el-button> </div> </div> </div> </template> <script> import { mapState } from 'vuex' export default { methods: { logout () { this.$confirm('您确定要退出吗, 是否继续?', '提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }).then(() => { window.sessionStorage.removeItem('rightsList') window.sessionStorage.removeItem('token') this.$router.push({ path: '/login' }) }).catch((err) => err) } }, computed: { ...mapState(['user']) } } </script> <style lang="less" scoped> .header { width: 100%; height: 100%; background-color: #00BFFF; display: flex; justify-content: space-between; padding: 0 15px; box-shadow: 0 2px 5px #00BFFF; &-left { height: 100%; display: flex; align-items: center; &__logo { width: 80px; height: 100%; padding: 5px; & > img { width: 100%; height: 100%; } } &__title { font-size: 20px; color: #fff; text-shadow: 10px 10px rgba(25, 255, 255, .3); } } &-right { height: 100%; display: flex; align-items: center; &__info { &-name { color: #F0FFFF; font-size: 16px; margin-right: 15px; } } } } </style>
Main.vue
<template> <div class="main"> <div class="bread"> <v-bread-crumb></v-bread-crumb> </div> <div class="content"> <router-view :key="+new Date()"></router-view> </div> </div> </template> <script> import BreadCrumb from '@/components/BreadCrumb.vue' export default { data () { return { activeKey: '' } }, components: { 'v-bread-crumb': BreadCrumb } } </script> <style lang="less" scoped> .content { width: 100%; height: 100%; background-color: #fff; padding: 20px 15px; border-radius: 5px; transform: translateY(15px); } </style>
在main文件夹里写两个页面,添加以及查找
Card.vue
<template> <div class="card animate__animated animate__zoomIn" v-loading="isOperate"> <div class="card-header"> <div class="card-header__title"> {{ isAdd ? '添加信息' : '删除信息' }} </div> <div class="card-header__close" @click="$emit('close')"> <i class="el-icon-close"></i> </div> </div> <div class="card-content"> <el-form :model="formData" :rules="rules" ref="form"> <el-form-item label="姓名:" prop="name"> <el-input v-model="formData.name"></el-input> </el-form-item> <el-form-item label="地址:" prop="address"> <el-input v-model="formData.address"></el-input> </el-form-item> <el-form-item label="爱好:" prop="likes"> <el-input v-model="formData.likes"></el-input> </el-form-item> <el-form-item> <el-button @click="$emit('close')" type="info">取消</el-button> <el-button @click="submitForm" type="primary">{{ isAdd ? '添加' : '修改' }}</el-button> </el-form-item> </el-form> </div> </div> </template> <script> export default { props: { isAdd: Boolean, isOperate: Boolean, rowData: { type: Object, default () { return {} } } }, data () { return { formData: { name: '', address: '', likes: '' }, rules: { name: { required: true, message: '请输入姓名', trigger: 'blur' }, address: { required: true, message: '请输入地址', trigger: 'blur' }, likes: { required: true, message: '请输入爱好', trigger: 'blur' } } } }, created () { if (Object.keys(this.rowData).length && !this.isAdd) { this.formData = { ...this.rowData } } else { this.formData = { name: '', address: '', likes: '' } } }, methods: { submitForm () { this.$refs.form.validate(valid => { if (valid) { this.$emit('submitAction', { isAdd: this.isAdd, rowData: this.formData }) } }) } } } </script> <style lang="less" scoped> .card { position: fixed; left: 50%; top: 50%; transform: translate(-50%, -50%); transform-origin: 0 0; z-index: 99; background: #fff; box-shadow: 0 0 20px gray; border-radius: 5px; padding: 15px 80px; &-header { display: flex; justify-content: space-between; margin: 0 -50px 30px; &__title { font-size: 20px; } &__close { cursor: pointer; transition: all .5s; &:hover { transform: rotate(-15deg); } } } .el-form-item { display: flex; &:last-child { display: block; text-align: right; } } .el-input { width: 200px; } } .animate__animated.animate__zoomIn { --animate-duration: .8s; } </style>
TopBar.vue
<template> <div class="top-bar"> <el-input placeholder="请输入内容,按回车键搜索..." @keydown.enter.native="handleSearch" v-model="inputValue"> <template slot="append"> 搜索 </template> </el-input> <el-button type="primary" @click="$emit('show')">添加商品</el-button> </div> </template> <script> export default { data () { return { inputValue: '' } }, methods: { handleSearch () { this.$emit('handleSearch', this.inputValue) } } } </script> <style lang="less" scoped> .top-bar { .el-input { width: 300px; } .el-button { margin-left: 15px; } } </style>
引入moke文件夹
// main.js import './mock'
在moke文件夹中写入两种权限的数据
index.js
引入mockjs插件
npm install --save mockjs
// 使用 Mock const Mock = require('mockjs') require('./my-radom') const Random = Mock.Random Mock.setup({ timeout: '500-1000' }) const list = [] for (let i = 0; i < 20; i++) { list.push({ id: i + 1, date: Random.date(), name: Random.cname(), address: Random.address(), likes: Random.likes() }) } const users = [ { id: 1, username: 'normal', password: 'normal', token: 'abcdefghijklmnopqrstuvwxyz', rights: [{ id: 1, authName: '用户管理', icon: 'icon-menu', children: [{ id: 11, authName: '用户项目1', path: '/menu/one', rights: ['view', 'edit', 'add', 'delete'] }, { id: 12, authName: '用户项目2', path: '/menu/two', rights: ['view'] }] }] }, { id: 2, username: 'admin', password: 'admin', token: 'abcdefghijklmnopqrstuvwxyz'.split('').reverse().join(''), rights: [{ id: 1, authName: '用户管理', icon: 'icon-menu', children: [{ id: 11, authName: '用户项目1', path: '/menu/one', rights: ['view', 'edit', 'add', 'delete'] }, { id: 12, authName: '用户项目2', path: '/menu/two', rights: ['view', 'edit', 'add', 'delete'] }] }, { id: 2, authName: '用户权限', icon: 'icon-menu', children: [{ id: 22, authName: '权限项目1', path: '/menu/three', rights: ['view', 'edit', 'add', 'delete'] }] }] } ] // 获取列表 Mock.mock('/list', 'get', options => { const { current } = JSON.parse(options.body) return list.slice(((current - 1) * 10), current * 10) }) // 总数 Mock.mock('/list/total', 'get', () => { return list.length }) // 查询 Mock.mock('/list/value', 'get', options => { const { value } = JSON.parse(options.body) const _list = list.filter(item => { if (item.name.includes(value) || item.address.includes(value) || item.likes.includes(value)) { return true } return false }) return { list: _list, total: _list.length } }) // 添加 Mock.mock('/list/add', 'post', options => { const { rowData } = JSON.parse(options.body) rowData.id = list[list.length - 1].id + 1 rowData.date = new Date().toLocaleDateString().replace(/\//g, '-') list.unshift(rowData) return rowData }) // 修改 Mock.mock('/list/update', 'put', options => { const { rowData } = JSON.parse(options.body) let _rowData = {} list.forEach((item, idx) => { if (item.id === rowData.id) { _rowData = rowData list[idx] = rowData } }) return _rowData }) // 删除 Mock.mock('/list/delete', 'delete', options => { const { id } = JSON.parse(options.body) const index = list.findIndex(item => item.id === id) const item = index > 0 ? list[index] : {} list.splice(index, 1) return item }) // 用户登录 Mock.mock('/login', 'post', options => { const { username, password } = JSON.parse(options.body) const user = users.find(item => { return item.username === username && item.password === password }) return user })
my-radom.js
// 使用 Mock var Mock = require('mockjs') Mock.Random.extend({ likes: function () { const likes = [ '喜欢打游戏,看电影,尤其是英雄联盟和欧美大片。', '喜欢做饭,尤其是西餐,喜欢做甜点,自己每次都吃得饱饱的。', '我最爱去游泳了,当然也喜欢潜水,在海底下看各种好看的鱼鱼。', '我最最喜欢的就是去旅游了,看沿途的风景,真是美呆了。', '我的爱好是打篮球,我很喜欢打篮球,我的偶像是科比。', '我超喜欢去蹦迪了,感觉整个身体都在那里放松了。', '哈哈哈,我喜欢的是和女孩子一起玩,因为男女搭配,干活不累嘛。', '我没啥爱好,唯一的爱好就是宅。', '我喜欢看动漫,更喜欢日漫,我可是一个二次元哦。', '我喜欢cosplay,喜欢cos动漫里的每一个角色。' ] return this.pick(likes) } }) Mock.Random.extend({ address: function () { const address = [ '深圳市南山区科技园南区R2-B三楼', '深圳南山区科技园汇景豪苑海欣阁', '深圳市南山区白石洲中信红树湾', '上海市普陀区金沙江路 1517 弄', '四川成都市中德英伦联邦C区', '北京市中南海老四合院靠左', '广州市中心中央银行33号' ] return this.pick(address) } })
在store文件夹的index.js中写入以下代码
import Vue from 'vue' import Vuex from 'vuex' Vue.use(Vuex) export default new Vuex.Store({ state: { breads: [], /* user: JSON.parse(window.sessionStorage.getItem('rightsList')||'{}') */ user:JSON.parse(window.sessionStorage.getItem('rightsList')||'{}') }, mutations: { addBread (state, bread) { const index = state.breads.findIndex(_bread => _bread.name === bread.name) if (index > -1) { state.breads.splice(index + 1, state.breads.length - index - 1) } else { state.breads.push(bread) } }, removeBread (state, bread) { state.breads = state.breads.filter(_bread => _bread !== bread) }, setUser(state,preload) { state.user = preload window.sessionStorage.setItem('rightsList',JSON.stringify(preload)) } /* setUser (state, user) { state.user = user window.sessionStorage.setItem('rightsList',JSON.stringify(user)) } */ }, actions: { }, modules: { } })
接下来做核心的页面部分
Page1.vue
<template> <div class="page"> <v-card :isOperate="isOperate" v-if="cardShow" @close="cardShow = false" @submitAction="submitAction" :isAdd="isAdd" :rowData="rowData"></v-card> <v-top-bar @show="showAddCard" @handleSearch="handleSearch"></v-top-bar> <el-table :data="tableData" v-loading="loading" border> <el-table-column type="index" label="#" width="50"> </el-table-column> <el-table-column prop="date" label="日期" width="180"> </el-table-column> <el-table-column prop="name" label="姓名" width="180"> </el-table-column> <el-table-column prop="address" label="地址"> </el-table-column> <el-table-column prop="likes" label="爱好"> </el-table-column> <el-table-column label="操作" width="200"> <template v-slot:default="scope"> <div class="btns"> <el-button type="primary" @click="handleUpdate(scope)"> <i class="el-icon-edit"></i> </el-button> <el-button type="danger" @click="handleDelete(scope)"> <i class="el-icon-delete"></i> </el-button> </div> </template> </el-table-column> </el-table> <el-pagination class="pagi" background layout="prev, pager, next" :total="total" @current-change="sizeChange"> </el-pagination> </div> </template> <script> import TopBar from '@/components/main/TopBar.vue' import Card from '@/components/main/Card.vue' import { mapMutations } from 'vuex' export default { data() { return { tableData: [], page: { size: 10, current: 1 }, total: 0, loading: false, title: '添加用户', cardShow: false, isAdd: true, rowData: {}, isOperate: false } }, methods: { ...mapMutations(['addBread']), sizeChange(data) { this.page.current = data this.getList(this.page.current) }, initData() { this.getList(this.page.current) this.getTotal() }, showAddCard() { this.isAdd = true this.cardShow = true }, submitAction(info) { // 添加或修改项目 this.isOperate = true if (info.isAdd) { this.$api.addList({ rowData: info.rowData }).then(() => { this.initData() this.cardShow = false this.isOperate = false this.$message({ message: '添加成功!', type: 'success', customClass: 'v-message', offset: 100 }) }) } else { this.$api.updateList({ rowData: info.rowData }).then(() => { this.initData() this.cardShow = false this.isOperate = false this.$message({ message: '修改成功!', type: 'success', customClass: 'v-message', offset: 100 }) }) } }, handleSearch(value) { if (value === '') { this.$message({ message: '警告哦,这是一条警告消息', type: 'warning' }); this.getList() return } this.loading = true this.$api.getListByValue({ value }).then(res => { this.loading = false this.tableData = res.data.list this.total = res.data.total }) }, handleUpdate(scope) { this.isAdd = false this.cardShow = true this.rowData = scope.row }, handleDelete(scope) { this.$confirm('你确定要删除这一条数据吗?', '警告', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'danger' }).then(() => { this.$api.deleteList({ id: scope.row.id }).then(res => { this.initData() this.$message({ message: '删除成功!', type: 'success', customClass: 'v-message', offset: 100 }) }) }).catch(err => err) }, getList(current) { this.loading = true this.$api.getList({ current }).then(res => { this.tableData = res.data this.loading = false document.querySelector('.page').scrollTop = 0 }) }, getTotal() { this.$api.getTotal().then(res => { this.total = res.data }) } }, created() { if (this.$route.query.name) { this.addBread({ name: this.$route.query.name, path: this.$route.path }) } this.initData() }, components: { 'v-top-bar': TopBar, 'v-card': Card } } </script> <style lang="less"> .page { height: 100%; overflow-y: scroll; } .el-table { transform: translateY(15px); } .pagi { margin: 30px 0; text-align: center; } .v-message { left: 58%; } </style
下面写api
http.js 这个页面主要封装的是axios
import Axios from 'axios' const axios = Axios.create({ // baseURL: process.env.NODE_ENV === 'development' ? '' : '', }) export default (url, method = 'get', data = {}) => { return axios({ url, method, data }) }
index.js
require.context是什么
一个webpack的api,通过执行require.context函数获取一个特定的上下文,主要用来实现自动化导入模块,在前端工程中,如果遇到从一个文件夹引入很多模块的情况,可以使用这个api,它会遍历文件夹中的指定文件,然后自动导入,使得不需要每次显式的调用import导入模块
把那三个js导入到这个index.js文件中 (自动化的导入,不需要一个一个的去引入页面)
这个方法有 3 个参数:
读取文件的路径
是否遍历文件的子目录(上述的图片中只有当前目录没有子目录所以为false)
以及一个匹配文件的正则表达式。
const context = require.context('./', false, /.js$/) const modules = {} console.log(context.keys()); context.keys().forEach(fileName => { if (!['./index.js', './http.js'].includes(fileName)) { Object.assign(modules, context(fileName)) } }) export default modules page.js 页面所对应的接口全部获取过来 import http from './http' export const getList = data => { return http('/list', 'get', data) } export const getTotal = () => { return http('/list/total') } export const getListByValue = data => { return http('/list/value', 'get', data) } export const addList = data => { return http('/list/add', 'post', data) } export const updateList = data => { return http('/list/update', 'put', data) } export const deleteList = data => { return http('/list/delete', 'delete', data) }
user.js
import http from './http' export const login = data => { return http('/login', 'post', data) }
在此路径添加common.js
export default { filters: { toString (str) { return str.toString() } } }
最后把Home页面进行绑定
<template> <div class="home"> <el-container> <el-header> <v-header></v-header> </el-header> <el-container class="v-container"> <el-aside width="200px"> <v-aside></v-aside> </el-aside> <el-main> <v-main></v-main> </el-main> </el-container> </el-container> </div> </template> <script> import Header from '@/components/Header.vue' import Aside from '@/components/Aside.vue' import Main from '@/components/Main.vue' export default { name: 'Home', components: { 'v-header': Header, 'v-aside': Aside, 'v-main': Main } } </script> <style lang="less" scoped> .home { height: 100%; } .el-container { height: 100%; } .v-container { height: calc(100% - 60px); } .el-header { padding: 0; z-index: 9; } .el-aside { height: 100%; background-color: #00BFFF; } .el-main { height: 100%; background-color: #eee; .main { height: 100%; overflow: hidden; } } </style>