对数据进行增删改查
- 关键点:怎么知道用户点击的是哪个电影,实现数据获取?
- 通过
Element子组件配置的作用域插槽,可以拿到当前行电影数据,即可获取电影ID
<el-table-column fixed="right" label="操作" width="90">
<template #default="{ row: { _id } }">
<el-button type="primary" :icon="Edit" circle size="small" @click="$router.push(`/film/${_id}`)" />
<el-button type="danger" :icon="Delete" circle size="small" />
</template>
</el-table-column>
一、搭建详情页路由
- 路由是用来跳转页面,与获取数据基本无关,最多可拿取跳转地址当中携带的参数
$route.params.id来获取当前详情页的电影数据
//在router/index.js中
{
path: '/film/:id([\\w\\d]{24})',
name: 'detail',
component: () => import('../views/film/Detail.vue')
},
二、搭建“后端数据库”,获取详情页数据
- 配置应用层API(之前我们已经配置好接口地址 http://localhost:8173/api/film/film_id)
//在movieApi.js中
export async function getDetail(id) {
const {arr:[detail]} = await doGet(`/film/${id}`);
// localhost:5173/api/film/0 => vite会将该地址代理到localhost:8173/api/film/0
return detail;
}
三、得到数据之后,对页面进行渲染
//在Detail.vue中,使用onMount()生命周期,回调应用层API且带入电影ID,得到数据
onMounted(async () => {
const detail = await getDetail(route.params.id)
console.log("detail=", detail);
})
四、对数据进行删减
- 关键点还是通过
作用域插槽获得子组件里面的电影数据 - 在删减之前,使用
Element组件,做了小优化,防止用户手滑,增加了一层确认按钮 - 在单个删除的基础上,实现批量删除,首先肯定是需要获得用户选中的电影数据+通过向服务端发起请求删除数据+由于只能单个删除比较麻烦,但是巧妙的运用一下
promise.all()+在我们封装的业务API当中,返回的就是一个promise,我们只需要判断是否全部删除成功,就代表批量删除成功,若有一个失败,则失败
废话不多说,上代码
/* 多选时,能拿到选中的电影数据 */
const deleteArr = ref([])
const handleSelectionChange = (val) => {
console.log("handleSelectionChange", val);
deleteArr.value = val
};
/* 用于缓存电影ID */
const deleteId = ref(0)
/* 点击表格删除按钮 */
const deleteItem = async (id) => {
// 先把将要删除的id暂存起来
deleteId.value = id
// 显示删除对话框
dialogVisible.value = true
}
/* 确认删除 */
const doDeleteItem = async () => {
/* 将对话框隐藏 */
dialogVisible.value = false
/* 输入id,调用服务端API执行删除 */
/* (后端)服务器数据删除 */
const { msg, deletedCount } = await deletePlay(deleteId.value);
/* deletedCount不为0/undefined , 代表删除成功 */
/* 使用message提示信息 */
/* 信息提示框 */
ElMessage({
showClose: true,
message: msg,
type: deletedCount ? 'success' : 'error',
})
/* 删除数据后,进行暴力刷新 性能垃圾*/
// window.location.reload()
/* deletedCount不为0/undefined , 代表删除成功 */
/* 向服务端发起请求,删除数据 */
/* 修改响应式数据,让数据去驱动视图做【差量渲染】 */
if (deletedCount) {
/* 通过发起请求带入的电影id,再通过find,查找整个电影数据对应上删除电影 */
tableData.value.find((item, index) => {
if (item._id === deleteId.value) {
/* 等find正常返回后,再执行删除操作 */
/* 前端数据删除 */
setTimeout(() => {
tableData.value.splice(index, 1)
})
/* 是为先得到用户要删除的数据,定时器异步任务,会挂起,return是为了结束find */
return item
}
})
}
}
/* 点击头部的 批量删除 按钮 切换批量模式*/
const patchDelete = () => {
/* 切换模式 */
dialogModel.value = dialogModels.patchDelete
/* 显示对话款 */
dialogVisible.value = true
}
/* 用户确认 执行批量删除 */
const doPatchDelete = () => {
/* 隐藏对话框 */
dialogVisible.value = false
console.log("doPatchDelete");
/* 全部删除成功,才算成功 */
/* 通过promise.all 判断是否全部删除成功,才算完全成功 */
/* 服务端只配置了单个删除,需要多次请求 */
/* 但是,对话框只用显示一次 then代表全部删除成功 */
Promise.all(
/* 把需要删除的电影数据映射为 向请求服务端删除数据 返回的promise数组 */
deleteArr.value.map((film) => deletePlay(film._id))
)
.then((results) => {
ElMessage({
showClose: true,
message: "批量删除成功",
type: 'success'
})
/* 依然使用数据驱动试图,这里做了简单处理 */
window.location.reload()
})
.catch((err) => {
ElMessage({
showClose: true,
message: err,
type: 'error'
})
})
}
const dialogVisible = ref(false)
/* 用一个对象,装载着会话框的模式 */
const dialogModels = {
deleteItem: {
msg: "确认删除影片吗",
callback: doDeleteItem,
},
patchDelete: {
msg: "确认执行批量删除影片吗",
callback: doPatchDelete,
}
}
/* 默认单个删除模式 具有响应式,【在模板上使用】 */
let dialogModel = ref(dialogModels.deleteItem)
五、对详情页的数据,实时修改+实时更新
- 首先我们要知道,使用
Element组件的表单组件,里面数据都是使用v-model双向绑定的,只要我们修改,视图和数据就能实时匹配更新【注意这里只是前端页面的数据更新】 - 【后端数据更新】需要向服务器发起请求+携带最新数据,有可能我们需要对数据进行相应的还原,还原成后端传给我们的数据格式,看自己的业务逻辑
- 有一点需要我们去注意,修改id对数据库来说是非法操作,再上传数据的时候,自行删除ID
- 我们对数据进行修改时,数据确实已经修改完毕,但!我们需要手动刷新一次才可以看到,以下有两种方法解决
代码出山
//简单暴力,但!性能垃圾
window.location.reload()
//优雅永不过时
/*deletedCount为后端返回删除数据的提示信息*/
/* deletedCount不为0/undefined , 代表删除成功 */
/* 向服务端发起请求,删除数据 */
/* 修改响应式数据,让数据去驱动视图做【差量渲染】 */
if (deletedCount) {
/* 通过发起请求带入的电影id,再通过find,查找整个电影数据对应上删除电影 */
tableData.value.find((item, index) => {
if (item._id === deleteId.value) {
/* 等find正常返回后,再执行删除操作 */
/* 前端数据删除 */
setTimeout(() => {
tableData.value.splice(index, 1)
})
/* 是为先得到用户要删除的数据,定时器异步任务,会挂起,return是为了结束find */
return item
}
})
}
- 使用
ElementPlus组件库里面的符合我们需求的组件 + 二次封装 - 补充使用组件过程中值得学习的地方
- 父子通信当中,通过prop注入一个函数:父组件给子组件传递一个函数,参数由子组件内部的数据来注入,父组件可以对子组件数据进行二次修改,然后再重新把返回值传递给子组件
//父组件 部署TestPropFn子组件
<TestPropFn :fatherFn="onTest"></TestPropFn>
const onTest = (a, b, c) => {
console.log("a", a);
console.log("b", b);
console.log("c", c);
return a + b + c
}
//子组件
<p>sonFn(a, b, c):{{ sonFn(a, b, c) }} ***这是父组件传入返回值渲染的结果</p>
const a = ref(1)
const b = ref(2)
const c = ref(3)
const prop = defineProps({
fatherFn: {
type: Function
}
})
const sonFn = (a, b, c) => {
return prop.fatherFn(a, b, c)
}
- 使用作用域插槽(使得在父组件部署子组件的时候,能够访问到子组件内部的数据,至父组件插槽内)
- 子组件自己暴露数据给父组件,暴露出去的数据,是以一个对象的形式存在,父组件接收时,在起一个别名 newObj.xxx即可
//父组件 #具名插槽名="起一个别名"
<TestPropFn>
<template #one="obj">
这里是用到子组件的数据【通过作用域插槽传递】:
<br>
{{ obj.a1 }},
{{ obj.b2 }},
{{ obj.c3 }},
</template>
</TestPropFn>
//子组件 通过name属性设置具名插槽
<slot name="one" a1="a1Value" b2="b2Value" c3="c3Value">
one作用域插槽的内容
</slot>
-Element当中的v-loading指令
- 这个loading就只是负责,给页面加上loading效果,不具备判断能力,我们需要在DOM结构当中根据业务逻辑自行加上
<el-card class="box-card" v-loading="loading">
<el-form :model="form" label-width="120px" v-if="form['name']">
...
...
</el-form>
</el-card>
onMounted(async () => {
/* 刚开始加载页面启动 v-loading,设置loading = true */
loading.value = true
const detail = await getDetail(route.params.id)
console.log("detail=", detail);
/* 待得到数据,取消v-loading,设置loading = false*/
setTimeout(() => {
loading.value = false
}, 300)
})
- Object.assign()方法的使用【深拷贝】
Object.assign()用于将所有可枚举属性的值从一个或多个源对象复制到目标对象。它将返回目标对象;当后面对象和前面对象,有相同属性名时,后者会覆盖来前者- detail对象里面的数据会一一复制一份给form,如果后面想对先前数据进行修改,配置同名key,再次覆盖即可
Object.assign(form, detail, {
premiereAt: detail.premiereAt * 1000, category: detail.category.split("|"), poster: [{ name: `poster`, url: detail.poster }], actors: detail.actors.map(({ name, role, avatarAddress }) =>
({
name: `${name}-${role}`,
url: avatarAddress
})
)
})