npm package: @holyhigh/crud-vue
Description: A CRUD Container in Vue
Author: holyhigh
Latest version: 1.2.2

> # !!! 该库已迁移至 [Cruda](https://github.com/holyhigh2/cruda)，本仓库不再维护 !!!

# ~~CRUD-vue~~

&emsp;&emsp;基于 Vue 的通用 CRUD 视图模型库；提供对常用 CRUD 场景的视图与操作托管，通过不同适配器可以实现对主流 UI 库的自动适配。

## 概念图

![CRUD-vue](./assets/crud-vue.png)

## 介绍

&emsp;&emsp;在所有存在列表展示的业务系统中，无论使用何种 UI 库都存在大量 CRUD 场景。CRUD-vue 为使用者抽象通用行为并内建数据模型统一调度相关操作。通过标准 REST 接口对接服务端，并通过适配器对接不同 UI 库，实现低成本、配置化的界面托管。

## 基本特性

- 数据托管

```html
<!-- $crud.query 托管搜索数据 -->
<el-input v-model="$crud.query.xxx">
  <!-- $crud.table.data 托管表格数据 -->
  <el-table v-model="$crud.table.data">
    <!-- $crud.form 托管表单数据 -->
    <el-form :model="$crud.form">
      <!-- $crud.table.data 托管tree(表单数据) -->
      <el-tree :model="$crud.table.data"> </el-tree></el-form></el-table
></el-input>
```

- 操作托管

```js
$crud.submit(formEl) //提交表单
$crud.reload() //刷新列表数据
$crud.cancel() //设置表单状态为取消
```

- 状态托管

```html
<!-- 下面的例子实现了一个点击查询时锁定按钮的场景 -->
<button class="..." :loading="$crud.loading.query" @click="$crud.reload()">
  查询
</button>
```

- 过程封装

```js
/*调用查询方法时，系统会执行
1. 设置表单与查询按钮的loading状态为true
2. 自动封装查询参数(query、分页、排序、...)
3. 触发钩子
4. 异常捕获
5. ...
*/
$crud.toQuery()
```

- RESTful HTTP Method

```js
$crud.toQuery() //GET
$crud.toDelete() //DELETE
$crud.doUpdate() //PUT
```

## 安装

&emsp;&emsp;从 NPM 安装

```
npm i @holyhigh/crud-vue
```

&emsp;&emsp;然后选择对应 ui 库并安装适配器

## 如何使用

### 1. 安装适配器

- **element-ui**

```js
// 安装CRUD
import request from 'axios'
import CRUD from '@holyhigh/crud-vue/element-ui'
//通常request总是会使用封装后的axios实例
Vue.use(CRUD, { request: request })
```

- **element-plus**

```js
// 安装CRUD
import request from 'axios'
import CRUD from '@holyhigh/crud-vue/element-plus'
//通常request总是会使用封装后的axios实例
createApp(App).use(CRUD, { request: request })
```

### 2. 激活

- **element-ui**

```js
//在vue对象的根属性中标记crud/cruds(多实例)属性
export default {
  crud: 'auth/users',
  // crud: {url:'auth/users'} 对象方式
  mounted() {
    // 激活后业务组件会被注入一个名为“$crud”的CRUD实例
    this.$crud.reload()
    // 以及一个入口标识。标识可以用于区分入口或自定义组件
    this.$isCrudEntry
  }
  ...
}
```

通过对象方式激活 CRUD 时，可以传递除 url 外的其他自定义参数。比如

```js
export default {
  crud: {url:'auth/users',permission:'a_b_c'}
  ...
}
```

之后可通过 VM 属性`params`(read only)来获取激活参数

```js
this.$crud.params.permission
```

params 参数在创建自定义 CRUD 组件时非常有用，比如通过 permission 参数可以实现组件自动权限管理，控制组件视图展示

> **v1.1+**
>
> ```js
> //element-ui 也可以使用useCrud动态创建crud实例
> const $crud = useCrud(vm, restURLMap)
> ```

- **element-plus**

```html
<script lang="ts" setup>
  import { useCrud } from '@holyhigh/crud-vue/element-plus'
  //通过useCrud函数获取$crud实例
  //对象方式激活时与element-ui行为一致
  const $crud = useCrud('/api/single')

  //自动加载
  onMounted(() => {
    $crud.reload()
  })
</script>
```

### 3. 多实例

- **element-ui**

```js
//在一个页面上需要同时处理多CRUD业务时可以通过修改激活方式来处理
export default {
  cruds: {//注意，激活标识与单实例不同，多了一个 "s"
    user: '/api/users',// user: {url:'/api/users'} 对象方式
    log: '/api/logs'
  },
  mounted() {
    // 多实例激活时注入变量会变成“$cruds”,且必须通过明确的实例标识来调用API
    this.$cruds.user.reload()
  }
  ...
}
```

