这篇文章来完成用户组件 也就是增删改查表格
用户页面信息页面由头部,表格,和弹框组成
js
<template>
<div class="user-header">
<el-button type="primary" @click="handleAdd">新增</el-button>
<el-form :inline="true" :model="formInline">
<el-form-item label="请输入">
<el-input placeholder="请输入用户名" v-model="formInline.keyWord">
</el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleSearch">搜索</el-button>
</el-form-item>
</el-form>
</div>
<div class="table">
<el-table :data="tableData" style="width:100%">
<el-table-column
v-for="item in tableLabel"
:key="item.prop"
:width="item.width ? item.width :125"
:prop="item.prop"
:label="item.label"
></el-table-column>
<el-table-column fixed="right" label="Operations" min-width="150">
<template #="scope">
<el-button type="primary" size="small" @click="handleEdit(scope.row)">
编辑
</el-button>
<el-button type="danger" size="small" @click="handleDelete(scope.row)">
删除
</el-button>
</template>
</el-table-column>
</el-table>
<el-pagination
class="pager"
background
layout="prev, pager,next"
size="small"
:total="config.total"
@current-change="handleChange"
>
</el-pagination>
</div>
<el-dialog
v-model="dialogVisible"
:title="action === 'add' ? '新增用户' : '编辑用户'"
width="35%"
:before-close="handleClose"
>
<!--需要注意的是设置了:inline="true",
会对el-select的样式造成影响,我们通过给他设置一个class=select-clearn
在css进行处理-->
<el-form :inline="true" :model="formUser" :rules="rules" ref="userForm">
<el-row>
<el-col :span="12">
<el-form-item label="姓名" prop="name">
<el-input v-model="formUser.name" placeholder="请输入姓名" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="年龄" prop="age">
<el-input v-model.number="formUser.age" placeholder="请输入年龄" />
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="12">
<el-form-item class="select-clearn" label="性别" prop="sex">
<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="出生日期" prop="birth">
<el-date-picker
v-model="formUser.birth"
type="date"
placeholder="请输入"
style="width: 100%"
/>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-form-item
label="地址"
prop="addr"
>
<el-input v-model="formUser.addr" placeholder="请输入地址" />
</el-form-item>
</el-row>
<el-row style="justify-content: flex-end">
<el-form-item>
<el-button type="primary" @click="handleCancel">取消</el-button>
<el-button type="primary" @click="onSubmit">确定</el-button>
</el-form-item>
</el-row>
</el-form>
</el-dialog>
</template>
<style lang="scss" scoped>
.user-header{
display:flex;
justify-content: space-between;
}
.table{
position:relative;
height:520px;
.pager{
position:absolute;
right:10px;
bottom:30px;
}
.el-table{
width:100%;
height:500px;
}
}
</style>
在api文件夹中api.js文件加入
js
getUserData(data){
return request({
url:"/home/getUserData",
method:'get',
data
})
},
deleteUser(data){
return request({
url:"/home/deleteUser",
method:'get',
data
})
},
addUser(params) {
return request({
url: '/user/addUser',
method: 'post',
data: params
})
},
editUser(params) {
return request({
url: '/user/editUser',
method: 'post',
data: params
})
},
在mock.js中加入
js
Mock.mock(/api\/home\/getUserData/,"get",userApi.getUserList)
Mock.mock(/api\/home\/deleteUser/,"get",userApi.deleteUser)
Mock.mock(/api\/user\/addUser/,"post", userApi.createUser)
Mock.mock(/api\/user\/editUser/, "post",userApi.updateUser)
mockData文件夹中加入user.js模拟数据
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 = 200
//模拟200条用户数据
for (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 => {
//limit默认是10,因为分页器默认也是一页10个
const { name, page = 1, limit = 10 } = param2Obj(config.url)
const mockList = List.filter(user => {
//如果name存在会,根据name筛选数据
if (name && user.name.indexOf(name) === -1) return false
return true
})
//分页
const pageList = mockList.filter((item, index) => index < limit * page && index >= limit * (page - 1))
return {
code: 200,
data: {
list: pageList,
count: mockList.length, //数据总条数需要返回
}
}
},
//在原来的export default 中添加
/**
* 删除用户
* @param id
* @return {*}
*/
deleteUser: config => {
const { id } = param2Obj(config.url)
if (!id) {
return {
code: -999,
message: '参数不正确'
}
} else {
List = List.filter(u => u.id !== id)
return {
code: 200,
message: '删除成功'
}
}
},
/**
* 增加用户
* @param name, addr, age, birth, sex
* @return {{code: number, data: {message: string}}}
*/
createUser: config => {
const { name, addr, age, birth, sex } = JSON.parse(config.body)
List.unshift({
id: Mock.Random.guid(),
name: name,
addr: addr,
age: age,
birth: birth,
sex: sex
})
return {
code: 200,
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 = name
u.addr = addr
u.age = age
u.birth = birth
u.sex = sex_num
return true
}
})
return {
code: 200,
data: {
message: '编辑成功'
}
}
}
}
有了相应的请求数据 就可以实现增删改差以及相应的功能
js
<script setup>
//
import {ref,getCurrentInstance,onMounted,reactive,nextTick} from 'vue'
const tableLabel = reactive([
{
prop:'name',
label:'姓名'
},
{
prop:'age',
label:'年龄'
},
{
prop:'sexLabel',
label:'性别'
},
{
prop:'birth',
label:'出生日期',
width:200
},
{
prop:'addr',
label:'地址',
width:400
},
])
const tableData =ref([])
const {proxy} = getCurrentInstance()
const getUserData = async () => {
let data = await proxy.$api.getUserData(config)
// console.log(data)
tableData.value = data.list.map(item =>({
...item,
sexLabel: item.sex === 1 ? '男' : '女'
}))
config.total = data.count
}
const formInline = reactive({
keyWord:''
})
const config =reactive({
name:'',
total:0,
page:1
})
const handleSearch=() => {
config.name = formInline.keyWord
getUserData()
}
const handleChange = (page) => {
config.page = page
getUserData()
}
const handleDelete =(val) => {
ElMessageBox.confirm("你确定要删除吗?").then(async () => {
await proxy.$api.deleteUser({id:val.id})
ElMessage({
showClose:true,
message:'删除成功',
type:'success'
})
getUserData()
}
)
}
const action =ref('add')
const dialogVisible = ref(false)
const formUser = reactive({
sex:"1"
})
const rules = reactive({
name: [{ required: true, message: "姓名是必填项", trigger: "blur" }],
age: [
{ required: true, message: "年龄是必填项", trigger: "blur" },
{ type: "number", message: "年龄必须是数字" },
],
sex: [{ required: true, message: "性别是必选项", trigger: "change" }],
birth: [{ required: true, message: "出生日期是必选项" }],
addr:[{ required: true, message: '地址是必填项' }]
})
//这个方法之前定义过
const handleAdd = () => {
action.value="add"
//打开对话窗
dialogVisible.value=true
}
//对话框右上角的关闭事件
const handleClose = () => {
//获取到表单dom,执行resetFields重置表单
// proxy.$refs["userForm"].resetFields()
//关闭对话框
dialogVisible.value=false
}
//对话框右下角的取消事件
const handleCancel = () => {
// proxy.$refs["userForm"].resetFields()
dialogVisible.value=false
}
//格式化日期,格式化为:1997-01-02这种
const timeFormat = (time) => {
var time = new Date(time);
var year = time.getFullYear();
var month = time.getMonth() + 1;
var date = time.getDate();
function add(m) {
return m < 10 ? "0" + m : m;
}
return year + "-" + add(month) + "-" + add(date);
}
const onSubmit = () => {
//执行userForm表单的validate进行规则校验,传入一个回调函数,回调函数会接受到一个是否校验通过的变量
proxy.$refs["userForm"].validate(async (valid)=>{
//如果校验成功
if (valid) {
//res用于接收添加用户或者编辑用户接口的返回值
let res=null
//这里无论是新增或者是编辑,我们都要对这个日期进行一个格式化
//如果不是1997-01-02这种格式,使用timeFormat方法进行格式化
formUser.birth=/^\d{4}-\d{2}-\d{2}$/.test(formUser.birth) ? formUser.birth : timeFormat(formUser.birth)
//如果当前的操作是新增,则调用新增接口
if (action.value == "add") {
res = await proxy.$api.addUser(formUser);
}else if(action.value == "edit"){
res = await proxy.$api.editUser(formUser)
}
//如果接口调用成功
if(res){
//关闭对话框,重置表单,重新请求用户数据
dialogVisible.value = false;
proxy.$refs["userForm"].resetFields()
getUserData()
}
//如果校验失败
}else {
ElMessage({
showClose: true,
message: "请输入正确的内容",
type: "error",
})
}
})
}
const handleEdit = (val) => {
action.value="edit"
dialogVisible.value=true
nextTick(()=>{
//因为在第一次显示弹窗的时候form组件没有加载出来,如果直接对formUser赋值,这个值会作为form表单的初始值
//所以使用nextTick,赋值的操作在一个微任务中,这样就可以避免在from表单加载之前赋值
Object.assign(formUser,{...val,sex:""+val.sex})
//这里需要改变sex数据类型,是因为el-option的value有类型的校验
})
}
onMounted(() => {
getUserData()
})
</script>
用户表格实现完成
接下来实现面包屑组件
js
<template>
<div class="tags">
<el-tag
v-for="(tag,index) in tags"
:key="tag.name"
:closable="tag.name !== 'home'"
:effect = "route.name === tag.name ? 'dark': 'plain'"
@click="handleMenu(tag)"
@close="handleClose(tag,index)"
>{{tag.label}}
</el-tag>
</div>
</template>
<script setup>
import {ref,computed} from 'vue'
import {useRoute,useRouter} from 'vue-router'
import {useAllDataStore} from '../stores/index.js'
const router = useRouter()
const store = useAllDataStore()
const tags = computed(()=>store.$state.tags)
const route = useRoute()
const handleMenu = (tag) => {
router.push(tag.name)
store.selectMenu(tag)
}
const handleClose = (tag,index) => {
store.updateMenu(tag)
if(index === store.$state.tags.length){
store.selectMenu(tags.value[index-1])
router.push(tags.value[index-1].name)
}else{
store.selectMenu(tags.value[index])
router.push(tags.value[index].name)
}
}
</script>
<style lang="scss" scoped>
.tags{
margin:20px 0 0 20px;
}
el-tag{
margin-right:10px;
}
</style>
在store/index.js中添加内容
js
export const useAllDataStore = defineStore('allData', {
state:() => {
return {
isCollapse: true,
tags:[
{
path:'/home',
name:'home',
label:'首页',
icon:'home',
}
],
currentMenu:null,
}
},
actions: {
selectMenu(val){
if(val.name === "home"){
this.currentMenu =null;
}else{
this.currentMenu = val
// console.log()
let index = this.tags.findIndex((item)=> item.name === val.name);
index === -1 ? this.tags.push(val) : "";
}
},
updateMenu(tag){
let index =this.tags.findIndex((item) => item.name === tag.name)
this.tags.splice(index,1)
},
搭建登陆页面 动态添加路由 有两个账号 账号一admin密码123456与账号二oner密码123456
js
<template>
<div class="body-login">
<el-form :model="loginForm" class="login-container">
<h1>欢迎登陆</h1>
<el-form-item>
<el-input type="input" placeholder="请输入账号"
v-model="loginForm.username"></el-input>
</el-form-item>
<el-form-item>
<el-input type="input" placeholder="请输入密码"
v-model="loginForm.password"></el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleLogin"> 登陆</el-button>
</el-form-item>
</el-form>
</div>
</template>
<script setup>
import {reactive,getCurrentInstance} from 'vue'
import {useAllDataStore} from '../stores/index.js'
import {useRouter} from 'vue-router'
const loginForm = reactive({
username:'',
password:'',
})
const {proxy} = getCurrentInstance()
const store = useAllDataStore()
const router = useRouter()
const handleLogin = async () => {
const res = await proxy.$api.getMenu(loginForm)
store.updateMenuList(res.menuList)
store.$state.token = res.token
store.addMenu(router)
router.push('/home')
}
</script>
<style lang="scss" scoped>
.body-login{
width:100%;
height:100%;
background-image:url("../assets/images/background.png");
background-size:100%;
overflow:hidden;
}
.login-container{
width:400px;
background-color:#fff;
border:1px solid #eaeaea;
border-radius:15px;
padding:35px 35px 15px 35px;
box-shadow:0 0 25px #cacaca;
margin:250px auto;
h1{
text-align:center;
margin-bottom:20px;
color:#505450;
}
:deep(.el-form-item__content){
justify-content:center;
}
}
</style>
更新store/index.js代码
js
import { defineStore } from 'pinia'
export const useAllDataStore = defineStore('allData', {
state:() => {
return {
isCollapse: true,
tags:[
{
path:'/home',
name:'home',
label:'首页',
icon:'home',
}
],
currentMenu:null,
menuList:[],
token:"",
routerList:[],
}
},
actions: {
selectMenu(val){
if(val.name === "home"){
this.currentMenu =null;
}else{
this.currentMenu = val
// console.log()
let index = this.tags.findIndex((item)=> item.name === val.name);
index === -1 ? this.tags.push(val) : "";
}
},
updateMenu(tag){
let index =this.tags.findIndex((item) => item.name === tag.name)
this.tags.splice(index,1)
},
updateMenuList(val){
this.menuList =val;
},
//动态添加路由
addMenu(router,type){
const menu = this.menuList
const module = import.meta.glob("../views/**/*.vue");
const routeArr = [];
menu.forEach((item)=>{
if(item.children){
item.children.forEach((val) => {
let url = `../views/${val.url}.vue`;
val.component = module[url];
routeArr.push(...item.children);
})
}else{
let url = `../views/${item.url}.vue`;
item.component = module[url];
routeArr.push(item)
}
})
this.routerList= [];
let routers = router.getRoutes()
routers.map((item) => {
if(item.name === 'Main' || item.name ==="Login"){
return ;
}else{
router.removeRoute(item.name)
}
})
routeArr.forEach(
(item) => {
this.routerList.push(router.addRoute("Main",item))
}
)
// this.routerList= [];
// let routers = router.getRoutes()
// routers.map((item) => {
// if(item.name === 'main' || item.name ==="login"){ return ;}else{
// router.removeRoute(item.name)
// }
// })
},
clean(){
this.routerList.forEach((item) => {
if(item) item()
})
this.$reset()
}
},
})
项目到此就结束了
源码地址:https://github.com/zhaimou/vue3-admin