vue3后台管理系统

  • vite构建vue3项目
  • 项目中其他需求的引入
    • 1. element-plus引入
    • 2. vue3引入路由
    • 3. element-plus图标的引入和使用
      • 静态引入图标
      • 动态引入图标
    • 4. 引入less
    • 5. 基础样式引入
    • 6. vuex的引入
  • 工具类的使用
    • 一、mock的使用
      • 本地mock 的使用
      • 线上fastmock 的使用
    • 二、二次封装axios
  • 普通component组价
    • CommonHeader.vue(布局里的头部组件)
      • 面包屑的实现
    • CommonAside.vue(布局里的左侧菜单)
    • CommonTab.vue -----(tag标签的展示及切换)
  • 路由views组件
    • LoginApp.vue(登录页面)
      • 动态路由的实现
      • 登出功能的实现
      • 路由守卫的实现
    • mainApp.vue(总体的结构布局组件)
      • HomeApp.vue(布局里的主要展示区域)
        • 折线图(echart表格)
        • 柱状图(echart表格)
        • 饼状图(echart表格)
    • UserApp.vue(用户管理页面)
      • 获取用户数据
        • 用户的分页实现
      • 增删改查用户数据
        • 1. 搜索用户的实现
        • 2. 新增用户的实现
        • 3. 编辑用户的实现
        • 4. 删除用户的实现

vite构建vue3项目

  1. npm create vite@latest,再回车
  2. 按要求写下项目名;manage-app
  3. 选择vue,再回车
  4. 选择javascript,在回车
  5. cd manage-app,到该项目目录下,回车
  6. 安装依赖:npm install,再回车
  7. 启动项目:npm dev
  8. main.js文件的改变:
createApp(App).mount('#app')
//上面那行改为下面这行,一样的,
const app=createApp(App)
app.mount('#app')

项目中其他需求的引入

1. element-plus引入

1. 全部引入:

  • 在终端安转:npm install element-plus --save
  • main.js文件引入:
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'const app=createApp(App)
app.use(VueElementPlus)
app.mount('#app')
  • 使用:直接在组件里编写element代码即可

2. 按需引入:

  • 在终端安转:npm install element-plus --save
  • 在终端安装插件:npm install -D unplugin-vue-components unplugin-auto-import
  • 在 vite.config.ts文件引入:
//引入部分
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'//plugins数组部分加入plugins: [AutoImport({resolvers: [ElementPlusResolver()],}),Components({resolvers: [ElementPlusResolver()],}),],
  • 使用:直接在组件里编写element代码即可

3. 手动引入:

  • 在终端安转:npm install element-plus --save
  • 终端安装:npm install unplugin-element-plus
  • 在 vite.config.ts文件引入:
//引入部分
import ElementPlus from 'unplugin-element-plus/vite'//plugins数组部分加入plugins: [ElementPlus()],
  • 使用:在使用element的组件里引入具体需要的插件,比如需要引入按钮插件则需要加这段代码:
<script>
import { ElButton } from 'element-plus'export default defineComponent({component:{ ElButton}
})
</script>

2. vue3引入路由

  1. 终端安装:npm install vue-router -S
  2. 新建文件:src / router / index.js:文件配置如下
import {createRouter,createWebHashHistory} from 'vue-router' const routes=[{path:'/',redirect:'/home',component:()=>import('../views/MainApp.vue'),children:[{path:'/home',nsme:'home',component:()=>import('../views/home/HomeApp.vue')}]}
]const router=createRouter({history:createWebHashHistory(),routes
})export default router
  1. main.js文件引入
import router from './router'const app=createApp(App)
app.use(router)
app.mount('#app')
  1. 使用:在具体的组件中需要导入
