npm i -g @vue/cli
PS D:\kwai\vue3\project> vue create vue3-te-demo
element-plus
一个 Vue 3 UI 框架 | Element Plus
https://element-plus.org/zh-CN/guide/installation.html
安装:
npm install element-plus --save
完整引入使用:
使用:
按需引入使用
首先你需要安装unplugin-vue-components
和 unplugin-auto-import
这两款插件
shell
npm install -D unplugin-vue-components unplugin-auto-import
修改vue.config.js中的内容为:
const { defineConfig } = require('@vue/cli-service')
const AutoImport = require('unplugin-auto-import/webpack')
const Components = require('unplugin-vue-components/webpack')
const { ElementPlusResolver } = require('unplugin-vue-components/resolvers')module.exports = defineConfig({transpileDependencies: true,configureWebpack:{plugins: [AutoImport({resolvers: [ElementPlusResolver()],}),Components({resolvers: [ElementPlusResolver()],}),],}
})
如果报错:RROR TypeError: AutoImport is not a function TypeError,则需要降版本:
ERROR TypeError: AutoImport is not a function TypeError: AutoImport is not a-CSDN博客
使用:
重启项目
快速生成模版快捷键
登录页面
表单:
解决外边距塌陷:
css解决外边距塌陷的7种方法_css外边距塌陷-CSDN博客
内容:
<template><div class="login-box"><el-formref="ruleFormRef":model="ruleForm"status-icon:rules="rules"label-width="80px"class="demo-ruleForm"><h2>后台管理系统</h2><el-form-item label="姓名" prop="name"><el-input v-model="ruleForm.name" type="name" autocomplete="off" /></el-form-item><el-form-item label="密码" prop="pass"><el-input v-model="ruleForm.pass" type="password" autocomplete="off" /></el-form-item><el-form-item><el-button class="login-btn" type="primary" @click="submitForm(ruleFormRef)">登录</el-button><el-button class="login-btn" @click="resetForm(ruleFormRef)">重置</el-button></el-form-item></el-form></div>
</template><script setup lang="ts">
import { reactive, ref } from 'vue'
import type { FormInstance, FormRules } from 'element-plus'const ruleFormRef = ref<FormInstance>()const rules = reactive<FormRules>({name: [{required: true,trigger: 'blur',},{min: 3,max: 5,message: 'Length should be 3 to 5',trigger: 'blur',},],pass: [{required: true,trigger: 'blur',},{min: 3,max: 5,message: 'Length should be 6 to 8',trigger: 'blur',},]
});const ruleForm = reactive({pass: '',name: '',
})const submitForm = (formEl: FormInstance | undefined) => {if (!formEl) returnformEl.validate((valid) => {if (valid) {console.log('submit!')} else {console.log('error submit!')return false}})
}const resetForm = (formEl: FormInstance | undefined) => {if (!formEl) returnformEl.resetFields()
}
</script><style scoped lang="scss">.login-box{width: 100%;height: 100%;background:url("../assets/bg.png");padding: 1px;text-align: center;.demo-ruleForm{width: 500px;background-color: white;margin:100px auto;padding: 20px;border-radius: 20px;}.login-btn{width: 40%;}h2{margin-bottom: 16px;}}
</style>
使用ts对数据类型进行限制
新建一个文件夹type用于数据存放
export interface LoginForm{pass:string,name:string,
}export class LoginData{ruleForm:LoginForm={pass:"",name:""}
}
在之前对pass name的数据定义中,并没有规范类型
在定义类型之后:双向绑定
登录请求
axios中文网|axios API 中文文档 | axios
npm install axios --save-dev
新建 request/index.ts 使用index.ts的好处
https://juejin.cn/post/7221004205271646245
index.ts
import axios from "axios";const mockType = 'cbzMock' // fastMock
const baseURL = mockType === 'cbzMock' ? 'https://mock.presstime.cn/mock/63569fbbbee0a00099ca48a1/api/vue-ts-mall-demo' : 'https://www.fastmock.site/mock/bf1fcb3c2e2945669c2c8d0ecb8009b8/api'
//创建axios实例
const service = axios.create({baseURL: baseURL,timeout: 5000,headers: {"Content-type" : "application/json;charset=utf-8"}
})//请求拦截
service.interceptors.request.use((config) => {config.headers = config.headers || {}if(localStorage.getItem("token")){config.headers.token = localStorage.getItem("token") || ""}return config
})//响应拦截
service.interceptors.response.use(({ data }) => {const code : number = data.data.codeif(code != 200){return Promise.reject(data)}return data
},(err) => {console.log(err)
})export default service
api.ts
import service from "@/request/index";
import {LoginData} from "@/type/login";// 登录接口
export function login(data: LoginData) {return service({url: "/login",method: "POST",data})
}// 商品列表接口
export function getGoodsList(){return service({url: "/getGoodsList",method: "GET"})
}// 用户列表接口
export function getUserList(){return service({url: "/getUserList",method: "GET"})
}// 角色列表接口
export function getRoleList(){return service({url: "/getRoleList",method: "GET"})
}// 权限列表接口
export function getAuthorityList(){return service({url: "/getAuthorityList",method: "GET"})
}
登录操作
const submitForm = (formEl: FormInstance | undefined) => {if (!formEl) returnformEl.validate((valid) => {if (valid) {console.log('submit!')//假装成功!login(ruleForm).then( (res)=>console.log(res) ).catch( (err)=>{ console.log(err)//登录成功,切换到主页面router.replace({ name:'mainview'});});} else {console.log('error submit!')return false}})
}
主页面
布局容器
https://element-plus.org/zh-CN/component/container.html
header
<template><div>main view</div><div class="common-layout"><el-container><el-header><el-row :gutter="20"><el-col :span="4"> <img src="../assets/logo.png" alt="" class="icon-logo"> <div class="grid-content ep-bg-purple" /></el-col><el-col :span="16"> <h2 class="title">后台管理系统</h2> <div class="grid-content ep-bg-purple" /></el-col><el-col :span="4"> <span class="quit">退出登录</span> <div class="grid-content ep-bg-purple" /></el-col></el-row></el-header><el-container><el-aside width="200px">Aside</el-aside><el-main>Main</el-main></el-container></el-container></div></template><script setup lang="ts"></script><style scoped lang="scss">
.el-header{height: 80px;background: #666;.icon-logo{height: 80px;}.title ,.quit{text-align: center;height: 80px;line-height: 80px;}}
</style>
height 和line-height 的设置https://www.cnblogs.com/pwindy/p/13026176.html
实现上下居中的对齐,如果不设置line-height ,。则会居上
Aside
如何实现侧边栏一直到底部:
<template><div>main view</div><div class="common-layout"><el-container><el-header><el-row :gutter="20"><el-col :span="4"> <img src="../assets/logo.png" alt="" class="icon-logo"> <div class="grid-content ep-bg-purple" /></el-col><el-col :span="16"> <h2 class="title">后台管理系统</h2> <div class="grid-content ep-bg-purple" /></el-col><el-col :span="4"> <span class="quit">退出登录</span> <div class="grid-content ep-bg-purple" /></el-col></el-row></el-header><el-container><el-aside width="200px"><el-col :span="120"><el-menuactive-text-color="#ffd04b"background-color="#545c64"class="el-menu-vertical-demo"default-active="2"text-color="#fff"><el-menu-item index="2"><el-icon><icon-menu /></el-icon><span>商品列表</span></el-menu-item></el-menu></el-col></el-aside><el-main>Main</el-main></el-container></el-container></div></template><script setup lang="ts">
import {Menu as IconMenu,Setting,
} from '@element-plus/icons-vue'
const handleOpen = (key: string, keyPath: string[]) => {console.log(key, keyPath)
}
const handleClose = (key: string, keyPath: string[]) => {console.log(key, keyPath)
}
</script><style scoped lang="scss">
.el-header{height: 80px;background: #666;.icon-logo{height: 80px;}.title ,.quit{text-align: center;height: 80px;line-height: 80px;}
}.el-aside{.el-menu{height: calc(100vh - 80px);}}
</style>
可以通过计算:100vh即 100%的垂直视口 - header高度80px
.el-aside{.el-menu{height: calc(100vh - 80px);}}
动态路由
<template><div class="common-layout"><el-container><el-header><el-row :gutter="20"><el-col :span="4"> <img src="../assets/logo.png" alt="" class="icon-logo"> <div class="grid-content ep-bg-purple" /></el-col><el-col :span="16"> <h2 class="title">后台管理系统</h2> <div class="grid-content ep-bg-purple" /></el-col><el-col :span="4"> <span class="quit">退出登录</span> <div class="grid-content ep-bg-purple" /></el-col></el-row></el-header><el-container><el-aside width="120px"><el-menuactive-text-color="#ffd04b"background-color="#545c64"class="el-menu-vertical-demo"default-active="2"text-color="#fff"router><!--router开启路由模式 可以通过标签的index 进行跳转--><el-menu-item :index="item.path" v-for="item in subRouterList" :key="item.path"><span>{{item.meta.title}}</span></el-menu-item></el-menu></el-aside><!--设置路由出口--><el-main> <router-view></router-view></el-main></el-container></el-container></div></template><script setup lang="ts">
import {
} from '@element-plus/icons-vue'import { useRouter } from 'vue-router';
const router = useRouter();
const subRouterList = router.getRoutes().filter( v=>
v.meta.isShow)
console.log(subRouterList)</script><style scoped lang="scss">
.el-header{height: 80px;background: #666;.icon-logo{height: 80px;}.title ,.quit{text-align: center;height: 80px;line-height: 80px;}
}.el-aside{.el-menu{height: calc(100vh - 80px);}
}</style>
子路由设置:children
import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router'
import GoodsView from '@/views/GoodsView.vue'
import LoginView from '../views/LoginView.vue'
import MainView from '../views/MainView.vue'
import UserView from '@/views/UserView.vue'
const routes: Array<RouteRecordRaw> = [{path: '/mainview',name: 'mainview',component: MainView,children:[{path:'goods',name:'goods',meta:{isShow:true,title:"商品列表"},component:GoodsView},{path:'user',name:'user',meta:{isShow:true,title:"用户列表"},component:UserView}]},{path: '/login',name: 'login',component:LoginView}
]const router = createRouter({history: createWebHistory(process.env.BASE_URL),routes
})export default router
商品页面
搜索
列表
分页展示
列表数据的获取:
数据定义:
export interface Goods{id:number,userId:number,title:string,introduce:string,
}interface SelectData{id:number,userId:number,title:string,introduce:string,curPage:number,count:number,pageSize:number,
}export class GoodsPages{// 被选择的数据, 查询时用selected_data:SelectData ={ userId: 0,id: 0,title: "",introduce: "",curPage: 1,count: 0,pageSize: 10};// 展示的商品数据goods_list: Goods[] = []
}
数据获取:
<script setup lang="ts">
import { computed, onMounted } from 'vue';
import { getGoodsList } from '@/request/api';
import {GoodsPages } from '@/type/goods';
import { reactive } from 'vue'//定义数据
const goods_data = reactive(new GoodsPages())//当前页数据
const curPagaData = reactive( {comList:computed( ()=>{return goods_data.goods_list.slice((goods_data.selected_data.curPage-1)*10,goods_data.selected_data.curPage*10);})} )onMounted( ()=>{console.log('onMounted');//请求数据getGoodsList().then( (res)=>{ goods_data.goods_list = res.data.data;console.log('goodsList',goods_data.goods_list) }).catch( (err)=>{console.log(err) })}
)</script>
分页展示逻辑:
<div>curPagaData.comList 是列表当前页展示的数据<el-table :data=curPagaData.comList border style="width: 100%"><el-table-column prop="id" label="id" width="180" /> 使用列表中的字段与 每一列标题 <el-table-column prop="title" label="title" width="180" /><el-table-column prop="introduce" label="introduce" /></el-table>分页展示<el-pagination layout="prev, pager, next" v-model:page-size=goods_data.selected_data.pageSize 页面的大小:设置为10v-model:current-page=goods_data.selected_data.curPage 当前是第几页:total=goods_data.goods_list.length /> 总页数:是请求到的列表的总数</div>
查询逻辑
<div class="select-box"><el-form :inline="true" :model="goods_data.selected_data" class="demo-form-inline"><el-form-item label="用户Title"><el-input v-model="goods_data.selected_data.title" placeholder="用户Title" clearable /></el-form-item> <el-form-item><el-button type="primary" @click="onSubmit">查询</el-button></el-form-item></el-form></div>//查询数据
const onSubmit = () => {console.log('submit! suc',goods_data.selected_data.title)//如果是空的,则使用原来的数据if (goods_data.selected_data.title === ''){goods_data.goods_list = last_data;goods_data.selected_data.curPage=1;return;}//过滤数据let filterArry:Goods[]=[];//过滤数据filterArry = goods_data.goods_list.filter( (value)=>{return value.title.indexOf(goods_data.selected_data.title) !== -1} );goods_data.goods_list = filterArry;goods_data.selected_data.curPage=1;
}onMounted( ()=>{getGoodsList().then( (res)=>{ goods_data.goods_list = res.data.data;//记录当前的数据,用于恢复last_data = goods_data.goods_list;console.log('goodsList',goods_data.goods_list) }).catch( (err)=>{console.log(err) })}
)
用户列表
展示
自定义列模版:展示用户角色
<template #default="scope"><el-text class="mx-1" v-for="item in scope.row.role" :key="item.role"> {{item.roleName +" "}}</el-text>
选择器
数据定义:
/*
获取用户列表接口 GET /getUserList
请求报文:无
响应报文:
{"data": {"code": 200,"data": [{"id": 1,"nickName": "小明","userName": "小明","role": [{"role": 1,"roleName": "管理员"},{"role": 2,"roleName": "普通用户"}]},{"id": 2,"nickName": "红红","userName": "红红","role": [{"role": 1,"roleName": "管理员"}]},{"id": 3,"nickName": "绿绿","userName": "绿绿","role": [{"role": 2,"roleName": "普通用户"}]}]}
}获取角色列表接口 GET /getRoleList
请求报文:无
响应报文:
{"data": {"code": 200,"data": [{"roleName": "管理员","roleId": 1,"authority": [1,2,4,5,6,7,8,9,11,13,14,15,16]},{"roleName": "普通用户","roleId": 2,"authority": [1,3,4,6,7,8,9,11,12,13]}]}
}*/interface RoloInfo{role:number,roleName:string,
}interface UserInfo{id:number,nickName:string,userName:stringrole:RoloInfo[],
}interface RoleType{roleName:string,roleId:number,authority:Array<number>[],
}interface QueryUser{nickName: string, // 用户别名role: number // 角色编号
}export class UserPage{roleList:RoleType[]=[];userList:UserInfo[]=[];selectedData:QueryUser={nickName:'',role:0}
}
vue:
<template><div class="select-box"><el-form :inline="true" :model="userPage.selectedData" class="demo-form-inline"><el-form-item label="用户Title"><el-input v-model="userPage.selectedData.nickName" placeholder="用户nickName" clearable /></el-form-item> <el-form-item label="用户roleid"><el-select v-model="userPage.selectedData.role" placeholder="全部" style="width: 240px"><el-option:key="0"label="全部":value="0"/><el-optionv-for="item in userPage.roleList":key="item.roleId":label="item.roleName":value="item.roleId"/></el-select></el-form-item> <el-form-item><el-button type="primary" @click="onSubmit">查询</el-button></el-form-item></el-form></div><div><el-table :data=userPage.userList border style="width: 100%"><el-table-column prop="id" label="编号" width="180" /><el-table-column prop="nickName" label="用户昵称" width="180" /><el-table-column prop="role" label="用户角色" ><template #default="scope"><el-text class="mx-1" v-for="item in scope.row.role" :key="item.role"> {{item.roleName +" "}}</el-text> </template></el-table-column></el-table></div>
</template><script setup lang="ts">
import { getRoleList, getUserList } from '@/request/api';
import { UserInfo, UserPage } from '@/type/userinfo';
import { onMounted, reactive } from 'vue';const userPage = reactive(new UserPage())
let lastUserList:UserInfo[]=[];onMounted(()=>{getUserList().then((res)=>{userPage.userList = res.data.data;lastUserList =userPage.userList;console.log("userList",userPage.userList);})getRoleList().then((res)=>{userPage.roleList =res.data.data;console.log("roleList",userPage.roleList);})
})const onSubmit=()=>{console.log('onsubmit',userPage.selectedData.role,userPage.selectedData.nickName);if (userPage.selectedData.role===0&& userPage.selectedData.nickName===''){userPage.userList = lastUserList;return;}let list:UserInfo[]= userPage.userList.filter((value)=>{let found = false;if (userPage.selectedData.role===0){return value.nickName.indexOf(userPage.selectedData.nickName)!=-1;}if (userPage.selectedData.nickName===''){for(let i=0;i<value.role.length;++i){if (value.role[i].role === userPage.selectedData.role){return true;}}}if (value.nickName.indexOf(userPage.selectedData.nickName)!=-1){for(let i=0;i<value.role.length;++i){if (value.role[i].role === userPage.selectedData.role){return true;}}}return found;});userPage.userList = list;
}</script><style scoped></style>
编辑
编辑按钮
<el-table-column prop="role" label="操作" ><template #default="scope"><el-button type="primary" @click="handleEditUser(scope.row)">编辑</el-button></template> </el-table-column>
弹出编辑框
<!-- 编辑用户的弹出窗--><el-dialog v-model="userPage.editShow" title="编辑用户信息"><el-form :model="userPage.editUser"><el-form-item label="用户昵称" label-width="120px"><el-input v-model="userPage.editUser.nickName" autocomplete="off" /></el-form-item><el-form-item label="用户角色" label-width="120px"><el-select multiple v-model="userPage.editUser.role" class="m-2" size="large" placeholder="请选择角色"><el-optionv-for="item in userPage.roleList":key="item.roleId":label="item.roleName":value="item.roleId"/></el-select></el-form-item></el-form><template #footer><span class="dialog-footer"><el-button @click="userPage.editShow = false">取消</el-button><el-button type="primary" @click="ensureEditUser">修改</el-button></span></template></el-dialog>
数据结构
// 用户编辑接口
export interface UserEdit {id: number, // 用户idnickName: string, // 用户昵称role: number[], // 用户角色userName: string // 用户名
}export class UserPage{roleList:RoleType[]=[];userList:UserInfo[]=[];selectedData:QueryUser={nickName:'',role:0}editShow = false // 是否显示编辑用户弹出窗// 编辑用户时用到的对象editUser: UserEdit = {id: 0,nickName: "",role: [],userName: ""}
}
// 编辑用户弹窗
const handleEditUser = (row: UserInfo) => {userPage.editShow = trueuserPage.editUser = {id: row.id,nickName: row.nickName,role: row.role.map((value) => value.role),userName: ""}
}const ensureEditUser = () => {console.log(userPage.editUser)userPage.editShow = false;let obj= userPage.userList.find( (value)=>{ value.id === userPage.editUser.id})for(let i=0;i<userPage.userList.length;++i){if (userPage.editUser.id === userPage.userList[i].id){userPage.userList[i].nickName= userPage.editUser.nickName;userPage.userList[i].role =[];for(let item of userPage.roleList){if (userPage.editUser.role.find( (value)=>( value === item.roleId) )){userPage.userList[i].role.push({role:item.roleId,roleName:item.roleName})}}}}console.log(userPage.userList)
}
查询
let lastUserList:UserInfo[]=[];
const onSubmit=()=>{console.log('onsubmit',userPage.selectedData.role,userPage.selectedData.nickName);if (userPage.selectedData.role===0&& userPage.selectedData.nickName===''){userPage.userList = lastUserList;return;}let list:UserInfo[]= userPage.userList.filter((value)=>{let found = false;if (userPage.selectedData.role===0){return value.nickName.indexOf(userPage.selectedData.nickName)!=-1;}if (userPage.selectedData.nickName===''){for(let i=0;i<value.role.length;++i){if (value.role[i].role === userPage.selectedData.role){return true;}}}if (value.nickName.indexOf(userPage.selectedData.nickName)!=-1){for(let i=0;i<value.role.length;++i){if (value.role[i].role === userPage.selectedData.role){return true;}}}return found;});userPage.userList = list;
}
代码
通过百度网盘分享的文件:src.zip
链接:https://pan.baidu.com/s/1p9YR2W0-DuIZs1_UJwDh6g?pwd=kdhs
提取码:kdhs