# 生命周期

本文档介绍 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 为当前行数据,resuseDetail 接口返回的数据
  • 返回值
    • 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 进行配置和操作。