> **v1.1+**
>
> ```js
> //element-ui 也可以使用userCruds动态创建cruds实例
> const $cruds = useCruds(vm, restURL)
> ```

- **element-plus**

```html
<script lang="ts" setup>
  import { useCruds } from '@holyhigh/crud-vue/element-plus'
  //通过useCruds函数获取$cruds实例
  const $cruds = useCruds({
    t1: '/api/single',
    t2: {
      url: '/api/multiple',
    },
  })

  //自动加载t1
  onMounted(() => {
    $cruds.t1.reload()
  })
</script>
```

### 4. HOOK

- **element-ui**

```js
//CRUD提供多种回调钩子以满足不同的业务场景
import CRUD from '@holyhigh/crud-vue'

export default {
  crud: '/api/users',
  methods:{
    [CRUD.HOOK.AFTER_QUERY](crud, rs) {
	  //根据查询结果设置分页值与表格数据
      crud.pagination.total = rs.data.total
      crud.table.data = rs.data.records || []
    }
  }
  ...
}
```

- **element-plus**

```html
<script lang="ts" setup>
  import CRUD, { onHook } from '@holyhigh/crud-vue/element-plus'

  const $crud = useCrud()

  //使用onHook函数进行钩子注册
  onHook(CRUD.HOOK.AFTER_SUBMIT, (crud) => {
    ElNotification.success('提交成功')
    crud.toQuery()
  })
</script>
```

### 5. 自定义组件

- **element-ui**  
  &emsp;&emsp;自定义组件的核心逻辑是封装 crud 页面的数据及操作，比如 crudTable，而前提就是获取$crud实例。通过`lookUpCrud`方法可以拿到页面入口的$crud 实例。下面以一个查询框为例展示自定义组件需要关注的几个方面

```html
<!--
   通过$crud.view控制视图显示/隐藏
   通过$crud.loading控制按钮/视图的锁定
   通过$crud.query/table/form/pagination/...实现视图映射
   通过内置API实现UI交互
-->
<template>
  <div v-show="$crud.view.queryShow" class="...">
    <slot />
    <el-button
      class="..."
      :loading="$crud.loading.query"
      @click="$crud.reload()"
      >查询</el-button
    >
    <el-button v-if="$crud.view.queryReset" class="..." @click="reset()"
      >重置</el-button
    >
  </div>
</template>
<script>
  import CRUD, { lookUpCrud } from '@holyhigh/crud-vue/element-ui'
  import { each } from '@holyhigh/func.js/collection'

  export default {
    beforeCreate() {
      //通过lookUpCrud方法获取组件所在crud入口页面的$crud实例
      //如果启用了多实例，必须指定第二个参数`crudName`
      this.$crud = lookUpCrud(this)
    },
    methods: {
      reset() {
        each(this.$crud.query, (v, k) => {
          this.$crud.query[k] = this.$crud.defaults.query[k]
          this.$crud.reload()
        })
      },
    },
  }
</script>
```

- **element-plus**

```html
<template>
  <el-table :data="$crud.table.data" @sort-change="(o) => $crud.changeSort(o)">
    <el-table-column prop="uname" label="uname" width="180" />
    <el-table-column prop="email" label="email" width="180" sortable="custom" />
    <el-table-column prop="ip" label="ip" />
    <el-table-column align="right">
      <template #default="{ row }">
        <el-button size="small" @click="$crud.toEdit(row)">Edit</el-button>
        <el-button size="small" type="danger" @click="toDelete(row)"
          >Delete</el-button
        >
      </template>
    </el-table-column>
  </el-table>
</template>
<script lang="ts" setup>
  import { lookUpCrud } from '@holyhigh/crud-vue/element-plus'
  //element-plus的lookUpCrud函数只有一个可选入参`crudName`
  const $crud = lookUpCrud()

  //自定义组件封装删除逻辑
  function toDelete(row: Record<string, any>) {
    ElMessageBox.confirm('确认删除?').then(() => {
      $crud.toDelete(row)
    })
  }
</script>
```

### 6. URL 参数

- **element-ui**  
  &emsp;&emsp;CRUD 激活时，REST 地址支持 URL 参数来动态构建。通过 **`:varName`** 来声明变量，如下例

```js
//user实例的地址使用了orgId参数
export default {
  cruds: {
    org: '/api/orgs',
    user: '/api/orgs/:orgId/users'
  },
  ...
  methods:{
    //切换org时调用该方法
    setOrg(orgId){
      this.$cruds.user.setURLParams({orgId})
      this.$cruds.user.toQuery()
    }
  }
}
```

如你所见，动态 URL 最典型的使用场景就是关联业务(_当然，非动态 URL 也可实现相同功能_)。通过`setURLParams`方法可以动态修改请求地址，之后进行 C/R/U/D 操作

