📋前言
书接上回,上一篇文章介绍了一个基于 Vue3
和 ElementPlus
的联系人列表管理后台小 demo (Vue3 + ElementPlus实战学习——模拟简单的联系人列表管理后台),在有了上一篇文章的基础上,我们试着用 axios
来获取数据,而不是用写死的数据,然后用 Node.js + Vue3 + ElementPlus
来实现联系人列表管理后台的 demo 。功能包括功能包括了数据的展示、添加功能、编辑功能、删除功能以及列表分页功能。
🎯demo 介绍
通过上一篇文章的铺垫,我们可以继续使用那套布局,因此使用到的 ElementPlus
组件也是基本上一样的。功能包括了数据的展示、编辑功能、删除功能以及列表分页功能,在这基础上,新添加了一个添加联系人的功能。
关于后台数据方面,不同于上一篇文章,这里使用到 Node.js
模拟后端接口环境。因此需要用到 axios
来获取数据,然后展示出来。
接下来我们来分析每个功能具体如何实现,以及核心代码,首先我们可以看到一些效果图如下。
添加联系人的模块。
编辑联系人的弹窗。
删除联系人的弹窗。
🎯后端与接口的调试
🧩关于运行后端项目
在搭建项目之前,我们要先把 Node 后端的数据和接口处理好,首先把后端接口跑起来,然后再进行接口的测试。
这里有两种方法运行后端接口,第一种是通过 cmd
来运行,第二种是直接在 vscode
上面跑。这里我认为在 vscode
上面跑比较方便,有功能的上的需求可以直接修改,同时也可以手动修改 JSON
数据。接下来我们来用这两种方法来运行一下。
首先看一下 Node 后端项目的目录结构,我们可以看到这个后端的 api
,包含非常经典的增删查改功能,同时也是本次项目使用到的接口名称。然后 index.js
是这个后端项目的入口文件,运行只需要把 index.js
跑起来即可,通过 node index
来运行后端接口程序。
我们先用 cmd
来跑一次,在该项目文件的目录栏输入 cmd
,打开 cmd
窗口,然后输入 node index
,回车运行。如果出现 http://127.0.0.1:9999/api/select 地址,就代表后端接口运行起来了。
然后我们再试试看用 vscode
来跑,打开终端(快捷键:Ctrl + ~ ),输入 node index
,回车运行。如果出现 http://127.0.0.1:9999/api/select 地址,就代表后端接口运行起来了。
🧩关于接口的调试
后端接口运行成功后,出现的 http://127.0.0.1:9999/api/select 这个地址,我们可以通过浏览器或相关软件(这里用到的是 postman)来测试一下这个接口。(图一为浏览器测试、图二为 postman 测试)
我们可以看到返回的是一个 JSON 格式数据,每请求一次这个地址,我们都可以看到终端出现查询结果的提示,这里我们请求了两次。
最后要注意,在开始写前端后台的代码时,要保持后端接口的运行,如果关闭了这个终端或者 Ctrl + c
了,这个后端接口服务会终止,导致数据获取不到。然后接下来我们来具体看看如何实现这个后台项目。
🎯功能分析
在分析具体功能之前,我们先看看这个后台的布局和设计。首先数据展示跟上一篇文章的基本上一模一样,都是实现分页功能,每页展示五条数据,操作包括了编辑和删除功能,不同的是多了个添加联系人的按钮功能,因此还新增一个添加联系人的窗口。
然后我们分析一下这个后台要用到的 Element 组件,比如有 el-row
、el-button
、el-form
、el-card
、el-dialog
、el-table
、el-pagination
等等。
这个后台的 template 部分结构很简单,只包括了数据列表和弹窗,通过 el-card
、el-table
、el-button
实现数据列表,el-dialog
、el-form
、el-button
实现弹窗的部分。
介绍完基本的布局和设计思路,我们来看看具体的功能有哪些,又是怎么样实现的。
🧩数据的展示与分页功能
上面我们也把后端接口服务跑起来了,同时也测试了接口的数据。接下来我们先通过 axios 来异步获取数据,我们可以通过下图的代码来获取数据。
通过 console.log(contactList.list);
我们可以在控制台看到成功获取到了数据。
接下来我们用相关的组件布局好初始的页面,如下图(无数据时)。
从上到下首先是标题,然后是添加联系人的按钮,这里用到了两个 el-row
来区分这两个模块。
然后是数据列表的部分,列表包括了 id 、姓名、电话以及相关操作,操作包括了编辑功能和删除功能的按钮。
如上图代码截图所示,这里使用到了 el-card
、el-table
等相关组件来完成布局。然后是数据列表的分页,这里用到的是 el-pagination
实现分页的功能,五条数据为一页。
其中 table 中的 data 是 pageData
,我们对其的 id 进行了处理,为了保证其 id 是按照递增的顺序展示出来,而不是每一页都是 1-5 的排序。其中还有一个特点就是,通过后端接口插入的数据显示的 id 是时间戳,因为我们不一定知道最后插入的那条数据的 id 是多少,也没必要去特地看一眼,然后才接着插入数据。因此我们默认插入的 id 是时间戳,然后再对 id 进行处理。
🧩添加功能
相比上一篇文章的 demo 功能,这个项目多了一个添加联系人的功能,我们通过后端写好的 insert
接口来实现添加,我们只需要在使用 axios
获取数据的时候,使用到这个接口,以及传递对应的数据给后端即可实现添加功能,接下来我们看看 Node 代码。
其中红框的部分是一些对添加的数据的判断逻辑,然后就是把新传递的参数写入 JSON
格式数据,最后返回结果信息。接下来我们通过 post
请求以及写好的添加联系人模块来添加一条数据。
在添加联系人窗口输入数据,然后点击确认添加。
然后我们可以在终端看到添加成功的反馈,以及 JSON
数据中出现了刚刚新添加的数据。
然后在后台的联系人列表也可以看到。
通过上面的一系列操作,我们实现了添加联系人的功能,其中添加联系人的窗口这里没用使用弹窗的形式,而是使用卡片嵌套表单的形式。为了使添加联系人成功后会自动刷新列表,方便数据的显示,这里我们要把通过 axios
异步获取数据的方法封装到一个函数,然后挂载到 onMounted
函数,这样就方便在任意地方调用了。
🧩编辑功能
这下来我们来看看编辑功能,通过点击编辑按钮,然后出现弹窗,对数据进行修改和保持,这里使用到了 el-dialog
和 ElMessage
实现窗口的出现、隐藏以及一些交互效果(消息框)。
关于编辑功能,我们通过后端写好的 update
接口来实现编辑功能,我们只需要在使用 axios
获取数据的时候,使用到这个接口,以及传递对应的数据给后端即可实现编辑功能,接下来我们看看 Node 代码。
其中红框的部分是一些对编辑的数据的判断逻辑,然后就是把新传递的参数重新修改代替原本的数据,然后写入 JSON
格式数据,最后返回结果信息。接下来我们通过 post
请求以及写好的编辑模块来编辑刚刚新添加的那一条数据。
找到刚刚新添加的那条数据,然后点击编辑按钮,把姓名测试添加改成测试编辑。
然后我们可以在终端看到 id:xxx 编辑成功的反馈,以及 JSON
数据中更新了刚刚编辑的数据。
通过上面的一系列操作,我们实现了编辑功能,这里的编辑功能模块就是用了弹窗的形式,不同于添加联系人功能模块。
🧩删除功能
最后我们来看看删除功能,通过点击删除按钮,然后出现是否确认删除的弹窗,这里使用到了 ElMessageBox
实现弹窗的出现以及确认、取消的交互效果。
关于删除功能,我们通过后端写好的 delete
接口来实现删除功能,我们只需要在使用 axios
获取数据的时候,使用到这个接口,以及传递联系人的 id 给后端即可实现删除功能,接下来我们看看 Node 代码和 js 代码。
然后找到刚刚编辑的那条数据,然后点击删除按钮,删除这条数据。
然后我们可以在终端看到 id:xxx 删除功的反馈,以及 JSON
数据中删除这条数据。
通过上面的一系列操作,我们实现了删除功能。至此这个后台的功能已经基本全部实现了,上述的功能分析以及代码分析的完整内容还需要到具体的源码中去编写、浏览了才能体验的到了,最后附上完整代码,供大家参考和学习。
🎯完整代码
<template>
<div class="contact-list">
<!-- 标题 -->
<el-row justify="center">
<h1 style="text-align: center">Node.js 联系人列表管理后台</h1>
</el-row>
<!-- 添加联系人按钮 -->
<el-row justify="center">
<el-button type="primary" style="text-align: center" @click="addShowForm">添加联系人</el-button>
</el-row>
<br />
<!-- 添加联系人表单窗口 -->
<el-card class="add-card" v-if="addFormVisible">
<el-row justify="center">
<h1 style="text-align: center">添加</h1>
</el-row>
<el-form :model="formData" label-width="60px" style="text-align: center">
<el-col :sm="{ span: 12, offset: 5 }" :xs="{ span: 24 }">
<el-form-item label="姓名" prop="name">
<el-input v-model="formData.name" placeholder="请输入联系人姓名"></el-input>
</el-form-item>
</el-col>
<el-col :sm="{ span: 12, offset: 5 }">
<el-form-item label="电话" prop="tel">
<el-input v-model="formData.tel" placeholder="请输入联系人电话"></el-input>
</el-form-item>
</el-col>
<el-col>
<el-button type="primary" @click="addContact">确认添加</el-button>
<el-button @click="addFormVisible = false">取 消</el-button>
<el-button @click="refreshFormData()">清空</el-button>
</el-col>
</el-form>
</el-card>
<!-- 原编辑联系人表单窗口 -->
<!-- <el-card class="edit-card" v-if="editFormVisible">
<el-row justify="center">
<h1 style="text-align: center">编辑</h1>
</el-row>
<el-form :model="row" label-width="60px" style="text-align: center">
<el-col :sm="{ span: 12, offset: 5 }" :xs="{ span: 24 }">
<el-form-item label="姓名" prop="name">
<el-input v-model="row"></el-input>
</el-form-item>
</el-col>
<el-col :sm="{ span: 12, offset: 5 }">
<el-form-item label="电话" prop="tel">
<el-input v-model="row"></el-input>
</el-form-item>
</el-col>
<el-col>
<el-button type="primary" @click="editContact">确认修改</el-button>
<el-button @click="editFormVisible = false">取 消</el-button>
</el-col>
</el-form>
</el-card> -->
<!-- 编辑联系人dialog窗口 -->
<el-dialog title="编辑" v-model="editFormVisible" width="30%">
<el-form :model="formData">
<el-form-item label="姓名">
<el-input v-model="formData.name"></el-input>
</el-form-item>
<el-form-item label="电话">
<el-input v-model="formData.tel"></el-input>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="editFormVisible = false">取消</el-button>
<el-button type="primary" @click="editContact()">保存</el-button>
</template>
</el-dialog>
<!-- 联系人数据表格 -->
<el-card class="list-card">
<el-table :data="pagedData" empty-text="暂无联系人" stripe>
<el-table-column prop="virtualId" label="id" width="60" align="center"></el-table-column>
<el-table-column prop="name" label="姓名" align="center"></el-table-column>
<el-table-column prop="tel" label="电话" align="center"></el-table-column>
<el-table-column label="操作" width="150" align="center">
<template #default="{ row }">
<el-button size="small" @click="editShowForm(row)">编辑</el-button>
<el-button type="primary" size="small" @click="delContact(row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<el-row style="margin-top: 20px">
<el-col :span="24">
<el-pagination v-model:current-page="currentPage" :page-size="5" layout="prev, pager, next"
:total="contactList.list.length" @current-change="handleCurrentChange"></el-pagination>
</el-col>
</el-row>
</el-card>
</div>
</template>
<script setup>
// import { ElMessage, ElMessageBox } from "element-plus";
import { ref, reactive, onMounted, computed } from "vue";
import { ElMessage, ElMessageBox } from "element-plus";
import axios from "axios";
// 给json数据定义一个list
const contactList = reactive({
list: [],
});
// 当前页数
const currentPage = ref(1);
// 当前页数变化时的回调函数
const handleCurrentChange = (val) => {
currentPage.value = val;
};
// 分页后的数据
// const pagedData = computed(() => {
// const start = (currentPage.value - 1) * 5;
// const end = start + 5;
// return contactList.list.slice(start, end);
// });
const pagedData = computed(() => {
let start = (currentPage.value - 1) * 5;
const end = start + 5;
const res = contactList.list.slice(start, end);
for (let data of res) {
data.virtualId = ++start;
}
return res;
});
// axios默认数据
const instance = axios.create({
baseURL: "http://127.0.0.1:9999/api/",
timeout: 10000,
});
// 在组件挂载完毕后调用 refreshList 函数
onMounted(() => {
// instance.get("select").then((res) => {
// contactList.list = res.data.data.data;
// console.log(contactList.list);
// });
refreshList();
});
// 封装这个方法,方便在任意地方调用
const refreshList = async () => {
await instance
.get("select")
.then((res) => {
if (res.data.code === 200) {
contactList.list = res.data.data.data;
console.log(contactList.list);
} else {
ElMessage({
message: res.data.message,
grouping: true,
type: "error",
});
}
})
.catch((err) => {
ElMessage({
message: err,
grouping: true,
type: "error",
});
});
};
// 表单的数据
const formData = reactive({
id: "",
name: "",
tel: "",
});
// 清空formData数据
const refreshFormData = () => {
formData.id = "";
formData.name = "";
formData.tel = "";
};
// 添加联系人模块
const addFormVisible = ref(false);
const addShowForm = () => {
addFormVisible.value = true;
};
const addContact = () => {
// console.log(formData);
const params = {
name: formData.name,
tel: formData.tel,
};
instance
.post("insert", params)
.then((res) => {
if (res.data.code === 200) {
ElMessage({
message: "添加成功!",
grouping: true,
type: "success",
});
refreshList();
refreshFormData();
} else {
ElMessage({
message: res.data.message,
grouping: true,
type: "error",
});
}
})
.catch((err) => {
ElMessage({
message: err,
grouping: true,
type: "error",
});
});
};
// 编辑联系人模块
const editFormVisible = ref(false);
const editShowForm = (row) => {
formData.id = row.id;
formData.name = row.name;
formData.tel = row.tel;
editFormVisible.value = true;
};
const editContact = () => {
const params = {
id: formData.id,
name: formData.name,
tel: formData.tel,
};
console.log(params);
// .then(() => {
instance
.post("update", params)
.then((res) => {
if (res.data.code === 200) {
ElMessage({
message: "编辑成功!",
grouping: true,
type: "success",
});
editFormVisible.value = false;
refreshList();
} else {
ElMessage({
message: res.data.message,
grouping: true,
type: "error",
});
}
})
.catch((err) => {
ElMessage({
message: err,
grouping: true,
type: "error",
});
});
// })
// .catch(() => {
// ElMessage({
// type: "info",
// message: "已取消编辑!",
// });
// });
};
// 删除联系人模块
const delContact = (row) => {
console.log(row);
ElMessageBox.confirm(`确定要删除联系人${row.name}吗`, "Warning", {
confirmButtonText: "确认",
cancelButtonText: "取消",
type: "warning",
})
.then(() => {
instance
.delete("delete?id=" + row.id)
.then((res) => {
if (res.data.code === 200) {
ElMessage({
type: "success",
message: "删除成功!",
});
refreshList();
} else {
ElMessage({
message: res.data.message,
grouping: true,
type: "error",
});
}
})
.catch((err) => {
ElMessage({
message: err,
grouping: true,
type: "error",
});
});
})
.catch(() => {
ElMessage({
type: "info",
message: "已取消删除!",
});
});
};
</script>
<style scoped>
.contact-list {
max-width: 800px;
margin: auto;
padding: 20px;
}
.add-card {
margin-bottom: 20px;
}
.list-card {
overflow-x: auto;
}
el-form {
margin: 0 auto;
}
/* el-dialog 遮罩突然变黑问题解决 */
.v-modal {
opacity: 0.5 !important;
background: rgba(0, 0, 0, 0.5) !important;
}
@media (max-width: 768px) {
.add-card {
width: 100%;
box-shadow: none;
border-radius: 0;
}
.list-card {
width: 100%;
box-shadow: none;
border-radius: 0;
}
el-dialog {
--el-dialog-width: 80%;
}
}
@media only screen and (min-width: 768px) {
.el-col-sm-offset-1 {
margin-left: 0;
}
}
</style>
如果需要 Node 后端代码,或者完整项目的,可以私信或者在评论区留言,我会第一时间回复。
📝最后
通过这篇文章的实战进阶学习,我们可以学会一个基于 Vue3 + Node.js + ElementPlus
实现的联系人列表管理后台的 demo,同时对 axios
的使用更上一层楼,文章中的 Node 不是本次项目的重点,这篇文章重点是练习 axios 多方面的用法,因此后续会讲讲、记录一下 axios 进一步封装的操作。