# 生命周期
本文档介绍 CRUD 的常用逻辑,涵盖生命周期方法、事件处理和自定义事件,方便开发者进行灵活的数据处理和交互控制。
# 事件概览
| 方法名 | 说明 |
|---|---|
init | 初始化组件,进行基础设置或数据加载。 |
listBefore | 查询操作前触发,用于设置查询参数或执行前置逻辑。 |
listAfter | 查询操作后触发,用于处理查询结果。 |
addOpen | 打开新增表单时触发。 |
addBefore | 新增保存前触发。 |
addAfter | 新增保存后触发。 |
addError | 新增失败后触发。 |
addRewrite | 新增重写。 |
editOpenBefore | 编辑表单打开前触发。 |
editOpenAfter | 编辑表单打开后触发。 |
editBefore | 编辑保存前触发。 |
editAfter | 编辑保存后触发。 |
editError | 编辑失败后触发。 |
editRewrite | 编辑重写。 |
rowEditBefore | 行内编辑打开前触发。 |
rowEditAfter | 行内编辑保存后触发。 |
rowEditRewrite | 行内编辑重写。 |
viewOpenBefore | 查看弹窗打开前触发。 |
viewOpenAfter | 查看弹窗打开后触发。 |
viewOpenRewrite | 查看弹窗打开重写。 |
deleteBefore | 删除请求提交前触发。 |
deleteAfter | 删除请求提交后触发。 |
deleteRewrite | 删除请求提交重写。 |
deleteBatchBefore | 批量删除请求提交前触发。 |
deleteBatchAfter | 批量删除请求提交后触发。 |
deleteBatchRewrite | 批量删除请求提交重写。 |
importBefore | 导入请求提交前触发。 |
importAfter | 导入请求提交后触发。 |
importRewrite | 导入请求提交重写。 |
exportBefore | 导出请求提交前触发。 |
exportAfter | 导出请求提交后触发。 |
exportRewrite | 导出请求提交重写。 |
open | 打开弹窗。 |
close | 关闭弹窗。 |
# 初始化
- 作用:组件在加载完
config配置后触发,支持异步函数 - 示例:
handle = {
init() {
// 通常会在这里进行一些初始化操作
// 比如: 异步获取 options 数据
// 或者: 设置默认表单数据、搜索表单数据等
setOptions();
},
// 异步初始化
async init() {
// 比如: 异步获取数据
return getAPI();
// 或者: 其他异步情况
// ...
}
};
# 列表
# listBefore
- 作用:查询操作前触发,可用于设置查询参数或执行前置逻辑。
- 参数:
searchForm<object>:查询参数
- 返回值:
searchForm<object>:修改后的查询参数
- 示例:
handle = {
listBefore(searchForm) {
searchForm.is_all = true; // 修改查询列表的参数
return searchForm; // 最终需要返回,修改后的查询参数
}
};
# listAfter
- 作用:查询操作后触发,用于处理查询结果。
- 通常后端编写习惯、规范不同,返回值各不相同
- 所以这里更多的使用情况是处理返回值
- 参数:
res<object>:接口返回数据
- 返回值:
total<number>:总条数data<array>:列表数据
- 示例:
handle = {
listAfter({ data: { total, data } }) {
// 对 res 进行解构,获得 res.data.total 和 res.data.data
return { total, data };
// 如果后端返回 { res: { code: 200, data: { total: 10, list: Array(10) } } }
// 则可以这样处理
return { total: res.data.total, data: res.data.list };
},
// 也可以用来处理列表数据的格式化
listAfter({ data: dataList }) {
// 比如: 将列表数据格式化为树形结构
const list = formatList(dataList);
// 比如: 将转化的数据,当作 config 中对应的 options 数据
data.tree = [{ label: "顶级菜单", value: 0, children: list, id: 0 }];
config.find("parent_id").setTree(data.tree);
// 最终的固定返回值,接受 total 和 data 两个参数
return { total: 0, data: list }; // 当设置 total 为 0 时,不会触发分页
}
};
# 新增
# addOpen
- 作用:打开新增表单时触发。
- 参数:
object<{ row<object> }>:菜单按钮触发时为null, 行内新增触发时为当前行数据
- 示例:
handle.addOpen = ({ row }) => {
// 比如: 打开新增窗口时,设置默认的表单数据
data.form.menu_type = config.default.menu_type;
};
# addBefore
- 作用:提交表单前触发。
- 参数:
form<object>:表单数据
- 示例:
handle = {
addBefore(form) {
// 比如: 提交前,补充一些表单数据
form.user_name = "admin"; // 不需要返回,直接对 form 源修改
}
};
# addAfter
- 作用:提交表单后触发。
- 默认提交表单后,会自动执行 getList 方法,并提示 “新增成功”
- 如果需要自定义操作,可以定义 addAfter 方法,但是不会执行默认操作
- 参数:
object<{ getList<function> }>:列表数据方法
- 示例:
handle = {
addAfter({ getList }) {
getList();
}
};
# addError
- 作用:提交表单失败后触发。
- 通常情况下,不需要生命 addError 方法,因为框架中已经默认处理了提交失败后的逻辑
- 比如: 提交失败后,会提示 “新增失败”
- 示例:
handle = {
addError() {
// 如果有特殊情况,可以 addError 方法中做一些定制化处理
}
};
# addRewrite
- 作用:新增重写。用于覆盖提交表单的逻辑。
- 适用于保留按钮、以及自动打开弹窗等一些默认场景,但是需要修改表单提交的情况。
- 如果
addRewrite不满足定制化要求,可以在menu中进行完全的定制化。
- 参数:
hideLoading<function>:隐藏 loading 的方法getList<function>:列表数据方法dialog<function>:弹窗方法,接受一个参数:true打开 或false关闭
- 示例:
handle = {
async addRewrite({ hideLoading, getList, dialog }) {
// 定制逻辑
doSomething();
await new doSomeAPI();
proxy.$popup.success("操作成功");
hideLoading(); // 隐藏 loading
getList(); // 更新列表数据
dialog(false); // 关闭弹窗
}
};
# 编辑
# editOpenBefore
- 作用:编辑弹窗打开前触发,可以为异步操作。
- 通常
row的数据就满足需求,如果设置了useDetail,还会对 res 和 row 自动进行合并,足够应付大多数情况。 - 但是有些情况,在使用了详情接口时,后端返回的数据格式不可控,比如:
- 正常的数据
{ name: '张三', ... } - 后端返回了
{ userInfo: { name: '张三', ... } } - 这就需要进行人为处理了,而且
editOpenBefore本就是灵活性的体现,也方便处理其他情况。
- 正常的数据
- 通常
- 参数:
object<{ row<object>, res<object> }>:row为当前行数据,res为useDetail接口返回的数据
- 返回值:
object:处理完毕的数据
- 示例:
handle = {
editOpenBefore({ row, res }) {
return res;
// or
return { ...row, ...res.userInfo };
},
// 异步情况
async editOpenBefore({ row, res }) {
const res = await getUserInfo();
return { ...row, ...res.userInfo };
}
};
# editOpenAfter
- 作用:编辑弹窗打开后触发,增加灵活性。
- 示例:
handle = {
editOpenAfter() {
// 编辑弹窗打开后,可以做一些自定义操作
// doSomething();
}
};
# editOpenRewrite
- 作用:编辑弹窗打开的生命周期,不满足时,可以对逻辑重写
- 参数:
row<object>:当前行数据res<object>:接口返回数据, 需要设置useDetail时,才会返回dialog<function(boolean)>:弹窗方法,接受一个参数:true打开 或false关闭
- 示例:
handle = {
editRewrite({ row, res, dialog }) {
// 定制逻辑
doSomething();
await new doSomeAPI();
// 打开弹窗
dialog(true);
}
};
# editBefore
- 作用:编辑保存前触发。
- 参数:
form<object>:表单数据
- 示例:
handle = {
editBefore(form) {
// 比如: 提交前,补充一些表单数据
form.user_name = "admin"; // 不需要返回,直接对 form 源修改
}
};
# editAfter
- 作用:编辑保存后触发。
- 参数:
object<{ getList<function> }>:列表数据方法
- 示例:
handle = {
editAfter({ getList }) {
getList();
}
};
# editError
- 作用:编辑失败后触发。
- 示例:
handle = {
editError() {
// 如果有特殊情况,可以 editError 方法中做一些定制化处理
}
};
# editRewrite
- 作用:编辑重写。用于覆盖提交表单的逻辑。
- 示例:
handle = {
async editRewrite({ hideLoading, getList, dialog }) {
// 定制逻辑
doSomething();
await new doSomeAPI();
hideLoading(); // 隐藏 loading
getList(); // 更新列表数据
dialog(false); // 关闭弹窗
}
};
# 行内编辑
# rowEditBefore
- 作用:行内编辑打开前触发。
- 示例:
handle = {
rowEditBefore() {
// 定制逻辑
}
};
# rowEditAfter
- 作用:行内编辑保存后触发,getList 通常不需要执行,最终保存是直接作用于 row 上的。
- 参数:
getList<function>:列表数据方法hideLoading<function>:隐藏 loading 的方法
- 示例:
handle = {
rowEditAfter({ getList, hideLoading }) {
// 定制逻辑
}
};
# rowEditRewrite
- 作用:行内编辑重写。用于覆盖提交表单的逻辑。
- 参数:
row<object>:当前行数据getList<function>:列表数据方法hideLoading<function>:隐藏 loading 的方法
- 示例:
handle = {
rowEditRewrite({ row, getList, hideLoading }) {
// 定制逻辑
}
};
# 查看
# viewOpenBefore
- 作用:查看弹窗打开前触发,与
editOpenBefore类似,支持异步。 - 示例:
handle = {
viewOpenBefore({ row, res }) {
return res;
// or
return { ...row, ...res.userInfo };
}
};
# viewOpenAfter
- 作用:查看弹窗打开后触发。
- 示例:
handle = {
viewOpenAfter() {
// 定制逻辑
}
};
# viewOpenRewrite
- 作用:查看弹窗打开的生命周期,不满足时,可以对逻辑重写
- 参数:
row<object>:当前行数据res<object>:接口返回数据, 需要设置useDetail时,才会返回dialog<function(boolean)>:弹窗方法,接受一个参数:true打开 或false关闭
- 示例:
handle = {
viewOpenRewrite({ row, dialog }) {
// 定制逻辑
doSomething();
await new doSomeAPI();
// 打开弹窗
dialog(true);
}
};
# 删除
# deleteBefore
- 作用:请求提交前触发。
- 参数:
object<{ row<object> }>:当前行数据
- 示例:
handle = {
deleteBefore({ row }) {
// 定制逻辑
}
};
# deleteAfter
- 作用:请求提交后触发。
- 默认会调用
getList方法,并提示 “删除成功” - 覆写
deleteAfter方法,则需要自行处理相关逻辑
- 默认会调用
- 参数:
object<{ getList<function> }>:列表数据方法
- 示例:
handle = {
deleteAfter({ getList }) {
// 定制逻辑
}
};
# deleteRewrite
- 作用:请求提交重写。
- 默认情况下,会弹出确认窗口,确认是否删除
- 覆写
deleteRewrite方法,则需要自行处理相关逻辑
- 参数:
row<object>:当前行数据getList<function>:列表数据方法
- 示例:
handle = {
deleteRewrite({ row, getList }) {
// 定制逻辑
}
};
# 批量删除
# deleteBatchBefore
- 作用:批量删除请求提交前触发,交互形式与
deleteBefore类似。 - 参数:
object<{ list<array> }>:当前选中的行数据(主键、id 等)
- 示例:
handle = {
deleteBatchBefore({ list }) {
// 定制逻辑
}
};
# deleteBatchAfter
- 作用:批量删除请求提交后触发,交互形式与
deleteAfter类似。 - 参数:
object<{ getList<function> }>:列表数据方法
- 示例:
handle = {
deleteBatchAfter({ getList }) {
// 定制逻辑
}
};
# deleteBatchRewrite
- 作用:批量删除请求提交重写,交互形式与
deleteRewrite类似。 - 参数:
list<array>:当前选中的行数据(主键、id 等)getList<function>:列表数据方法
- 示例:
handle = {
deleteBatchRewrite({ list, getList }) {
// 定制逻辑
}
};
# 导入
导入导出的逻辑,尚未完全对接。主要是跟后端耦合度高,需要后端确定具体逻辑后。才能继续对接,封装
当前阶段有基础代码,导入前可选择导入模板,或者直接导入
- 导入模版下载后,填写数据导入
// 预留 3件套
handle = {
importBefore() {},
importAfter() {},
importRewrite() {}
};
# 导出
导入导出的逻辑,尚未完全对接。主要是跟后端耦合度高,需要后端确定具体逻辑后。才能继续对接,封装
- 当前前端有临时封装的导出
- 请求
page: 1, pageSize: 9999999,然后使用插件导出excel
- 请求
- 具体后续要不要更改,以后端为主
// 预留 3件套
handle = {
exportBefore() {},
exportAfter() {},
exportRewrite() {}
};
# 打开关闭
弹窗打开关闭,所有表单弹窗类型都会触发。比如新增、编辑、删除、查看,都会触发方法。
- 打开 -
open - 关闭 -
close
handle = {
open() {
// 所有 crud 中的 dialog 打开,都会触发回调
},
close() {
// 所有 crud 中的 dialog 关闭,都会触发回调
}
};
# 自定义数据
| 事件名 | 说明 |
|---|---|
v-model | 对应表单数据。 |
searchForm | 对应搜索表单数据。 |
selection | 对应多选数据。 |
@dynamicOptions | 对应动态搜索栏中 options 数据,后端返回的搜索栏,会带 options 一起返回,但是不经常使用。前端通常使用的单独接口获取 options 数据。数据会以 { propKey: 'options' } 的形式返回。比如 { role_id: [{label: '管理员', value: 1}] } |
# 简单示例
<template>
<crud
v-model="data.form"
v-model:searchForm="data.searchForm"
v-model:selection="data.selection"
@dynamicOptions="dynamicOptions"
:config="config"
:api="api"
:handle="handle"
>
<template #form-permissions>
<a-tree />
</template>
</crud>
</template>
<script setup>
// 插件
import api from "@/api/system/user";
import main from "./config.main";
const { proxy } = getCurrentInstance();
// 数据
const data = reactive({
form: {},
roles: []
});
// 配置
const config = proxy.$createConfig(main());
// 处理
const handle = {
init() {
setOptions();
},
listAfter({ data: { total, data } }) {
return { total, data };
},
addOpen() {
data.form.password = "123456"; // 默认密码
config.updateItems({
password: { hideForm: false },
password_edit: { hideForm: true }
});
},
addBefore(form) {
formatRoles(form); // 将角色 ids 转换为 JSON 字符串
},
editOpenBefore({ row, res: { data } }) {
config.updateItems({
password: { hideForm: true },
password_edit: { hideForm: false }
});
return { ...row, roles: data.roles };
},
editBefore(form) {
formatRoles(form); // 将角色 ids 转换为 JSON 字符串
}
};
// 字典
async function setOptions() {
const { role } = await proxy.$dict(["role"]);
config.find("roles").setOptions(role);
data.roles = role;
}
// 动态选项
// - 动态搜索栏会返回对应的 options 数据
// - 但是因为 “动态” 的缘故,不确定什么时候会取消
// - 所以表单的 options 使用字典的 options
// function dynamicOptions({ role_id }) {
// config.find('roles').setOptions(role_id)
// }
// 格式化角色 ID 为 JSON 字符串
function formatRoles(form) {
form.roles = JSON.stringify(form.roles);
// 编辑窗口时,如果设置了密码,则将密码设置为新密码
if (form.password_edit) form.password = form.password_edit;
}
</script>
<style scoped></style>
注意事项
- 生命周期方法应根据业务需求合理使用,确保数据的正确性和流程的完整性。
- 自定义事件
emit需确保正确触发,以保证父组件能够正确接收并处理。
以上为组件的详细说明,建议按照业务需求合理使用相关 API 进行配置和操作。