- **element-plus**  
  逻辑与 element-ui 相同

### 7. defaults

可以为 CRUD 指定全局 defaults 属性

```js
// 如果项目返回值统一格式就可以设置全局HOOK实现查询结果赋值
CRUD.defaults[CRUD.HOOK.AFTER_QUERY] = function (crud, rs) {
  crud.pagination.total = rs.data.total
  crud.table.data = rs.data.records || []
}
// 可以设置默认的全局分页数
CRUD.defaults.pagination.pageSize = 10
// 可以设置默认的按钮显示状态
CRUD.defaults.view.queryReset = true
// 可以设置默认的列表数据主键key
CRUD.defaults.table.rowKey = 'id'
```

支持默认值设置的属性可在 VM 中查看

### 8. RESTAPI

RESTAPI 用来构建资源请求地址。默认 C/R/U/D 的资源标识符都为空，如果服务器有不同模式可以修改 API 地址。

```js
CRUD.RESTAPI = {
  QUERY: '', //R
  EXPORT: '/export',
  ADD: '', //C
  UPDATE: '', //U
  DELETE: '', //D
  IMPORT: '/import',
  SORT: '/sort'
}
```

**注意**，C/R/U/D 对应的 HTTP Method 不会变更

## VM

- view ✅
  > 业务组件通过 view 来控制 UI
  >
  > - queryShow 查询框显示开关
  > - queryReset 查询框重置按钮显示开关
  > - opAdd 新增按钮显示开关
  > - opEdit 编辑按钮显示开关
  > - opDel 删除按钮显示开关
  > - opExport 导出按钮显示开关
  > - opSort 排序按钮显示开关
- loading
  > 通过 loading 控制锁定状态
  >
  > - query 查询按钮锁定开关
  > - table 表格锁定开关
  > - del 删除按钮锁定开关
  > - export 导出按钮锁定开关
  > - submit 提交按钮锁定开关
  > - form 表单加载锁定开关
  > - sort 排序加载锁定开关
- query
  > 托管查询条件的容器
- sortation
  > 托管排序结果的容器
- table
  > 表格容器托管当前 crud 实例的列表/tree 数据及显示状态
  >
  > - data 表格数据
  > - selection 当前选中行
  > - allColumns 表格所有列，用于动态展示
  > - orders 排序列表，会传递给 GET 请求
  > - rowKey✅ 表格行的 id key，默认为'id'。通常由适配器自动设置
- pagination
  > 分页容器托管当前 crud 实例的列表分页状态
  >
  > - pageSize✅ 每页记录数
  > - currentPage 当前页号
  > - total 总记录数
- form
  > 表单容器托管当前 crud 实例的表单数据
- formStatus
  > 表单当前状态 0：默认；1：新增；2：编辑；3：查看
- vm
  > crud 的入口 vue 实例
- params
  > crud 激活参数，通过对象方式构造 crud 时可以注入。可用于自定义组件中进行附加操作，比如附加 CRUD 权限控制
- error
  > crud 错误信息{name，message，status}。可以用于监控并作出合适的反馈，比如网络超时提示

✅ **_表示支持全局默认值_**

## APIs

- toQuery() : Promise
  > 启动 crud 实例的查询。向指定 REST 地址发送 GET 请求
- toDelete(rows) : Promise
  > 启动 crud 实例的删除。向指定 REST 地址发送 DELETE 请求
- toExport() : Promise
  > 启动 crud 实例的导出。向指定 REST 地址发送 GET 请求
- toImport(file/s) : Promise
  > 启动 crud 实例的导入。向指定 REST 地址发送 POST 请求
- toAdd(...args)
  > 设置 form 状态为新增。
- toEdit(row) : Promise
  > 设置 form 状态为编辑。向指定 REST 地址发送 GET 请求
- toView(row) : Promise
  > 设置 form 状态为查看。向指定 REST 地址发送 GET 请求
- toSort()
  > 触发
- submitSort() : Promise
  > 启动 crud 实例的排序。向指定 REST 地址发送 PUT 请求
- cancel()
  > 设置 form 状态为取消。
- submit(formEl) : Promise
  > 会调用 formEl 的 validate 方法，并在成功后执行 doAdd(POST)/doEdit(PUT)操作。对于验证错误，catch 中会返回 invalidFields
- submitSort() : Promise
  > 提交排序结果
- reload() : Promise
  > 重置分页信息并执行一次 toQuery()
- getRestURL()
  > 获取 crud 实例的服务地址。通常用于 crud 内部
- setURLParams(paramObj)
  > 设置服务地址中的参数表
- getDetails(rowId)
  > 获取行信息。通常用于 crud 内部
- changeSelection(selection: Record<string, any>[])
  > 用在 table 的 selection-change 事件中，记录 table 当前已选记录