import {useRouter} from 'vue-router' //导入1
export default {setup() {let router=useRouter()//声明2
//下面可以配置方法进行路由跳转}
}
  1. 需要显示路由组件的地方添加<router-view></router-view>

3. element-plus图标的引入和使用

  • 在终端安转:npm install element-plus --save
  • 终端安转:npm install @element-plus/icons-vue
  • main.js引入:
import * as ElementPlusIconsVue from '@element-plus/icons-vue'const app = createApp(App)for (const [key, component] of Object.entries(ElementPlusIconsVue)) {app.component(key, component)
}app.mount('#app')

静态引入图标

  • 直接点击想要的图标图案,就可以复制相关代码
//例如加号图标
<el-icon><Plus /></el-icon>

动态引入图标

 <!-- 遍历菜单栏--><el-menu-item :index="item.path" v-for="item in noChildren()" :key="item.path"><!-- 根据遍历得到的item,动态引入图标 --><component class="icons" :is="item.icon"></component></el-menu-item>

4. 引入less

  • 终端引入less:npm install less-loader less --save-dev

5. 基础样式引入

  • 在src/assets新建文件夹less
  • 在less文件夹新建reset.less文件,这个是全局样式
  • 在less文件夹新建index.less文件,里面只写@import './reset.less';
  • 在main.js中引入index.js文件 ,import './assets/less/index.less'

6. vuex的引入

    1. 终端安装:npm install vuex -S
    1. 新建文件:src / store / index.js,文件配置如下
import {createStore} from 'vuex'
export default createStore({//里面配置数据方法等state:{//数据} ,mutations:{//修改数据的方法},
})
    1. 在main.js文件引入
import store from './store/index.js'const app=createApp(App)
app.use(store)
app.mount('#app')
    1. 使用:使用vuex数据和方法的组件需要引入
<script>
import { useStore } from "vuex";
export default defineComponent ({setup() {//定义storelet store = useStore();function handleCollapse(){//调用vuex中的mutations中的updateIsCollapse方法store.commit("updateIsCollapse")}return {handleCollapse};},
});
</script>

工具类的使用

一、mock的使用

  • 本地mock 的使用

  1. 终端安转:npm install mockjs
  2. 新建文件:src / api / mockData / home.js(home.js表示的是home组件的mock数据)---------文件配置如下:
export default{getHomeData:()=>{    //导出home 的数据return {code:200,data:{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,},]}}}
}
  1. 再建文件:src / api / mock.js ,这个文件引入所有的mock数据,并且全部导出。----------- 文件配置如下
//导入mockjs
import Mock from 'mockjs'  //导入home的数据
import homeApi from './mockData/home' //拦截请求,两个参数,第一个参数是设置的拦截请求数据的路径,第二个参数是对应数据文件里该数据的方法a
Mock.mock('/home/getData',homeApi.getHomeData)
  1. 引入:在mian.js文件里引入mock--------如下引入
import './api/mock.js'
  1. 使用:在响应的组件上使用
async function getTableList(){await axios.get("/home/getData").then((res)=>{tableData.value=res.data.data.tableData})
}
onMounted(()=>{//调用getTableList()方法
getTableList()
})
  • 线上fastmock 的使用

  1. 点击“+”,创建项目,
  2. 得到项目的根路径
  3. 点击新增接口
  4. 编辑接口路径以及返回的数据
  5. 最终得到请求拦截地址,与请求得到的数据
  6. 在组件里的使用:
//axios请求table列表的数据,并且将请求来的数据赋值给tableDataasync function getTableList(){await axios//该路径为线上mock的接口根路径与对应数据请求的路径的拼接。.get("https://www.fastmock.site/mock/d32d92a0e177cd10b103d38a2b74d3ec/api/home/getTableData").then((res)=>{if (res.data.code == 200){//请求成功之后在进行渲染tableData.value=res.data.data}})
}
onMounted(()=>{//调用getTableList()方法
getTableList()
})

二、二次封装axios

  1. 二次封装axios的原因:处理接口请求之前或接口请求之后的公共部分

  2. 终端安装:npm install axios -S

  3. 在新建文件(环境配置文件):src /config / index.js------文件如下

/*** 环境配置文件* 一般在企业级项目里面有三个环境* 开发环境* 测试环境* 线上环境*/
// 当前的环境赋值给变量env
const env = import.meta.env.MODE || 'prod'const EnvConfig = {//1、开发环境development: {baseApi: '/api',//线上mock的根路径地址mockApi: 'https://www.fastmock.site/mock/d32d92a0e177cd10b103d38a2b74d3ec/api',},//2、测试环境test: {baseApi: '//test.future.com/api',mockApi: 'https://www.fastmock.site/mock/d32d92a0e177cd10b103d38a2b74d3ec/api',},//3、线上环境,企业才会用,pro: {baseApi: '//future.com/api',mockApi: 'https://www.fastmock.site/mock/d32d92a0e177cd10b103d38a2b74d3ec/api',},
}export default {env,// mock的总开关,true则项目的所有接口调用的是mock数据mock: true,...EnvConfig[env]//结构
}
  1. 新建文件:src / api /request.js-------文件如下
import axios from 'axios'
import config from '../config'
//错误提示
import { ElMessage } from 'element-plus'
const NETWORK_ERROR = '网络请求异常,请稍后重试.....'//创建axios实例对象service
const service=axios.create({//根路径为config里的index.js文件里的开发环境的baseApibaseURL:config.baseApi
})//在请求之前做的一些事情,request
service.interceptors.request.use((req)=>{//可以自定义header//jwt-token认证的时候return req//需要return出去,否则会阻塞程序
})//在请求之后做的一些事情,response
service.interceptors.response.use((res)=>{console.log(res)//解构res的数据const { code, data, msg } = res.data// 根据后端协商,视情况而定if (code == 200) {//返回请求的数据return data} else {// 网络请求错误ElMessage.error(msg || NETWORK_ERROR)// 对响应错误做点什么return Promise.reject(msg || NETWORK_ERROR)}
})//封装的核心函数
function request(options){//默认为get请求options.methods=options.methods || 'get'if (options.method.toLowerCase() == 'get') {options.params = options.data}// 对mock的处理let isMock = config.mock  //config的mock总开关赋值给isMockif (typeof options.mock !== 'undefined') { //若组件传来的options.mock 有值,单独对mock定义开关isMock = options.mock  //就把options.mock 的值赋给isMock}// 对线上环境做处理if (config.env == 'prod') {// 如果是线上环境,就不用mock环境,不给你用到mock的机会service.defaults.baseURL = config.baseApi} else {//ismock中开关是否为true,若为真,则说明事由mock环境,那么跟路径就要事由mock的根路径service.defaults.baseURL = isMock ? config.mockApi : config.baseApi}return service(options)   //函数的返回值
}export default request
  1. 新建文件 src / api / api.js-----文件如下:
// 整个项目api的管理
import request  from "./request";//导出
export default{//home组件左侧表格数据获取getTableData(params){//request就是reuest.js文件中封装的核心函数,里面的对象就是options参数return request({url:'/home/getTableData',method:'get',data:params,//通过getTableData(params)方法的形参传过来mock:true})},
}
  1. 如果要进行axios请求数据,直接引入api.js即可
  2. 将api.js的方法挂载到全局,在main.js的文件配置如下
import api from './api/api'  //引入api.jsconst app=createApp(App)
app.config.globalProperties.$api = api  //全局挂载,将api赋值给$api
app.mount('#app')
  1. 使用:
import { defineComponent ,getCurrentInstance,onMounted ,ref} from "vue";export default defineComponent({setup() {//proxy类似于vue2的thisconst {proxy}=getCurrentInstance()//左侧表格的tableData数据let tableData =ref([])//getTableList()这个方法里面使用了api.js里面的getTableData方法来请求table里的数据
async function getTableList(){let res=await proxy.$api.getTableData()  //通过proxy拿到api的请求数据的方法tableData.value=res
}onMounted(()=>{//调用getTableList()方法
getTableList()
})},
});
</script>

普通component组价

CommonHeader.vue(布局里的头部组件)

  1. 效果图

  2. 左侧引入图标,并且图标嵌套在el-button里

  3. 右侧个人头像,点击出现下拉菜单,个人中心和退出

    • 个人头像的图片为动态引入,vite的图片静态资源处理
    <img class="user" :src="getImageUrl('user')" alt="" />  //src前面加冒号,表示动态// 动态引入图片路径,参数为图片名字,
    function getImageUrl(user) {return new URL(`../assets/images/${user}.jpg`, import.meta.url).href;//../assets/images/${user}.jpg是图片相对路径,import.meta.url表示的是当前组件路径,两者进行拼接。
    }return {getImageUrl,
    };
    
    • 下拉菜单的实现:使用elment-plus的Dropdown 下拉菜单

面包屑的实现

  1. 使用element-plus的Breadcrumb 面包屑
  2. 实现思路:点击用户管理,首页后面显示用户管理,点击页面1,首页后面出现页面1,由于点击的是commonAside组件,而显示的面包屑出现在commonHeader组件,是跨组件建的通信,所以vuex管理数据。
  3. 在store / index.js 写如下代码
import {createStore} from 'vuex'
export default createStore({state:{//当前菜单赋值为空currentMenu:'',} ,mutations:{//选择菜单,val是commonAside组件传过来的当前点击的菜单值selectMenu(state,val){//判断,如果当前点击的菜单名为home,home就是首页,就让当前菜单currentMenu还是赋值为空,//否则就让currentMenu赋值为当前菜单项itemval.name=='home'?(state.currentMenu==null):(state.currentMenu==val)}},
})
  1. 在commonAside组件里通过store调用 selectMenu()方法,并且传入当前的菜单项item
        //在点击菜单进行路由跳转时调用vuex中的selectMenu(),并且传入当前的菜单项item// 点击菜单进行路由跳转方法function clickMenu(item){router.push({name:item.name})//vuex来管理路由跳转store.commit('selectMenu',item)}
  1. 在commonHeader组件里得到vuex的 currentMenu数据
     <!-- 面包屑 --><el-breadcrumb separator="/">//永远显示首页<el-breadcrumb-item :to="{ path: '/' }">首页</el-breadcrumb-item> //动态显示当前的菜单标签,并且有才current才显示<el-breadcrumb-item :to="current.path" v-if="current">{{current.label}}</el-breadcrumb-item>  </el-breadcrumb>import { computed, defineComponent } from "vue";
import { useStore } from "vuex";
export default defineComponent({setup() {let store = useStore()//拿到vuex的 currentMenu数据,用计算属性,这样子菜单改变,页面也会跟着改变const current= computed(()=>{return store.state.currentMenu})}return {current,};},
})
</script>

CommonAside.vue(布局里的左侧菜单)

  1. 效果图:

  2. 布局使用:elment-plus的Menu菜单的侧栏

  3. 具体菜单实现

    • 将菜单分为两组,有子菜单和无子菜单,分别遍历菜单数据并动态渲染文字、图标等。
  4. 功能:点击菜单,跳转到相应的路由组件。

<!-- 没有children的一级菜单 --><el-menu-item:index="item.path"v-for="item in noChildren()":key="item.path"@click="clickMenu(item)"  点击进行跳转的方法,并且传入item>
    // 点击菜单进行路由跳转方法function clickMenu(item){router.push({name:item.name,})}

CommonTab.vue -----(tag标签的展示及切换)

  1. 思路:

    • 首页的tag一开始就会存在,而且是不能进行删除的
    • 当点击左侧栏的时候,如果tag没有该菜单名称则新增,如果已经有了那么当前tag背景为蓝色。
    • 删除当前tag,如果是最后一个,那么路由调整到它前面那个标签并且背景为蓝色,如果不是最后一个,那么路由调整到它后面那个标签并且背景为蓝色。
    • 注意tag无论路由如何切换都会存在,所以这个tag一定存在mainApp.vue组件中。
  2. 使用elment-plus的Tag 标签
  3. 在store / index.js里存储首页tag数据
import {createStore} from 'vuex'
export default createStore({// 数据state:{//tag数据,一开始只有首页tabsList:[{path:'/',name:'home',label:'首页',icon:'home'}],},
  1. 在CommonTab.vue组件里拿到首页的tag数据
import { useStore } from "vuex";
export default {setup() {let store = useStore();const tags = store.state.tabsList;  //不需要计算属性拿到数据,因为这个值不会变化,永远都有首页这个tag
  1. 当点击不是首页的菜单时候,查看当前的 tabsList数组是否有该菜单,如果没有则向 tabsList数组添加该菜单项,如果有该菜单,就什么都不做。
 //点击菜单进行路由跳转selectMenu(state,val){if(val.name == 'home'){state.currentMenu = null}else{state.currentMenu=val//arr.findIndex 方法返回找到的元素的索引,而不是元素本身。如果没找到,则返回 -1let result= state.tabsList.findIndex(item=>item.name == val.name)//item表示的是数组的每一项result== -1? state.tabsList.push(val):''}},
  1. 点击对应tag标签进行路由跳转,所以需要tag标签里设置点击事件

<script>
import { useRouter, useRoute } from "vue-router";
export default {setup() {let router=useRouter()//点击tag标签进行路由跳转的方法function changeMenu(item){router.push({name:item.name})}return {changeMenu,};
};
</script>
  1. 点击关闭tag标签,设置点击事件
//点击tag标签关闭方法function handleClose(tag,index){//让长度与索引保持一致let length=tags.length - 1;//处理vuex的tablelist,就是删除当前的菜单项store.commit("closeTab",tag)//做第一个判断,如果当前显示的菜单tag与要删除的tag菜单不一致,不做处理,还是上面的closeTab方法if(tag.name !== route.name){return;}//如果当前显示的菜单tag与要删除的菜单tag相同//并且是最后一个菜单tag,则路由跳转到前面一个菜单tagif(index === length){router.push({name:tags[index-1].name})}else{ //不是最后一个菜单tag,则路由跳转到后面一个菜单tag,因为删除了该菜单,所以后面那个菜单的索引就是被删除菜单的索引。router.push({name:tags[index].name,})}}
  1. 在store文件里设置关闭标签的方法
   closeTab(state,val){//拿到当前菜单的索引let res= state.tabsList.findIndex(item=>item.name === val.name) //在tabsList数组里,从当前菜单索引开始删除 1 个元素,就是删除当前的菜单项目state.tabsList.splice(res,1)  },
  1. 在mainApp.vue组件里引用该tab组件
    .

路由views组件

LoginApp.vue(登录页面)

功能:根据不同的用户角色,返回不一样的系统菜单

  1. UI效果图

  2. 先配置路由跳转,登录路由级别与首页路由同级

  3. 使用element-plus的form表单


  1. 创建本地mock的permission.js写不同用户登录得到不同菜单的逻辑
  2. 在api.js里创建接口
   //根据用户的用户名不同,返回不一样的菜单列表getMenu(params){  //点击登录时,用户信息会作为参数传过来return request({url:'/permission/getMenu',method:'post',mock: false,data: params})},
  1. 在mock.js里导入permission.js并且拦截接口,调用permission.js的方法
//导入登录的数据
import permission from './mockData/permissin'
Mock.mock(/permission\/getMenu/, 'post', permission. getMenu)
  1. 用v-model双向绑定用户的账号和密码,当点击登录时,就调用api.js里的getMenu(params)方法,并把当前的用户信息作为参数传过去,判断用户返回不一样的菜单,如图所示。
  2. 将返回的菜单数据存储到vuex中,aside组件显示出来。之前的菜单项是写死的,现在需要动态渲染。
  3. 在store / inde.js里定义方法得到当前用户的菜单,并且localStorage.setItem存储菜单数据
  //根据不同用户返回不一样的菜单setMenu(state,val){state.menu=vallocalStorage.setItem('menu',JSON.stringify(val))  //将val转化为JSON格式},
  1. CommonAside组件里通过store拿到当前的项。

  2. 在登录页面点击登录时通过store调用该方法,并且进行路由跳转到首页

  3. 每次刷新时,存储在vuex里的数据会丢失,所以动态得到的菜单也会消失,所以我们要解决数据持久化的问题。

  4. 所以在vuex里定义一个方法,用localStorage.getItem来获取当时localStorage.setItem存储的菜单数据,并且该方法在每次刷新的时候调用,每次刷新肯定会经过App.vue组件,所以在App.vue组件里调用该方法。

//刷新获取 localStorage.setItem存储的菜单数据addMenu(state){if(!localStorage.getItem('menu')){return}const menu=JSON.parse(localStorage.getItem('menu'))state.menu=menu}

动态路由的实现

功能:根据后台返回的menu数据,动态的添加路由,而不是在router / index.js文件里直接配置要跳转的路由。除了首页和登陆注册页面,其他页面的路由都应该是动态添加的。
思路:

  1. 定义一个新的数组。用来存储菜单
const menuArray=[]
  1. 在store / index.js文件里的addmenu方法里遍历所得到的动态菜单,如果当前的菜单有子菜单,那么就用map对子菜单操作,返回当前菜单的路径url,并且使用懒加载的方式引入路由组件,并且把该子菜单项push到菜单空数组里
 menu.forEach(item => {if(item.children){item.children=item.children.map(item=>{let url=`../views/${item.url}.vue`item.component=()=>import(url)return item})menuArray.push(...item.children)//解构出子菜单}
  1. 如果当前菜单没有子菜单,依然返回路径,和上面操作一样,并且把该菜单项push到菜单空数组里
  2. 当menuArray里有菜单时,就遍历该菜单,并且使用addRoute方法添加一条新的路由记录作为首页路由(首页路由名为home1)的子路由。所以addmenu方法里还需要设置第二个参数router,当刷新时,需要传router过来。
  menuArray.forEach(item=>{router.addRoute('home1',item)})
  1. 则routre / index.js文件里的children路由应该设置为一个空数组。
  2. addmenu方法应该在main.js文件里使用,因为如果在app.vue里使用,此时,页面已经挂载完毕,此时在进行动态路由添加晚了。

登出功能的实现

思路:

  1. 点击退出时,添加点击事件。
  2. 清除掉当前菜单
  3. 在store / index.js里定义清除菜单的方法,用localstorage.remove清除掉当时存储的菜单,
    //    清除菜单clearMenu(state){state.menu=[]localStorage.removeItem('menu')},
  1. 然后在点击退出的时候调用该方法,并且跳转到登录页面。
//退出方法function handleLoginOut(){//清除菜单store.commit('clearMenu')router.push({name:'login'})}

路由守卫的实现

思路:

  1. 即使我们知道首页等其他页面地址,但是如果我们没有进行登录操作,就不能跳转到对应的地址,而是直接跳转到登录页面地址,进行登录操作。根据后端返回的token来进行路由守卫,
  2. 在vuex里进行管理。先设置token 为空,当我们登录时,就对拿到登录返回的token值,并且赋值给vuex力的token
  3. 并且要对Token进行持久化,需要下载cookie
  4. 终端安装cookie:npm install js-cookie --save
  5. 引用import jsCookie from 'js-cookie'
  6. 将登录时返回的token数据val赋值给当前vuex里的token,
    //设置tokensetToken(state,val){state.token=valCookie.set('token',val)},//清除tokenclearToken(state){state.token=''Cookie.remove('token')},//获取tokengetToken(state){//如果当前的vuex里有Token的值,就获取vuex里的token值,如果当前vuex里token值为空。state.token=state.token || Cookie.get('token')}
  1. 当点击登录按钮时,就调用设置Token的方法,并且拿到当前的Token作为参数对vuex里的token进行赋值
  2. 在main.js中添加一个全局路由守卫,先调用获取Token的方法,并且的到store里的token值,如果没有token 并且即将要跳转到的路由页面不是登录页面,那就直接让它跳转到登录页面。如果有token值,还要进行判断,如果没有匹配到当前路径,就直接跳转到首页,如果能匹配到当前路径,就跳转到对应的路由页面。
router.beforeEach((to,from,next)=>{store.commit('getToken')const token=store.state.tokenif(!token && to.name !=='login'){next({name:'login'})}else if(!checkRouter(to.path)){  //如果没有检测到当前路径,就直接跳转到首页next({name:'home'})} else{next()}
})
  1. 在mian.js文件里要定义一个检查已有路由的方法,并且该方法要写在动态路由方法下面。
  2. getRoutes获取所有 路由记录的完整列表。并且对这些路由进行过滤,输出与输入的路径相同的已有路径的路由,如果该长度为0,说明输入的路径在已有的路由路径里不存在。就return false
  3. 在长度,则说明输入的路径在已有的路由路径里存在,就return true
function checkRouter(path){let hasCheck=router.getRoutes().filter(route=>route.path==path).lengthif(hasCheck){return true}else{return false}
}

mainApp.vue(总体的结构布局组件)

  1. layout整体布局实现:. 使用element-plus的Container 布局容器
  2. 引入CommonHeader组件,并且在header区域导入<CommonHeader/>
  3. 引入CommonAside组件,并且在Aside区域导入<CommonAside/>

HomeApp.vue(布局里的主要展示区域)

  1. 总体使用elment-plus的 layout布局

    • layout布局的大概了解:

      一行:<el-row></el-row>, 整个页面可以用这个布局
      一列:<el-col></el-col>

  2. 用户信息展示使用使用elment-plus的card卡片

    • 代码:
      <el-card shadow="hover"><!-- 卡片的上部分具体的展示内容 --><div class="user"><!-- 图片展示 --><img src="../../assets/images/user.jpg" alt="" /><!-- 用户信息展示 --><div class="user-info"><p class="name">Admin</p><p class="role">超级管理员</p></div></div><!-- 卡片的下部分具体的展示内容 --><div class="login-info"><p>上次登录时间<span>2022-7-11</span></p><p>上次登录地点<span>南昌</span></p></div></el-card>
    
    • 效果:
  3. 数据展示使用elment-plus的Table 表格

    • 代码:
    <el-card shadow="hover" style="margin-top: 20px" height="450px"><!-- 卡片里显示的是表格 --><el-table :data="tableData"><!-- 表格的每列的标题<el-table-column/>,并且根据数据遍历每一列--><el-table-columnv-for="(val, key) in tableLabel":key="key":prop="key":label="val"></el-table-column></el-table></el-card>
    
    //数据
    <script>
    import { defineComponent } from "vue";
    export default defineComponent({setup() {//左侧表格的tableData数据
    const tableData = [{name: "oppo",todayBuy: 500,monthBuy: 3500,totalBuy: 22000,},{......},
    ........
    ];
    //tableData数据的表头
    const tableLabel = {name: "品类",todayBuy: "今日购买",monthBuy: "本月购买",totalBuy: "总共购买",
    };return {tableData,tableLabel};},});</script>
    
    • 效果:
  4. home组件右侧上面的数据展示,也是用card卡片展示

    • 效果图:

    • 数据来源于线上fastmock

    • 在api.js文件中配置对应的请求数据的方法

    <script>
    import { defineComponent, getCurrentInstance, onMounted, ref } from "vue";
    export default defineComponent({setup() {//proxy类似于vue2的this
    const { proxy } = getCurrentInstance();let countData = ref([]);//getCountData()这个方法里面使用了api.js里面的getCountData方法来请求table里的数据
    async function getCountData() {let res = await proxy.$api.getCountData();
    console.log(res);
    countData.value = res;
    }onMounted(() => {getCountData();
    });
    return {countData,};},});</script>
    
  5. homeApp组件右侧表格:使用echart表格

  • 折线图(echart表格)

具体可查看echart的配置项手册
* 效果:

* 终端下载echart:npm install echarts
* 页面引入echart:import * as echart from 'echarts'
* 装echart的盒子
xml <!-- 装echart折线表格的大卡片 --> <el-card style="height: 280px"> <!-- echart折线表格 --> <div ref="echart" style="height: 280px"></div> </el-card>

  • 图形的配置

      //折线图和柱状图的echarts配置let xOptions = reactive({// 图例文字颜色
    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: [],});```
  • 设置echart表格的空数据

         //下面是rchart表格的数据,先设置为双向绑定数据,并且都为空值,后面通过接口获取数据,进行赋值let orderData = reactive({xData: [],series: [],});xData: [],series: [],});let videoData = reactive({series: [],});
    • 线上mock配置表格数据

    • 在api.js文件中创建方法请求表格数据

     getEchartData(params){return request({url:'/home/getEchartData',method:'get',data:params,mock:true})
    },
    
  • 在homeApp.vue页面中获取数据,并且赋值给3个echart表格的空数据

         //通过接口获取echart表格的数据,api.js
    async function getEchartData(){let result=await proxy.$api.getEchartData()
    let orderData=result.orderData   //折线图数据
    let userData=result.userData    //柱状图数据
    let videoData=result.videoData   //饼状图数据
    orderData.xData=orderData.date     //将折线图的x坐标的数据进行赋值
    //每一条折线的数据是对象形式,每一条折线数据里的键相同,返回其中一条折线数据所有的键的数组。
    const keyArray=Object.keys(orderData.data[0])
    //定义一个空数组,折线数据的配置
    const series=[]
    //遍历每一个键,并向空数组里添加一个对象,该对象包括折线名字,折线纵坐标的值,还有折线的展现形式
    keyArray.forEach((key) => {series.push({name: key,  //折线的名字data:  orderData.data.map((item) => item[key]),  //得到该键的值,就是折线纵坐标的值type: "line",  //为折线展示
    });
    });
    }orderData.series=series    //对折线图的空数据进行赋值给orderData.series
    xOptions.xAxis.data=orderData.xData   //对折线图的xAxis.data横坐标空数据进行重新赋值xOptions.series=orderData.series  //对折线图的orderData.series赋值给空数组series//对折线图进行渲染
    let hEcharts = echart.init(proxy.$refs['echart'])  //将echart进行赋值给hechart
    hEcharts.setOption(xOptions)  //开始建立折线图渲染
    onMounted(() => {getEchartData()
    });
  • 柱状图(echart表格)

    • 效果:
    • 页面的
      <!-- 左侧柱状图 --><el-card style="height:260px"><div ref="userechart" style="height:240px"></div></el-card>
    
    • 柱状图的配置
      //折线图和柱状图的echarts配置相同,只是横坐标,纵坐标,series不同,这些数据请求得到。let xOptions=reactive({// 图例文字颜色
    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: [],})
    
    • 请求柱状图的数据
    //通过接口获取echart表格的数据,api.js
    async function getEchartData(){let result = await proxy.$api.  getEchartData();
    let userData=result.userData    //柱状图数据
    }
    
    • 将请求来的数据赋值给当前的图形数据
      // 柱状图进行渲染的过程
    userData.xData =userData.map((item) => item.date);  //柱状图的横坐标的值
    userData.series = [{name: "新增用户",data: userData.map((item) => item.new),type: "bar",},{name: "活跃用户",data: userData.map((item) => item.active),type: "bar",},
    ];
    xOptions.xAxis.data=userData.xData
    xOptions.series=userData.serieslet uEcharts = echart.init(proxy.$refs['userechart'])
    uEcharts.setOption(xOptions)
    
  • 饼状图(echart表格)

    • 效果:
    • 页面的装饼状图的代码
     <!-- 右侧饼状图 --><el-card style="height:260px"><div ref="videoechart" style="height:240px"></div></el-card>
    
    • 饼状图的配置
        //饼状图的配置let pieOptions = reactive({tooltip: {trigger: "item",},color: ["#0f78f4","#dd536b","#9462e5","#a6a6a6","#e1bb22","#39c362","#3ed1cf",],series: [],});
    
    // 饼状图进行渲染的过程
    videoData.series = [{data: videoData,type: "pie",},];pieOptions.series = videoData.series;let vEcharts = echart.init(proxy.$refs["videoechart"]);vEcharts.setOption(pieOptions);
    };
    

UserApp.vue(用户管理页面)

使用element-plus里的table表格的固定列

获取用户数据

  1. 使用本地mock来配置用户数据:在mockData / 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, '":"') +'"}')
}//创建了200条数据,
let List = []
const count = 200for (let i = 0; i < count; i++) {List.push(Mock.mock({id: Mock.Random.guid(),name: Mock.Random.cname(),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 => {//每一页数据20条const { name, page = 1, limit = 20 } = param2Obj(config.url)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: 200,data: {list: pageList, //返回当前页的数据count: mockList.length,   //返回整个数据的长度}}},
  1. 在mock.js文件里引入user.js文件,拦截请求,请求用户管理列表数据 ,
import userApi from './mockData/user'//本地获取user的数据,第一个参数通过正则来匹配路径,第二个参数是请求方式,第三个参数是请求数据的方法
Mock.mock(/user\/getUser/, 'get', userApi.getUserList)
  1. 在api.js里利用二次封装的axios进行请求数据
//获取user的table数据getUserData(params) {return request({url: '/user/getUser',method: 'get',// 这个mock如果是true的话 用的就是线上fastmock的数据mock: false,data: params// 分页器的data:{total: 0,page: 1,}//page是1拿到第一页的数据})},
  1. 在用户管理页面上获取该数据,并赋值给定义的双向绑定的空数据,渲染到页面上。

  2. 自己设置表头数据

// table 表格表头的数据
//因为返回的性别数据不是我们需要的,后面进行处理const tableLabel = reactive([{prop: "name",label: "姓名",},{prop: "age",label: "年龄",},{prop: "sexLabel",label: "性别",},{prop: "birth",label: "出生日期",width: 200,},{prop: "addr",label: "地址",width: 320,},]);
  1. 对拿到的性别数据进行处理,因为数据返回0和1,我们需要展示男女
//遍历每一条数据的sex,进行操作,将0,1该为男女,将值赋值给sexLabellist.value = res.list.map((item) => {item.sexLabel =(item.sex === 0 ? "女" : "男";)return item;  //并且每遍历一次就返回});

用户的分页实现

  1. 使用elment-plus的Pagination 分页
  2. 定义一个对象congig ,里面有tatol属性,和当前页page属性
//定义当前页和atol的响应数据对象configconst config = reactive({page: 0,//默认为1,展示第一页的数据total: 1,  //默认为 1,会别请求得到的tatol覆盖});
  1. 分页器设置点击事件,并且每次点击时候都请求一次用户数据
<!-- 分页器 --><el-pagination background layout="prev, pager, next" :total=config.total     //分页器的总数class="pager mt-4"   //自己设置样式@current-change="changePage"  //点击事件,不用传参,默认点击的时候会得到该分页器的页数,/>//点击分页器跳转方法function changePage(page) {config.page = page;getUserData(config);}

增删改查用户数据

1. 搜索用户的实现

  • 搜索框使用element-plus的form表单的行内表单
<!-- 搜索框 --><el-form :inline="true" :model="formInline"> //<el-form-item label="请输入">//<el-input v-model="formInline.keyword" placeholder="请输入用户名" /></el-form-item><el-form-item><el-button type="primary" @click="handleSerch">搜索</el-button></el-form-item></el-form>
  • 在搜索按钮里定义点击事件,
//定义当前页和atol的响应数据对象config,这里新增name属性const config = reactive({page: 0,total: 1,name: "", //默认空值});//定义forminline,搜索框的关键字const formInline = reactive({keyword: "",});//点击搜索的方法,把keyword关键字赋值给config的name属性,作为params参数,拿到对应的数据function handleSerch() {config.name = formInline.keyword;getUserData(config);}

2. 新增用户的实现

  1. 新增用户ui界面实现
  • 效果图:

  • 使用elmnt-plus的Dialog 对话框

  • Dialog 对话框添加用户信息新增的表单

  • 性别选择用表单里的选择框

  • 出生日期用表单的选择日期

  • 页面代码

 <!-- 用户新增布局dialog ,模态框,新增用户和编辑用户信息共用一个模态框,--><el-button type="primary"  @click="dialogVisible = true">+新增</el-button><el-dialogv-model="dialogVisible"title="新增用户"width="40%":before-close="handleClose"><!-- 用户信息表单部分 --><el-form :inline="true" :model="formUser" class="userForm"><!-- 一行包含两列 --><el-row><el-col :span="12"><el-form-item label="姓名"><el-input v-model="formUser.name" placeholder="请输入用户名" /></el-form-item></el-col><el-col :span="12"><el-form-item label="年龄"><el-input v-model="formUser.age" placeholder="请输入用户年龄" /></el-form-item></el-col></el-row><!-- 一行包含两列 --><el-row><el-col :span="12"><el-form-item label="性别"><el-select v-model="formUser.sex" placeholder="请输入性别"><el-option label="男" value="1" /><el-option label="女" value="0" /></el-select></el-form-item></el-col><el-col :span="12"><el-form-item label="出生日期"><el-date-pickerv-model="formUser.birth"type="date"placeholder="请选择出生日期"style="width: 100%"/></el-form-item></el-col></el-row><!-- 最后只有一行只有一列地址 --><el-row><el-col :span="12"><el-form-item label="地址"><el-input v-model="formUser.addr" placeholder="请输入地址" /></el-form-item></el-col></el-row><!-- 第四行是取消确定按钮,并且靠右 --><el-row style="justify-content: flex-end"><el-button type="primary" @click="handleCancel">取消</el-button><el-button type="primary" @click="onSubmit">确定</el-button></el-row></el-form></el-dialog>
  • 定义用户对象数据,双向定
//添加用户的form数据const formUser = reactive({name: "", //添加的用户姓名age: "", //添加用户年龄sex: "",birth: "",addr: "",});
  1. 新增用户数据实现
  • 要将对话框里填写的用户 提交到用户数据里。
  • 本地mock的user.js中已经定义了添加用户的方法,
  • 在mock.js里拦截数据,当匹配到该路径的话,就调用该方法
 Mock.mock(/user\/add/, 'post', userApi.createUser)
  • api.js中利用二次封装的axios提交用户数据。
//新增用户接口addUser(params){return request({url:'/user/add',method:'post',mock: false,data: params})},
  • 点击确定提交用户数据成功之后,需要完成以下事情

    • 对话框要重置,里面的数据要消失
    • 对话框要消失
    • 还需要重新调用getuserlist方法 。拿到最新的用户数据
//点击确定提交用户数据
async function onSubmit(){let res = await proxy.$api.addUser(formUser);if(res){//重置表单,<el-form :inline="true" :model="formUser" ref="userForm">,userForm表单数据重置proxy.$refs.userForm.resetFields();//点击确定后关闭对话框dialogVisible.value = false;//重新请求用户数据getUserData(config)}
}
  1. 新增用户表单验证
:rules就是自己定义的规则<el-form-item label="姓名"  prop="name"  :rules="[{ required: true, message: '姓名是必填项' }]" ><el-form-item label="年龄"  prop="age" :rules="[{ required: true, message: '年龄是必填项' },{ type: 'number', message: '年龄必须是数字' },]"><el-input v-model.number="formUser.age" placeholder="请输入用户年龄" />v-model.number可以使输入的字符串数字转变为number类型。
  • 点击确定时

    • 每一个用户信息框都进行校验,当有信息框为填时,不能提交数据 , 用表单的validae方法来校验。
    
    //点击确定提交用户数据
    function onSubmit(){//如果能拿到形参,就是如果所有的必选框都填了,就提交数据proxy.$refs.userForm.validate(async (valid)=>{if(valid){//调用日期格式化方法
    formUser.birth = timeFormat(formUser.birth);let res = await proxy.$api.addUser(formUser);if(res){//重置表单proxy.$refs.userForm.resetFields();//点击确定后关闭对话框dialogVisible.value = false;//重新请求用户数据getUserData(config)}}})
    }
    
  • 点击取消按钮时候,应该完成以下事情
    • 重置表单
    • 关闭对话框
  //点击取消,重置表单,关闭对话框function handleCancel() {proxy.$refs.userForm.resetFields();dialogVisible.value = false;}
  • 当有信息未填写完成,点击确定时,应该提示错误信息,使用element-plus的Message

3. 编辑用户的实现

思路:

  1. 点击编辑时候,显示对话框。并且复用新增用户的对话框,新增用户的对话框变成编辑用户的对话框
  2. 定义一个变量来区分当前是编辑用户还是新增用户。在点击新增和编辑的时候改变该变量。
  3. 点击编辑的时候要把拿到当前行的用户信息,可以用插槽的方法#default="scope"拿到当前表格的数据,再用scope.row 拿到当前行的数据。
//#default="scope"可以拿到当前表格的数据<template #default="scope">  //@click="handleEdit(scope.row)"点击编辑时候可以拿到当前行scope.row 的数据       <el-button type="primary" size="small" @click="handleEdit(scope.row)"  >编辑</el-button ></template>
  1. . 把拿到的这些数据赋值到编辑用户的对话框里。用浅拷贝object.assign(目标对象,源对象)
//把当前行的数据赋值当formUser用户对象上。Object.assign(formUser, row);
  1. 点击取消,再点击新增时,对话框框一个为空,所以一个异步操作浅拷贝
 proxy.$nextTick(() => {//拿到每一行的用户数据,可以用浅拷贝Object.assign(formUser, row);});
  1. 点击确定时,需要用编辑用户的接口
  2. 在api.js里利用二次封装的axios调用编辑用户的接口
  //编辑用户接口aditUser(params){return request({url:'/user/edit',method:'post',mock: false,data: params})},
  1. 在mock.js里通过匹配路径拦截请求,调用本地mock的user.js文件的编辑用户方法。
Mock.mock(/user\/edit/, 'post', userApi.updateUser)
  1. 在点击确定的时候的点击事件需要进行判断,判断如果当前是编辑用户的对话框,就调用编辑用户的接口

4. 删除用户的实现

思路

  1. 在api.js里利用二次封装的axios调用删除用户的接口
 //删除用户接口deleteUser(params){return request({url:'/user/delete',method:'get',mock: false,data: params})},
  1. 在mock.js里通过匹配路径拦截请求,调用本地mock的user.js文件的删除用户方法。
Mock.mock(/user\/delete/, 'get', userApi. deleteUser)
  1. 点击删除时,设置点击事件,并且获取当前行的数据
 <template #default="scope"><el-button type="primary" size="small" @click="handleDelete(scope.row)"  >编辑</el-button ></template>
  1. 点击删除时,判断你确定删除吗,如果确定删除,就调用删除用户的接口,并且需要传入当前数据的id值,删除成功就message提示删除成功。在重新调用获取用户信息接口。
     //删除用户方法function handleDelete(row){//判断是否删除吗ElMessageBox.confirm("确定删除吗")//确认删除则以下做法.then(async() => {//调用删除用户接口await proxy.$api. deleteUser({  id:row.id })//弹出删除成功信息ElMessage({showClose:true,Message:'删除成功',type:"success"})//重新获取用户数据getUserData(config)}).catch(() => {// catch error});}

vue3后台管理系统相关推荐

  1. vue3后台管理系统(https://github.com/noob-Jp/my-admin-vue3)

    # my-admin-vue3 线上预览地址:vue3后台管理系统 本项目是我第一次写vue3,vue2已经学了很长时间,早就想开始接触vue3,通过b站和github上项目的介绍我了解到好多成熟的v ...

  2. 开箱即用,这些 Vue3 后台管理系统模板绝对让你爽歪歪!

    原文链接:Vue3 后台管理系统模板推荐. 之前写了一篇关于 Vue2 的后台管理系统模板的推荐,详情请见 Vue后台管理系统模板推荐. Vue3 在今年2月份已成为新的默认版本,本文收集了一些 Vu ...

  3. Vue3后台管理系统模板推荐

    文章目录 @[TOC](文章目录) 1.Vue-Vben-Admin 2.vue-manage-system 1.Vue-Vben-Admin Vue-Vben-Admin(github上的标星数为1 ...

  4. Vue3后台管理系统(四)SVG图标

    目录 一.安装 vite-plugin-svg-icons 二.创建图标文件夹 三.main.ts 引入注册脚本 四.vite.config.ts 插件配置 五.TypeScript支持 六.组件封装 ...

  5. 零基础快速开发全栈后台管理系统(Vue3+ElementPlus+Koa2)—项目概述篇(一)

    零基础快速开发全栈后台管理系统(Vue3+ElementPlus+Koa2)-项目概述篇(一) 一.项目开发总体框架 二.项目开发流程 三.项目技术选型

  6. vue3+vite+antd——后台管理系统——基础模板

    2023年了,让我看看谁还不会用vue3,其实我也不太会.... 不会就学啊,别的不说,潜力无穷.下面就把我从网上找到的一个后台管理系统模板放在下面了,需要自取. 该系统模板还是不太完善,后续完善后, ...

  7. Python VUE3 + Django Ninja的后台管理系统

    Python VUE3 + Django Ninja的后台管理系统 为什么使用 Django-Ninija 和 Vue3

  8. 《vue3+ts+element-plus 后台管理系统系列》之微前端版本

    系列文章目录 <vue3+ts+element-plus 后台管理系统系列一>之简介 <vue3+ts+element-plus 后台管理系统系列二>之布局 <vue3+ ...

  9. vue3.0 + elementPlus 后台管理系统模板

    gif示例 github地址: github 基于vue3.x + elementPlus 实现的后台管理系统模板 1. 登录逻辑. 2. 基本布局(基于elementPlus) 3. 动态路由逻辑 ...

最新文章

  1. linux系统文件保存后恢复,linux系统文件误删恢复
  2. r语言 读取dta_R语言将大型Excel文件转为dta格式
  3. T4模板使用记录,生成Model、Service、Repository
  4. (Photo Metadata Remover)Android App 一键去除照片 EXIF 隐私信息
  5. CentOS 6.9 下安装DB2
  6. Extjs textfield keyup事件
  7. gartner 服务器虚拟化 市场份额 2013,Gartner:VMware成虚拟化魔力象限领导者
  8. 杂谈:中国互联网早已经被群雄割据各自为王了
  9. nginx错误代码说明,出现原因及解决方法
  10. matplotlib画图并设置图片大小
  11. 编译内核报错——*** 没有规则可制作目标“debian/canonical-revoked-certs.pem”,由“certs/x509_revocation_list” 需求。 停止。
  12. C# 强化系列文章四:匿名方法的使用
  13. TortoiseSVN安装及使用总结
  14. JavaScript课堂笔记一
  15. IE的layout布局
  16. 2.5元组tuple
  17. CUDA编程--邻近点查询
  18. 线性相关与线性无关的定义与性质
  19. Python列表的extend函数
  20. 3DMark Vantage

热门文章

  1. Flutter videoplayer适配
  2. 脚本语言与非脚本语言
  3. chrome插件——xpath
  4. Oracle之SELECT语句
  5. 异地冗灾方案[专业文件]
  6. 让我又爱又恨的Java《打工人的那些事》
  7. 使用pycharm时安装pymouse和pyhook的一个细节
  8. 【数学建模】清风数学建模笔记之——层次分析法
  9. VC++ | DLL的创建和使用
  10. 计算机专业英语教程比较实用,计算机专业英语教程(经典版).ppt