文章目录
前言项目文件目录apimockServehome.jspermission.js index.jsmock.jsuser.js assertcomponentsCommonAside.vueCommonHeader.vueCommonTags.vue dataechartsDataorder.jsuser.jsvideo.js mockDatatableData.jsuserData.jsvideoData.js CountData.jsMenuData.jsTableData.jsTableLabel.js routerindex.js storeindex.jstab.js utilsrequest.js ViewsHome.vueLogin.vueMain.vueMall.vuePageOne.vuePageTwo.vueUser.vue App.vuemain.js要安装的依赖已上传到Gitee参考视频: VUE项目,VUE项目实战,vue后台管理系统,前端面试,前端面试项目
前言
本来不打算用博客的方式记录代码的,想用git把它上传到代码仓库。但是由于对git的使用不太熟悉,把写得完善的项目代码初始化掉了!!非常崩溃…后来废了很大力气才把代码找回来。
为了以后把代码搞丢后还能找回来,还是要写个博客来记录一下代码。
项目链接:/s/1fTh4m_OkqV2PIuWdCgE-QQ
提取码:35sv
项目文件目录
api
mockServe
home.js
// mock数据模拟import Mock from 'mockjs'// 导入数据import videoData from '../../data/mockData/videoData'import userData from '../../data/mockData/userData'import tableData from '../../data/mockData/tableData'// 图表数据let List =[]// 直接导出export default {getStatisticalData: () => {//Mock.Random.float 产生随机数100到8000之间 保留小数 最小0位 最大0位for (let i = 0; i < 7; i++) {List.push(Mock.mock({苹果: Mock.Random.float(100, 8000, 0, 0),vivo: Mock.Random.float(100, 8000, 0, 0),oppo: Mock.Random.float(100, 8000, 0, 0),魅族: Mock.Random.float(100, 8000, 0, 0),三星: Mock.Random.float(100, 8000, 0, 0),小米: Mock.Random.float(100, 8000, 0, 0)}))}// 返回给浏览器的数据return {code: 20000,data: {// 饼图videoData,// 柱状图userData,// 折线图orderData: {date: ['1001', '1002', '1003', '1004', '1005', '1006', '1007'],data: List},tableData}}}}
permission.js
import Mock from 'mockjs'export default {getMenu: config => {const {username, password } = JSON.parse(config.body)// 先判断用户是否存在// 判断账号和密码是否对应if (username === 'admin' && password === 'admin') {return {code: 20000,data: {menu: [{path: '/home',name: 'home',label: '首页',icon: 's-home',url: 'Home.vue'},{path: '/mall',name: 'mall',label: '商品管理',icon: 'video-play',url: 'Mall.vue'},{path: '/user',name: 'user',label: '用户管理',icon: 'user',url: 'User.vue'},{label: '其他',icon: 'location',children: [{path: '/page1',name: 'page1',label: '页面1',icon: 'setting',url: 'PageOne.vue'},{path: '/page2',name: 'page2',label: '页面2',icon: 'setting',url: 'PageTwo.vue'}]}],token: Mock.Random.guid(),message: '获取成功'}}} else if (username === 'xiaoxiao' && password === 'xiaoxiao') {return {code: 20000,data: {menu: [{path: '/home',name: 'home',label: '首页',icon: 's-home',url: 'Home.vue'},{path: '/video',name: 'video',label: '商品管理',icon: 'video-play',url: 'Mall.vue'}],token: Mock.Random.guid(),message: '获取成功'}}} else {return {code: -999,data: {message: '密码错误'}}}}}
index.js
import http from '../utils/request'// 请求首页数据,直接把这个对象导出export const getData = () => {// 返回一个promisereturn http.get('/home/getData')}// 下面四个:用户管理-后端-网络请求接口export const getUser = (params) => {return http.get('/user/get/', params)}export const createUser = (data) => {return http.post('/user/create', data)}export const deleteUser = (data) => {return http.post('/user/del', data)}export const updateUser = (data) => {return http.post('/user/update', data)}// 登录权限export const getMenu = (data) => {return http.post('/permission/getMenu',data)}
mock.js
import Mock from 'mockjs'import homeMock from '../api/mockServe/home'import user from './user'import permission from './mockServe/permission'// 定义mock拦截Mock.mock('/api/home/getData',homeMock)// 用户管理:增删查改Mock.mock(/\/api\/user\/get/,user.getUserList)Mock.mock('/api/user/create','post',user.createUser)Mock.mock('/api/user/update','post',user.updateUser)Mock.mock('/api/user/del','post',user.deleteUser)// 登录权限Mock.mock(/api\/permission\/getMenu/,'post',permission.getMenu)
user.js
import Mock from 'mockjs'// get请求从config.url获取参数,post从config.body中获取参数function param2Obj (url) {const search = url.split('?')[1]if (!search) {return {}}return JSON.parse('{"' +decodeURIComponent(search).replace(/"/g, '\\"').replace(/&/g, '","').replace(/=/g, '":"') +'"}')}let List = []const count = 200for (let i = 0; i < count; i++) {List.push(Mock.mock({id: Mock.Random.guid(),name: ame(),addr: Mock.mock('@county(true)'),'age|18-60': 1,birth: Mock.Random.date(),sex: Mock.Random.integer(0, 1)}))}export default {/*** 获取列表* 要带参数 name, page, limt; name可以不填, page,limit有默认值。* @param name, page, limit* @return {{code: number, count: number, data: *[]}}*/getUserList: config => {const {name, page = 1, limit = 20 } = param2Obj(config.url)// console.log('name:' + name, 'page:' + page, '分页大小limit:' + limit)const mockList = List.filter(user => {if (name && user.name.indexOf(name) === -1 && user.addr.indexOf(name) === -1) return falsereturn true})const pageList = mockList.filter((item, index) => index < limit * page && index >= limit * (page - 1))return {code: 20000,count: mockList.length,list: pageList}},/*** 增加用户* @param name, addr, age, birth, sex* @return {{code: number, data: {message: string}}}*/createUser: config => {const {name, addr, age, birth, sex } = JSON.parse(config.body)console.log(JSON.parse(config.body))List.unshift({id: Mock.Random.guid(),name: name,addr: addr,age: age,birth: birth,sex: sex})return {code: 20000,data: {message: '添加成功'}}},/*** 删除用户* @param id* @return {*}*/deleteUser: config => {const {id } = JSON.parse(config.body)if (!id) {return {code: -999,message: '参数不正确'}} else {List = List.filter(u => u.id !== id)return {code: 20000,message: '删除成功'}}},/*** 批量删除* @param config* @return {{code: number, data: {message: string}}}*/batchremove: config => {let {ids } = param2Obj(config.url)ids = ids.split(',')List = List.filter(u => !ids.includes(u.id))return {code: 20000,data: {message: '批量删除成功'}}},/*** 修改用户* @param id, name, addr, age, birth, sex* @return {{code: number, data: {message: string}}}*/updateUser: config => {const {id, name, addr, age, birth, sex } = JSON.parse(config.body)const sex_num = parseInt(sex)List.some(u => {if (u.id === id) {u.name = nameu.addr = addru.age = ageu.birth = birthu.sex = sex_numreturn true}})return {code: 20000,data: {message: '编辑成功'}}}}
assert
这些图片在链接里:
components
CommonAside.vue
<template><el-menu default-active="1-4-1" class="el-menu-vertical-demo" @open="handleOpen" @close="handleClose":collapse="isCollapse" background-color="#545c64" text-color="#fff" active-text-color="#ffd04b"><!-- 要放到导航栏里面 --><h3>{{ isCollapse ? "后台" : "通用后台管理系统" }}</h3><!-- 观察数据,我们发现name是唯一标识 --><!-- 查看文档,index是唯一标识 --><el-menu-item @click="clickItem(item)" v-for="item in noChildren" :key="item.name" :index="item.name"><!-- 这里是字体图标,用模板字符串拼接,注意要动态绑定 --><i :class="`el-icon-${item.icon}`"></i><span slot="title">{{ item.label }}</span></el-menu-item><el-submenu v-for="item in hasChildren" :key="item.label" :index="item.label"><template slot="title"><i :class="`el-icon-${item.icon}`"></i><span slot="title">{{ item.label }}</span></template><el-menu-item-group v-for="subItem in item.children" :key="subItem.name"><el-menu-item @click="clickItem(subItem)" :index="subItem.name">{{ subItem.label }}</el-menu-item></el-menu-item-group></el-submenu></el-menu></template><style lang="less" scoped>.el-menu-vertical-demo:not(.el-menu--collapse) {width: 200px;min-height: 400px;}.el-menu {height: 100vh;// Aside和Header之间没有边框缝隙border-right: none;h3 {text-align: center;line-height: 48px;color: #fff;font-size: 16px;font-weight: 400;}}</style><script>import cookie from 'js-cookie'export default {data() {return {};},methods: {handleOpen(key, keyPath) {console.log(key, keyPath);},handleClose(key, keyPath) {console.log(key, keyPath);},clickItem(item) {// 防止自己跳自己的报错if (this.$route.path !== item.path && !(this.$route.path === '/home' && (item.path === '/'))) {this.$router.push(item.path)}// 面包屑this.$mit('SelectMenu', item)}},computed: {noChildren() {// 如果没有children则返回true,会被过滤器留下return this.MenuData.filter(item => !item.children)},hasChildren() {return this.MenuData.filter(item => item.children)},// 要放到计算属性,自动计算isCollapse() {return this.$store.state.tab.isCollapse},// 获取菜单MenuData() {return JSON.parse(cookie.get('menu')) || this.$store.state.tab.menu}}}</script>
CommonHeader.vue
<template><div class="header-container"><div class="l-content"><el-button @click="handleMenu" icon="el-icon-menu" size="mini"></el-button><!-- 面包屑 --><el-breadcrumb separator="/"><el-breadcrumb-item v-for="item in tags" :key="item.path" :to="{ path: item.path }">{{ item.label }}</el-breadcrumb-item></el-breadcrumb></div><div class="r-content"><el-dropdown @command="handleClick"><span class="el-dropdown-link"><img class="user" src="../assets/images/user.png" alt=""></span><el-dropdown-menu slot="dropdown"><el-dropdown-item>个人信息</el-dropdown-item><el-dropdown-item command="logout">退出</el-dropdown-item></el-dropdown-menu></el-dropdown></div></div></template><script>import {mapState } from 'vuex'import Cookie from 'js-cookie'export default {methods: {handleMenu() {// 相当于调用这个方法this.$mit('CollapseMenu')},handleClick(command) {if (command === 'logout') {Cookie.remove('token')this.$router.push('/login')}}},computed: {...mapState({tags: state => state.tab.tabList})}}</script><style lang="less" scoped>.header-container {background-color: #333;height: 60px;// 让按钮和头像居中display: flex;justify-content: space-between;align-items: center;// 不要紧贴边框padding: 0 20px;.el-dropdown-link {cursor: pointer;color: #409EFF;.user {width: 40px;height: 40px;// 50%变圆形border-radius: 50%;}}}.l-content {display: flex;// 上下居中align-items: center;.el-breadcrumb {margin-left: 15px;// deep 强制生效/deep/.el-breadcrumb__item {.el-breadcrumb__inner {&.is-link {color: #666;}}&:last-child {.el-breadcrumb__inner {color: #fff;}}}}}</style>
CommonTags.vue
<template><div class="tabs"><!-- closable是否可删除:除了"首页"都可删 --><!-- effect:主题,当前主题是dark,其他事plain --><el-tag v-for="(item, index) in tags" :key="item.path" :closable="item.name !== 'home'":effect="item.name === $route.name ? 'dark' : 'plain'" @click="changeMenu(item)"@close="handleClose(item, index)">{{ item.label }}</el-tag></div></template><script>import {mapState } from 'vuex'export default {methods: {changeMenu(item) {this.$router.push({name: item.name })},handleClose(item, index) {// 删除面包屑数据this.$mit('closeTag', item)// 如果删除的刚好是自己if (item.name === this.$route.name) {const length = this.tags.length// 如果删除的是最后一个:跳到前一个if (length === index) {this.$router.push({name: this.tags[index - 1].name })}// 不是最后一个:往后一个else {this.$router.push({name: this.tags[index].name })}}}},computed: {...mapState({tags: state => state.tab.tabList})}}</script><style lang="less" scoped>.tabs{padding: 20px 20px 0 20px;.el-tag{margin-right: 15px;// 鼠标悬停:小手cursor: pointer;}}</style>
data
echartsData
order.js
const order = {legend: {// 图例文字颜色textStyle: {color: "#333",},},grid: {left: "20%",},// 提示框tooltip: {trigger: "axis",},xAxis: {type: "category", // 类目轴data: [],axisLine: {lineStyle: {color: "#17b3a3",},},axisLabel: {interval: 0,color: "#333",},},yAxis: [{type: "value",axisLine: {lineStyle: {color: "#17b3a3",},},},],color: ["#2ec7c9", "#b6a2de", "#5ab1ef", "#ffb980", "#d87a80", "#8d98b3"],series: [],}export default order
user.js
const user = {legend: {// 图例文字颜色textStyle: {color: "#333",},},grid: {left: "20%",},// 提示框tooltip: {trigger: "axis",},xAxis: {type: "category", // 类目轴data: [],axisLine: {lineStyle: {color: "#17b3a3",},},axisLabel: {interval: 0,color: "#333",},},yAxis: [{type: "value",axisLine: {lineStyle: {color: "#17b3a3",},},},],color: ["#2ec7c9", "#b6a2de"],series: [],}export default user
video.js
const video = {tooltip: {trigger: "item",},color: ["#0f78f4","#dd536b","#9462e5","#a6a6a6","#e1bb22","#39c362","#3ed1cf",],series: [],}export default video
mockData
tableData.js
const tableData = [{name: 'oppo',todayBuy: 500,monthBuy: 3500,totalBuy: 22000},{name: 'vivo',todayBuy: 300,monthBuy: 2200,totalBuy: 24000},{name: '苹果',todayBuy: 800,monthBuy: 4500,totalBuy: 65000},{name: '小米',todayBuy: 1200,monthBuy: 6500,totalBuy: 45000},{name: '三星',todayBuy: 300,monthBuy: 2000,totalBuy: 34000},{name: '魅族',todayBuy: 350,monthBuy: 3000,totalBuy: 22000}]export default tableData
userData.js
// 柱状图const userData = [{date: '周一',new: 5,active: 200},{date: '周二',new: 10,active: 500},{date: '周三',new: 12,active: 550},{date: '周四',new: 60,active: 800},{date: '周五',new: 65,active: 550},{date: '周六',new: 53,active: 770},{date: '周日',new: 33,active: 170}]export default userData
videoData.js
// 饼图const videoData = [{name: '小米',value: 2999},{name: '苹果',value: 5999},{name: 'vivo',value: 1500},{name: 'oppo',value: 1999},{name: '魅族',value: 2200},{name: '三星',value: 4500}]export default videoData
CountData.js
const CountData = [{name: "今日支付订单",value: 1234,icon: "success",color: "#2ec7c9",},{name: "今日收藏订单",value: 210,icon: "star-on",color: "#ffb980",},{name: "今日未支付订单",value: 1234,icon: "s-goods",color: "#5ab1ef",},{name: "本月支付订单",value: 1234,icon: "success",color: "#2ec7c9",},{name: "本月收藏订单",value: 210,icon: "star-on",color: "#ffb980",},{name: "本月未支付订单",value: 1234,icon: "s-goods",color: "#5ab1ef",},]export default CountData
MenuData.js
const MenuData= [{path: '/',name: 'home',label: '首页',icon: 's-home',url: 'Home/Home'},{path: '/mall',name: 'mall',label: '商品管理',icon: 'video-play',url: 'MallManage/MallManage'},{path: '/user',name: 'user',label: '用户管理',icon: 'user',url: 'UserManage/UserManage'},{label: '其他',icon: 'location',children: [{path: '/page1',name: 'page1',label: '页面1',icon: 'setting',url: 'Other/PageOne'},{path: '/page2',name: 'page2',label: '页面2',icon: 'setting',url: 'Other/PageTwo'}]}]export default MenuData
TableData.js
const TableData = [{name: 'oppo',todayBuy: 100,monthBuy: 300,totalBuy: 800},{name: 'vivo',todayBuy: 100,monthBuy: 300,totalBuy: 800},{name: '苹果',todayBuy: 100,monthBuy: 300,totalBuy: 800},{name: '小米',todayBuy: 100,monthBuy: 300,totalBuy: 800},{name: '三星',todayBuy: 100,monthBuy: 300,totalBuy: 800},{name: '魅族',todayBuy: 100,monthBuy: 300,totalBuy: 800}]export default TableData
TableLabel.js
const TableLabel={name:'课程',todayBuy:'今日购买',monthBuy:'本月购买',totalBuy:'总购买'}export default TableLabel
router
index.js
import Vue from "vue";import VueRouter from "vue-router";import Main from '../Views/Main'// import Home from '../Views/Home.vue'// import Mall from '../Views/Mall.vue'// import User from '../Views/User.vue'// import PageOne from '../Views/PageOne.vue'// import PageTwo from '../Views/PageTwo.vue'import Login from '../Views/Login.vue'import Cookie from 'js-cookie'Vue.use(VueRouter)const routes = [// 主路由{path: '/',name:'Main',component: Main,redirect: '/home', // 重定向children: [// 子路由// { path: '/home', name: 'home', component: Home }, // 首页// { path: '/user', name: 'user', component: User }, // 用户管理// { path: '/mall', name: 'mall', component: Mall }, // 商品管理// { path: '/page1', name: 'page1', component: PageOne }, // 页面1// { path: '/page2', name: 'page2', component: PageTwo }, // 页面2]},{path: '/login',name: 'login',component: Login}]const router = new VueRouter({routes})// 路由守卫:全局前置导航守卫router.beforeEach((to, from, next) => {// 获取tokenconst token = Cookie.get('token')if (!token && to.name !== 'login') {next({name: 'login' })} else if (token && to.name === 'login') {next({name: 'home' })} else {next()}})export default router
store
index.js
import Vue from "vue";import Vuex from 'vuex';import tab from './tab';Vue.use(Vuex)// 创建Vuex实例并导出export default new Vuex.Store({modules:{tab}})
tab.js
import Cookie from "js-cookie"export default {state: {isCollapse: false,//导航栏是否折叠tabList: [{path: '/',name: 'home',label: '首页',icon: 's-home',url: 'Home/Home'}],//面包屑的数据:点了哪个路由,首页是一定有的menu: [],//不同用户的菜单},mutations: {// 修改导航栏展开和收起的方法CollapseMenu(state) {state.isCollapse = !state.isCollapse},// 更新面包屑的数据SelectMenu(state, item) {// 如果点击的不在面包屑数据中,则添加const index = state.tabList.findIndex(val => val.name === item.name)if (index === -1) {state.tabList.push(item)}},// 删除tag:删除tabList中对应的itemcloseTag(state, item) {// 要删除的是state.tabList中的itemconst index = state.tabList.findIndex(val => val.name === item.name)state.tabList.splice(index, 1)},// 设置不同用户的菜单setMenu(state, val) {state.menu = valCookie.set('menu', JSON.stringify(val))},// 动态添加路由addMenu(state, router) {// 判断Cookieif (!Cookie.get('menu')) returnconst menu = JSON.parse(Cookie.get('menu'))state.menu = menuconst menuArray = []// 组装路由menu.forEach(item => {// 判断是否有子路由if (item.children) {item.children = item.children.map(itemm => {ponent = () => import(`../Views/${itemm.url}`)return itemm})menuArray.push(...item.children)} else {ponent = () => import(`../Views/${item.url}`)menuArray.push(item)}});console.log(menuArray, 'menuArray');menuArray.forEach(item => {router.addRoute('Main', item)})}}}
utils
request.js
import axios from "axios";// 封装一个axios实例const http = axios.create({// 通用请求的地址前缀baseURL: '/api',// 超时时间timeout: 100000})// 请求拦截器http.interceptors.request.use(function (config) {// 在发送请求之前做什么return config;}, function (error) {// 对请求错误做什么return Promise.reject(error);})// 添加响应拦截器http.interceptors.response.use(function (response) {// 对响应数据做什么return response;}, function (error) {// 对响应错误做什么return Promise.reject(error);})export default http
Views
Home.vue
<template><el-row><el-col :span="8"><!-- user卡片 --><el-card><div class="user"><img src="../assets/images/user.png" alt=""><div class="userInfo"><p div class="name">Admin</p><p div class="access">超级管理员</p></div></div><div class="loginInfo"><p>上次登录时间:<span>-7-19</span></p><p>上次登陆地点:<span>武汉</span></p></div></el-card><!-- table卡片 --><el-card style="margin-top: 20px;"><el-table :data="TableData" style="width: 100%"><!-- 这里的val,key对应的是对象里的 --><el-table-column v-for="(value, key) in TableLabel" :prop="key" :label="value"></el-table-column></el-table></el-card></el-col><el-col :span="16"><div class="num"><el-card v-for="item in CountData" :key="item.name" :body-style="{ display: 'flex', padding: 0 }"><i class="icon" :class="`el-icon-${item.icon}`" :style="{ backgroundColor: item.color }"></i><div class="details"><p class="price">{{ priceFormate(item.value) }}</p><p class="desc">{{ item.name }}</p></div></el-card></div><!-- echarts图表 --><div style="margin-left:20px"><!-- 折线图 --><el-card style="height:280px"><div ref="echarts1" style="height:280px"></div></el-card><div class="graph"><!-- 柱状图 --><el-card style="height:280px"><div ref="echarts2" style="height:280px"></div></el-card><!-- 饼状图 --><el-card style="height:320px"><div ref="echarts3" style="height:320px"></div></el-card></div></div></el-col></el-row></template><script>import TableLabel from '../data/TableLabel'import CountData from '../data/CountData'import {getData } from '../api/index'import * as echarts from 'echarts'// echarts的配置数据import order from '../data/echartsData/order'import user from '../data/echartsData/user'import video from '../data/echartsData/video'export default {data() {return {TableData: [],TableLabel,CountData}},methods: {priceFormate(price) {return "¥" + price}},mounted() {getData().then((data) => {// console.log(data);this.TableData = data.data.getStatisticalData.data.tableData// echarts图表// 折线图// 基于准备好的dom,初始化echarts实例const echarts1 = echarts.init(this.$refs.echarts1)var echarts1Option = order// ES6解构语法var {orderData, userData, videoData } = data.data.getStatisticalData.data// 获取x轴:要求是一个对象const xAxis = Object.keys(orderData.data[0])const xAxisData = {data: xAxis}// 配置echarts1Option.legend = xAxisDataecharts1Option.xAxis = xAxisDataecharts1Option.yAxis = {}echarts1Option.series = []// 配置seriesxAxis.forEach(key => {echarts1Option.series.push({name: key,type: 'line',// key对应的orderData的所有值data: orderData.data.map(item => item[key])})})// 使用刚指定的配置项和数据显示图表。echarts1.setOption(echarts1Option);// 柱状图const echarts2 = echarts.init(this.$refs.echarts2)var echarts2Option = user// 配置echarts2Option.xAxis.data = userData.map(item => item.date)echarts2Option.series = [{name: '新增用户',data: userData.map(item => item.new),// 类型:bar是柱状图 type: 'bar'},{name: '活跃用户',data: userData.map(item => item.active),type: 'bar'}]echarts2.setOption(echarts2Option);// 饼状图const echarts3 = echarts.init(this.$refs.echarts3)var echarts3Option = videoecharts3Option.series = {data: videoData,type: 'pie'}echarts3.setOption(echarts3Option);})}}</script><style lang="less" scoped>.user {// 垂直居中display: flex;align-items: center;// 外边距:分割线距离loginInfo的距离margin-bottom: 20px;// 内边距:分割线距离User的距离padding-bottom: 20px;border-bottom: 1px solid #ccc;img {width: 150px;height: 150px;border-radius: 50%;margin-right: 40px;}.userInfo {.name {font-size: 32px;margin-bottom: 10px;}.access {color: #999999;}}}.loginInfo {p {line-height: 28px;font-size: 14px;color: #999999;span {color: #666666;margin-left: 60px;}}}.num {display: flex;// 要换行flex-wrap: wrap;// 从头到尾均匀排列justify-content: space-between;margin-left: 20px;.el-card {width: 32%;margin-bottom: 20px;.icon {width: 80px;height: 80px;line-height: 80px;text-align: center;font-size: 30px;color: #fff;}.details {// 竖着排且居中display: flex;flex-direction: column;justify-content: center;margin-left: 15px;.price {font-size: 30px;margin-bottom: 10px;line-height: 30px;height: 30px;}.desc {font-size: 14px;color: #999;text-align: center;}}}}.graph {display: flex;// 两个靠边justify-content: space-between;margin-top: 20px;.el-card {width: 49%;}}</style>
Login.vue
<template><el-form ref="form" class="login_container" :model="login" status-icon :rules="rules" label-width="70px"><!-- h3要放在里面:只能有一个根,且title也是表单的一部分 --><h3 class="login_title">用户登录</h3><!-- prop对应rules里的键 --><el-form-item label="用户名" prop="username"><el-input v-model="login.username" autocomplete="off"></el-input></el-form-item><el-form-item label="密码" prop="password"><el-input type="password" v-model="login.password" autocomplete="off"></el-input></el-form-item><el-form-item><el-button @click="submit" type="primary" style="margin-left:30px;margin-top:10px">提交</el-button></el-form-item></el-form></template><script>import Cookie from 'js-cookie'import {getMenu } from '../api/index'export default {data() {return {// 登陆数据login: {username: '',password: ''},// 校验规则rules: {username: [{required: 'true', message: '请输入用户名', trigger: 'blur' }],password: [{required: 'true', message: '请输入用户名', trigger: 'blur' }]}}},methods: {submit() {// 表单的校验this.$refs.form.validate((valid) => {if (valid) {// 传入表单数据getMenu(this.login).then((data) => {// console.log(data);if(data.data.code===20000){// 记录cookieCookie.set('token',data.data.data.token)// 设置菜单this.$mit('setMenu',data.data.data.menu)// 动态添加路由this.$mit('addMenu',this.$router)// 跳转到首页this.$router.push('/home')}else{// 验证失败的弹窗this.$message.error(data.data.data.message);}})}})}}}</script><style lang="less" scoped>.login_container {width: 350px;border: 1px solid #eaeaea;// 居中margin: 180px auto;padding: 35px 35px 15px 35px;// 让padding在width里面box-sizing: border-box;border-radius: 15px;background-color: #fff;box-shadow: 0 0 25px #cac6c6;.login_title {color: #505458;// 左右居中text-align: center;margin-bottom: 40px;}.el-input {width: 198px;}}</style>
Main.vue
<template><el-container><!-- width自适应,不然header与aside有间隔 --><el-aside width="auto"><common-aside/></el-aside><el-container><el-header><common-header/></el-header><common-tags/><el-main><router-view></router-view></el-main></el-container></el-container></template><script>import CommonAside from '../components/CommonAside.vue'import CommonHeader from '../components/CommonHeader.vue'import CommonTags from '../components/CommonTags.vue'export default {data(){return{}},components:{CommonAside,CommonHeader,CommonTags}}</script><style lang="less" scoped>.el-header{padding:0;}</style>
Mall.vue
<template><h1>Mall</h1></template><script>export default {}</script><style></style>
PageOne.vue
<template><h1>Page1</h1></template><script>export default {}</script><style></style>
PageTwo.vue
<template><h1>Page2</h1></template><script>export default {}</script><style></style>
User.vue
<template><div class="manage"><div class="manage-header"><!-- 新增按钮 --><el-button type="primary" @click="handlecreate">+ 新增</el-button><!-- 对话框:点击新增或编辑才会弹出表单 --><!-- :before-close="closeDialog" 点击关闭的x之前要做的事情 --><el-dialog :title="modalType == 0 ? '新建' : '编辑'" :visible.sync="dialogVisible" width="50%":before-close="closeDialog"><!-- 表单Form --><!-- ref=form:为了通过this.$refs调用组件的方法 --><el-form :inline="true" :model="form" :rules="rules" ref="form" label-width="80px"><!-- 每一项表单域:el-form-item --><el-form-item label="姓名" prop="name"><el-input placeholder="请输入姓名" v-model="form.name"></el-input></el-form-item><el-form-item label="年龄" prop="age"><el-input placeholder="请输入年龄" v-model="form.age"></el-input></el-form-item><el-form-item label="性别" prop="sex"><el-select v-model="form.sex" placeholder="请输入性别"><el-option label="男" :value="1"></el-option><el-option label="女" :value="0"></el-option></el-select></el-form-item><el-form-item label="出生日期"><el-form-item prop="birth"><el-date-picker type="date" placeholder="请选择日期" v-model="form.birth" value-format="yyyy-MM-DD"></el-date-picker></el-form-item></el-form-item><el-form-item label="地址" prop="addr"><el-input placeholder="请输入地址" v-model="form.addr"></el-input></el-form-item></el-form><div slot="footer" class="dialog-footer"><el-button @click="closeDialog">取 消</el-button><el-button type="primary" @click="submit">确 定</el-button></div></el-dialog><!-- 搜索框 --><el-form :inline="true"><el-form-item><el-input v-model="searchForm.name" placeholder="请输入名称"></el-input></el-form-item><el-form-item><el-button type="primary" @click="search">查询</el-button></el-form-item></el-form></div><div class="common-table"><!-- 用户数据Table --><el-table :data="tableData" stripe style="width: 100%" height="90%"><el-table-column prop="name" label="姓名"></el-table-column><el-table-column prop="age" label="年龄"></el-table-column><el-table-column prop="sex" label="性别"><template slot-scope="scope"><span>{{ scope.row.sex == 1 ? '男' : '女' }}</span></template></el-table-column><el-table-column prop="birth" label="出生日期"></el-table-column><el-table-column prop="addr" label="地址"></el-table-column><!-- 自定义列 --><el-table-column label="操作"><template slot-scope="scope"><el-button @click="handleEdit(scope.row)">编辑</el-button><el-button type="danger" @click="handleDelete(scope.row)">删除</el-button></template></el-table-column></el-table><!-- 分页 --><div class="pager"><el-pagination layout="prev, pager, next" :total="total" @current-change="currentChange"></el-pagination></div></div></div></template><script>import {getUser, createUser, deleteUser, updateUser } from '../api/index'export default {data() {return {// 表单绑定的数据form: {name: '',age: '',sex: '',birth: '',addr: ''},// 表单验证规则rules: {name: [{required: true, message: '请输入名称', trigger: 'blur' }],age: [{required: true, message: '请输入年龄', trigger: 'blur' }],sex: [{required: true, message: '请输入性别', trigger: 'blur' }],birth: [{required: true, message: '请输入日期', trigger: 'blur' }],addr: [{required: true, message: '请输入地址', trigger: 'blur' }],},// 表单是否打开dialogVisible: false,// 列表数据tableData: [],// 打开表单:新建0,编辑1modalType: 0,// 分页的对象pageData: {page: 1,limit: 20},// 分页页数total: 0,// 搜索框表单searchForm: {name: ''}}},methods: {// 获取列表数据getList() {// 由接口文档知传入一个对象:要返回的是当前页面数据和搜索到的数据的交集getUser({params: {...this.pageData, ...this.searchForm } }).then((data) => {this.tableData = data.data.listthis.total = data.data.count || 0})},// 表单提交submit() {// 要用箭头函数,若用function会报错,不知道为什么this.$refs.form.validate((valid) => {// 符合校验if (valid) {// 提交数据if (this.modalType === 0) {// 新增createUser(this.form).then(() => {this.getList()})} else {// 编辑updateUser(this.form).then(() => {this.getList()})}// 清空,关闭this.closeDialog()}})},// 关闭对话框closeDialog() {// 先重置this.$refs.form.resetFields()// 后关闭this.dialogVisible = false},// 编辑按钮handleEdit(index) {this.modalType = 1this.openForm()// 深拷贝this.form = JSON.parse(JSON.stringify(index))},// 删除按钮handleDelete(index) {this.$confirm('此操作将永久删除该用户, 是否继续?', '提示', {confirmButtonText: '确定',cancelButtonText: '取消',type: 'warning'}).then(() => {// 删除操作:根据后端接口,参数是对象,id是唯一标识符deleteUser({id: index.id }).then(() => {this.$message({type: 'success',message: '删除成功!'})this.getList()});}).catch(() => {// 点击取消:不删除了this.$message({type: 'info',message: '已取消删除'});});},// 新建按钮handlecreate() {this.modalType = 0this.openForm()},// 打开表单openForm() {this.dialogVisible = true},// 改变页码currentChange(val) {this.pageData.page = valthis.getList()},// 搜索search() {this.getList()}},mounted() {this.getList()}}</script><style lang="less" scoped>.manage {height: 100%;.manage-header {display: flex;justify-content: space-between;align-items: center;}.common-table {height: 90%;position: relative;.pager {position: absolute;right:20px;bottom: 0;}}}</style>
App.vue
<template><div id="app"><router-view></router-view></div></template><script>export default {}</script><style>html,body,h3,p {margin: 0;padding: 0}</style>
main.js
import Vue from 'vue'import App from './App.vue'import ElementUI from 'element-ui'import 'element-ui/lib/theme-chalk/index.css';import router from './router/index'import store from './store/index'import './api/mock'Vue.config.productionTip = falseVue.use(ElementUI)new Vue({router,store,render: h => h(App),created() {mit('addMenu', router)}}).$mount('#app')
要安装的依赖
"dependencies": {"axios": "^1.1.3","core-js": "^3.8.3","echarts": "^5.1.2","element-ui": "^2.15.10","js-cookie": "^3.0.1","less": "^4.1.3","less-loader": "^11.1.0","mockjs": "^1.1.0","vue": "^2.6.14","vue-router": "^3.6.5","vuex": "^3.6.2"}
已上传到Gitee
代码已经上传到Gitee:/karshey/general-background