在后端的服务器没有搭建完成之前,无论我们做什么功能我们都需要自己mock数据。具体如下(这里只展示一个模块的模拟数据,其余类似):
import type { MockMethod } from 'vite-plugin-mock'
const userList = [
{
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: '普通用户',
},
],
},
]
export default [
{
url: '/mock/api/getUserList',
method: 'get',
response: () => {
return {
code: 0,
message: 'success',
data: userList,
}
},
},
{
url: '/mock/api/editUser',
method: 'post',
response: ({ body }) => {
const { id, nickName, role } = body
const user = userList.find((item) => item.id === Number(id))
if (user) {
user.nickName = nickName
user.role = role.map((item: number) => {
return {
role: item,
roleName: item === 1 ? '管理员' : '普通用户',
}
})
}
const newUserList = userList.map((item) => {
if (item.id === user?.id) {
return user
}
return item
})
return {
code: 0,
message: '修改成功',
data: newUserList,
}
},
},
] as MockMethod[]
编写好mock数据以后,我们需要写好对应的接口,之后我们就可以正式开始功能开发了。
import RYRequest from '@/http'
import { IEditUser, IUserList, loginRequest, loginResponse } from './type'
import { BaseResponse } from '@/http/request/type'
export const userLogin = (data: loginRequest) =>
RYRequest.post<BaseResponse<loginResponse>>({ url: '/login', data })
export const getUserList = () =>
RYRequest.get<BaseResponse<IUserList[]>>({ url: '/getUserList' })
export const editUser = (data: IEditUser) =>
RYRequest.post<BaseResponse<IUserList[]>>({
url: '/editUser',
data,
})
export interface loginRequest {
username: string
password: string
}
export interface loginResponse {
username: string
accessToken: string
roles: string[]
}
export interface reLoginRequest {
accessToken: string
}
export interface IUserList {
id: number
nickName: string
userName: string
role: IRole[]
}
export interface IRole {
role: number
roleName: string
}
export interface IEditUser {
id: number
nickName: string
role: number[]
}
一、查数据
1.1 查全部数据以及查指定数据
查全部数据很简单,我们只需要调用相对应的接口拿到数据,然后通过element-plus的table组件完成数据的渲染就可以了,当然了,一般来说我们都是先搭建好HTML结构之后,在获取数据、渲染数据的。
<el-table :data="userList">
<el-table-column prop="id" label="编号" />
<el-table-column prop="nickName" label="昵称" />
<el-table-column prop="role" label="权限">
<template #default="{ row }: { row: IUserList }">
<tag
v-for="role in row.role"
:key="role.role"
:text="role.roleName"
theme="#95d475"
/>
</template>
</el-table-column>
<el-table-column label="操作">
<template #default="{ row }">
<el-button type="primary" size="small" @click="handleEdit(row)">
编辑
</el-button>
</template>
</el-table-column>
</el-table>
import { getUserList } from '@/api/user/user'
const userList = ref<IUserList[]>([])
const fetchUserList = async () => {
const res = await getUserList()
userList.value = res.data
}
onMounted(() => {
fetchUserList()
})
完成上面两个步骤,就可以完成对应数据的表格展示了,展示完数据之后,为了满足用户的需求,我们还需要编写对应的搜索功能来提供给用户查指定条件的数据,跟上面一样的步骤,我们首先需要搭建对应的HTML结构,具体的界面实现如下: 然后再实现对应逻辑,这里着重描述一下逻辑的实现,首先先看具体的逻辑实现:
const handleSearch = () => {
let res: IUserList[] = userList.value
if (searchData.role || searchData.nickName) {
if (searchData.nickName) {
res = res.filter((item) => item.nickName.includes(searchData.nickName))
}
if (searchData.role) {
res = searchData.nickName ? res : userList.value
res = res.filter((item) => {
return item.role.find((r) => r.role === searchData.role)
})
}
} else {
res = userList.value
}
userList.value = res
}
watch(
[() => searchData.nickName, () => searchData.role],
([nickName, role]) => {
if (!nickName || !role) {
fetchUserList()
}
},
)
根据界面结构,我们可以知道用户可以根据两个条件来实现对数据的搜索,因此我们需要对两个数据分别进行判断,除此之外,我们还需要声明一个变量来保存所有的数据,防止对原来的数据进行修改。
1.2 数据的分页展示
为了体现与后端的配合,我们mock数据时提前将数据分页之后再发送给前端,前端根据后端提供的参数进行数据的渲染,具体的操作如下:
{
url: '/mock/api/project',
method: 'get',
response: ({ query }) => {
const { pageNum, pageSize } = query
const length = data.length
const totalPage = Math.ceil(length / pageSize)
const startIndex = (pageNum - 1) * pageSize
const endIndex = pageNum * pageSize
const currentData = data.slice(startIndex, endIndex)
return {
code: 0,
message: 'success',
data: {
list: currentData,
pageNum: Number(pageNum),
pageSize: Number(pageSize),
total: length,
totalPage,
},
}
},
},
这是项目中项目描述的数据接口,这个接口根据前端传递的当前页码,以及每页数据的数量两个参数对数据进行分页。编写好接口之后,我们在前端使用axios进行调用,具体如下:
export const getProjectList = (params: IParamsType) => {
return ryRequest.get<BaseResponse<IProjectListType>>({
url: '/project',
params,
})
}
有了以上这两步之后,我们就可以画HTML页面以及实现数据展示了,我们使用的是element-plus组件库的分页组件,使用这个组件,将从后端传递过来的参数传给这个组件,我们就可以实现分页功能了。具体实现如下:
<script setup lang="ts">
import { getProjectItem, getProjectList } from '@/api/project/project'
import { IProjectListItemType } from '@/api/project/type'
import { IPageInfo, ISearchData } from './type'
const pageInfo = reactive<IPageInfo>({
currentPage: 1,
pageSize: 5,
total: 0,
})
const searchData = reactive<ISearchData>({
title: '',
introduce: '',
})
const tableData = ref<IProjectListItemType[]>([])
const fetchTableData = async (pageNum: number = 1, pageSize: number = 5) => {
const result = await getProjectList({
pageNum,
pageSize,
})
tableData.value = result.data.list
pageInfo.total = result.data.total
}
onMounted(() => {
fetchTableData(pageInfo.currentPage, pageInfo.pageSize)
})
const handleCurrentChange = (val: number) => {
pageInfo.currentPage = val
fetchTableData(val, pageInfo.pageSize)
}
const handleSearch = async () => {
if (searchData.title) {
const res = await getProjectItem(searchData.title)
tableData.value = res.data
}
}
watch(
() => searchData.title,
(val) => {
if (!val) {
fetchTableData(pageInfo.currentPage, pageInfo.pageSize)
}
},
)
</script>
<template>
<div class="home-container">
<el-form :model="searchData" :inline="true">
<el-form-item label="名称">
<el-input v-model="searchData.title" placeholder="请输入名称" />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleSearch">查询</el-button>
</el-form-item>
</el-form>
<el-table :data="tableData">
<el-table-column prop="id" label="编号" width="180" />
<el-table-column prop="title" label="名称" width="180" />
<el-table-column prop="introduce" label="介绍" />
</el-table>
<div class="pagination">
<el-pagination
v-model:current-page="pageInfo.currentPage"
:page-size="pageInfo.pageSize"
layout="prev, pager, next, jumper"
:total="pageInfo.total"
@current-change="handleCurrentChange"
/>
</div>
</div>
</template>
二、添加数据以及修改数据
无论是添加数据还是修改数据,我们在后端的接口没有好之前,都要自己mock接口,使用axios获取我们mock的数据,这些步骤之前已经讲过,这里不再赘述,直接开始业务的开发,在后台管理系统中,添加数据一般都需要用到dialog弹出框,不过在element-plus组件库,他提供了一个新的组件MessageBox,这个组件比dialog更加简洁,我们可以直接使用JavaScript进行调用,具体逻辑如下:
const handleAdd = () => {
ElMessageBox.prompt('请输入角色名称', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
})
.then(async ({ value }: { value: string }) => {
await addRole(value)
ElMessage.success('添加成功')
await fetchRoleList()
})
.catch(() => {
ElMessage.info('点击了取消')
})
}
ElmessageBox.prompt返回一个promise,然后我们就可以实现添加数据的功能了。具体就是调用接口添加数据,然后刷新数据。 修改数据的界面采取dialog+form来实现,修改数据的具体步骤跟添加数据一样,都是调用接口修改,修改成功之后刷新数据,不过修改数据比添加数据多了一步数据回显,当用户点击编辑数据弹出框显示时,我们需要让用户看到修改前的数据,这里用到了对象的浅拷贝(不能将获取到的值直接赋值给变量,这样会让变量失去响应),具体操作如下:
const handleEdit = (row: IUserList) => {
dialogVisible.value = true
Object.assign(editData, {
...row,
role: row.role.map((r) => r.role),
})
}
当然除了Object.assign这个比较方便方法以外,我们也可以一个一个赋值,或者使用展开运算符,这里不过多描述。完成数据回显以后,我们要做的就是掉接口了。除此之外,添加数据也能够使用dialog+form的方式来实现,具体与修改数据类似,不过如果添加与修改共用一个dialog时,需要将编辑时保存的数据清空。
三、删除数据
删除数据应该是后台管理系统中最简单的了,我们在点击删除按钮时需要将点击数据的id传给后端就行了,然后后端将删除后的数据返回回来,我们将返回来的值赋值给变量就行了。具体的逻辑如下:
const handleDelete = async (id: number) => {
const deleteRes = await deleteUser(id)
userList.value = deleteRes.data
ElMessage.success(deleteRes.message)
}