- changeOrder(orderData: {
  column: Record<string, any>
  prop: string
  order: string | null
  })
  > 用在 table 的 sort-change 事件，会自动管理排序信息并触发查询

## HOOKs

- BEFORE_QUERY(crud,params,orders,cancel)
  > 查询前回调，可以修改请求参数(params)，比如分页名称等，可取消。取消后不会触发 AFTER_QUERY  
  > **注意** ,params 为提交接口的实际对象（包含 query、pagination)，此处修改 crud.query/pagination 的内容不会提交到接口
- AFTER_QUERY(crud,rs)
  > 查询后回调，可以获取查询结果，设置表格
- BEFORE_DELETE(crud,rows,cancel)
  > 删除前调用，可取消。取消后不会触发 AFTER_DELETE
- AFTER_DELETE(crud,rs,rows)
  > 删除后调用
- BEFORE_ADD(crud,...args)
  > 新增前调用，可以用来清空表单或产生 uuid 等
- BEFORE_EDIT_QUERY(crud,params,cancel,row)
  > 发出编辑查询前调用，可取消。取消后不会触发 BEFORE_EDIT。如需实现row数据回显，可通过cancel取消远程请求并通过row填充crud.form
- BEFORE_EDIT(crud,rs,row)
  > 编辑前调用，可以用来锁定某些字段
- BEFORE_VIEW_QUERY(crud,params,cancel,row)
  > 发出查看查询前调用，可取消。取消后不会触发 BEFORE_VIEW。如需实现row数据回显，可通过cancel取消远程请求并通过row填充crud.form
- BEFORE_VIEW(crud,rs,row)
  > 查看查询结果返回后调用
- BEFORE_SUBMIT(crud,cancel)
  > 提交前调用，可对 form 进行最后加工，可取消。取消后不会触发 AFTER_SUBMIT
- AFTER_SUBMIT(crud,rs)
  > 提交后调用，可以用来刷新页面、发送通知或其他操作
- BEFORE_EXPORT(crud,params,orders,cancel)
  > 导出前调用，同 BEFORE_QUERY，可取消。取消后不会触发 AFTER_EXPORT
- AFTER_EXPORT(crud,rs)
  > 获取导出数据后调用
- ON_ERROR(crud,error)
  > 操作发生错误时调用
- ON_CANCEL(crud)
  > 表单取消编辑时触发（调用 cancel 后）
- BEFORE_IMPORT(crud,params,cancel)
  > 导入文件上传前调用，可在 params 中添加额外参数，可取消。取消后不会触发 AFTER_IMPORT
- AFTER_IMPORT(crud,rs)
  > 导入上传完成后调用
- BEFORE_SORT(crud,...args)
  > 排序前调用，可用来打开排序框或启动自动排序处理
- BEFORE_SUBMIT_SORT(crud,sortation,cancel)
  > 提交排序前调用，可对 排序内容 进行最后加工，可取消。取消后不会触发 AFTER_SUBMIT_SORT
- AFTER_SUBMIT_SORT(crud,rs)
  > 提交排序后调用，可以用来刷新页面、发送通知或其他操作

## 可导出

```js
import CRUD,{...} from '@holyhigh/crud-vue/element-ui'
import CRUD,{...} from '@holyhigh/crud-vue/element-plus'
```

- CRUD crud 命名空间，可设置全局默认值、调用钩子等

  **element-ui**

  - useCrud(vm,restURL) : CRUD
    > 创建一个 crud 单实例入口并绑定指定的 vm(vue 组件)
  - useCruds(vm,restURLMap) : Record<string, CRUD>
    > 创建一个 crud 多实例入口并绑定指定的 vm(vue 组件)
  - lookUpCrud(vm,crudName?) : CRUD | null
    > 向上查找最近的 crud 实例

  **element-plus**

  - useCrud(restURL) : CRUD
    > 创建一个 crud 单实例入口并返回
  - useCruds(restURLMap) : Record<string, CRUD>
    > 创建一个 crud 多实例入口并返回
  - lookUpCrud(crudName?) : CRUD | null
    > 向上查找最近的 crud 实例
  - onHook(hookName,hook) : void
    > 用于注册钩子

## 错误信息

- Must specify 'crudName' when multiple instances detected
  > 多实例时调用 lookUpCrud 方法未指定 crud 标识。解决方法见【6. 自定义组件】
- form validation Xxx
  > 表单提交时验证错误信息
- Cannot find [request] in the installation options
  > 安装时未指定请求器。解决方法见【1. 安装】
- table.rowKey is a blank value 'Xxx', it may cause an error - toDelete/Edit/View()
  > 进行删除/编辑/查看操作时未指定 table.rowKey。可以设置默认/$crud 实例的对应属性