Skip to content

Admin CRUD frontend meta framework for Vue 2/3, extremely concise without losing flexibility.

License

Notifications You must be signed in to change notification settings

cloydlau/admate

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

中后台 CRUD 前端元框架,极致简洁的基础上不向灵活性妥协,
致力于攻克「页面重复度高,提取公共代码却难以兼顾定制化需求」的痛点。

created at code style minzipped size attw conventional commits semantic release Start new PR in StackBlitz Codeflow


特性

  • Vue 2.6/2.7/3 一体通用
  • 符合直觉的行为抽象
  • 贴心却不武断的数据与状态管理
  • 保姆级收尾工作,无后顾之忧

对比中后台模板

中后台模板如 vue-pure-adminvue-vben-adminyudao-ui-admin-vue3 等的定位是中后台一整套解决方案,跟 UI 框架、CSS 框架高度耦合。

视图层面的技术百家争鸣、日新月异,但 CRUD 逻辑是亘古不变的,这正是 Admate 的发力点。

Admate 专注逻辑层面,轻量化、侵入性低,可独立使用,也支持跟任意中后台模板搭配使用。


安装

npm i admate

外置依赖

  • vue
  • @vue/composition-api:仅 Vue 2.6 或更早版本需要

阶梯式的配置作用域

全局配置

  1. 建立全局适配层

    示例: src/utils/useAdmateAdapter.js

    • 量身打造生命周期行为
    • 列表筛选参数重置 & 参数校验
    • 支持 URL 传参指定筛选项默认值
    • 支持动态生成筛选项默认值,使用场景举例:日期/时间类的参数,如果其默认值为当前最新时刻,重置筛选项时会重置到已过期的时刻
  2. 在页面中导入使用

    示例: src/views/Page.vue

模块级配置

  1. 按模块拆分适配层,每个模块拥有自己的适配层

    比如:

    • src/views/system/useAdmateAdapter.js
    • src/views/infra/useAdmateAdapter.js
  2. 在页面中导入对应模块的适配层

页面级配置

const { list, listFilterRef, form, faFormDialogRef } = useAdmateAdapter({
  // ...Admate 配置
}, {
  // ...Admate 适配层配置
})

接口级配置

示例: 接口级请求配置


请求配置

axios

useAdmate({
  // Axios 或 Axios 实例
  // 用于调用接口
  axios,
})

axiosConfig

useAdmate({
  // Axios 配置
  axiosConfig: {
    // 各接口的 URL 前缀
    urlPrefix: `${import.meta.env.VITE_BASE_URL}/module`,
    // 列表相关接口
    list: {
      // 读取列表
      read: {},
    },
    // 表单相关接口
    form: {
      // 新增
      create: {},
      // 读取
      read: {},
      // 编辑
      update: {},
      // 删除
      delete: {},
      // 切换状态
      switch: {},
    },
  },
})
// 示例: URL 前缀不统一

useAdmate({
  axiosConfig: {
    urlPrefix: 'module1',
    list: {
      read: {
        // 如果某个接口的前缀不是 'somepage',可以在 URL 前面加斜线,即可忽略该前缀
        url: '/module2/selectOne',
      }
    }
  }
})

全局请求配置

// src/http/index.js

const axiosInstance = axios.create({
  headers: {
    xxx: 'xxx',
  },
})

页面级请求配置

const headers = {
  xxx: 'xxx',
}

const { list, listFilterRef, form, faFormDialogRef } = useAdmateAdapter({
  axiosConfig: {
    urlPrefix,
    list: {
      read: {
        url: 'page',
        headers,
      },
    },
    form: {
      create: {
        url: 'create',
        headers,
      },
      read: {
        url: 'get',
        headers,
      },
      update: {
        url: 'update',
        headers,
      },
      delete: {
        url: 'delete',
        headers,
      },
    },
  },
})

接口级请求配置

const { list, listFilterRef, form, faFormDialogRef } = useAdmateAdapter({
  axiosConfig: {
    urlPrefix,
    form: {
      create: {
        url: 'create',
        headers: {
          xxx: 'xxx',
        },
      },
    },
  },
})

RESTful

如果接口地址需要进行动态拼接

// 配置
const { list, form } = useAdmate({
  axiosConfig: {
    urlPrefix: `${import.meta.env.VITE_BASE_URL}/module`,
    form: {
      read: ({ id }) => ({
        method: 'GET',
        url: id,
      }),
      update: ({ id }) => ({
        method: 'PUT',
        url: id,
      }),
      delete: ({ id }) => ({
        method: 'DELETE',
        url: id,
      }),
      switch: ({ id }) => ({
        method: 'PUT',
        url: id,
      }),
    },
  }
})

// 使用
form.open({ id: 1 }, 'config')
form.read({ id: 1 }, 'config')
form.update({ id: 1 }, 'config')
form.delete({ id: 1 }, 'config')
form.switch({ id: 1 }, 'config')

FormData

Axios 的 data 默认以 application/json 作为 MIME type,如果你需要使用 multipart/form-data

  • 全局配置

给你的 Axios 配置 transformRequestheaders['Content-Type']

  • 局部配置

list.readlist.searchlist.resetform.openform.deleteform.switchform.submit 的参数 1 均支持 FormData 类型

<!-- 示例: 局部配置 -->

<script setup>
import useAdmateAdapter from '@/utils/useAdmateAdapter'

// 过滤 list.value.filter 并转换为 FormData 格式
FormData.from = (json) => {
  const formData = new FormData()
  for (const k in json) {
    if (![Number.NaN, null, undefined].includes(json[k])) {
      formData.append(k, json[k])
    }
  }
  return formData
}

useAdmateAdapter({
  list: {
    proxy: {
      read(readList, trigger) {
        readList(FormData.from(list.value.filter))
      },
    }
  }
})

const FormData = window.FormData
</script>

<template>
  <el-table>
    <el-table-column label="操作">
      <template #default="{ row: { id } }">
        <el-button @click="form.read(FormData.from({ id }))">
          查看
        </el-button>
        <el-button @click="form.update(FormData.from({ id }))">
          编辑
        </el-button>
        <el-button @click="form.delete(FormData.from({ id }))">
          删除
        </el-button>
      </template>
    </el-table-column>
  </el-table>

  <el-dialog>
    <template #footer>
      <el-button @click="() => form.submit(FormData.from(form.data))">
        确 定
      </el-button>
    </template>
  </el-dialog>
</template>

列表

筛选参数

list.filter

useAdmate({
  list: {
    // 页码的参数名称或路径,必填
    // 支持属性名,如 `'pageNo'`
    // 支持属性路径,如 `'page.pageNo'`
    pageNumberAt: undefined,

    // 可以在这里提供筛选参数的默认值
    filter: {
      'pageNumberAt 生成的页码参数名称': 1
    },
  },
})

触发读取

  • 点击专用的读取按钮触发
    • ✗ 操作相对繁琐。
    • ✗ 列表数据与筛选条件可能是无关的。可能产生 “当前的列表数据是否基于筛选项?” 的顾虑,导致徒增点击读取按钮的次数。
    • ✓ 想同时设置多个筛选条件时,只调用一次接口,不浪费服务器资源。
useAdmate({
  list: {
    watchFilter: false,
  }
})

  • 改变筛选条件后即时触发
    • ✓ 操作相对简便。
    • ✓ 列表数据与筛选条件即时绑定。
    • 想同时设置多个筛选条件时,接口会被多次调用,浪费服务器资源 (Admate 已优化)。
useAdmate({
  list: {
    watchFilter: true, // 默认值

    // 防抖间隔,单位毫秒
    // 如果筛选参数不含 input 类型,可以设置为 0,即不防抖
    // 翻页不会触发防抖
    // watchFilter 开启时有效
    debounce: 300, // 默认值
  }
})

列表数据

list.data

useAdmate({
  list: {
    // 列表数据
    data: [],

    // 指定接口返回值中列表数据的位置
    // 支持属性名,如 `'data'`
    // 支持属性路径,如 `'data[0].records'`
    // 支持 symbol 类型的属性名
    // 支持 Function,如 `response => response.data`
    dataAt: undefined,

    // 指定接口返回值中记录总数的位置
    // 支持属性名,如 `'total'`
    // 支持属性路径,如 `'data[0].total'`
    // 支持 symbol 类型的属性名
    // 支持 Function,如 `response => response.total`
    totalAt: undefined,
  }
})

读取列表

list.read

读取列表,在首次进入页面、列表筛选参数改变、单条记录增删查改后会被调用

const { list } = useAdmate()

/**
 * PS: 以下为原始函数签名,如果你配置了 list.proxy.read ,则以 list.proxy.read 为准
 *
 * @param {any} [payload = list.filter]
 * @param {'data'|'params'|'config'} [payloadAs] 指定 payload 的用途
 * @returns {Promise<any>} 接口返回值
 */
list.read() // 手动读取

list.search

重置页码后执行 list.read ,用于筛选条件改变后检索列表

const { list } = useAdmate()

/**
 * PS: 以下为原始函数签名,如果你配置了 list.proxy.read ,则以 list.proxy.read 为准
 *
 * @param {any} [payload = list.filter]
 * @param {'data'|'params'|'config'} [payloadAs] 指定 payload 的用途
 * @returns {Promise<any>} 接口返回值
 */
list.search() // 手动检索

list.reset

重置筛选条件后执行 list.read

const { list } = useAdmate()

/**
 * PS: 以下为原始函数签名,如果你配置了 list.proxy.reset ,则以 list.proxy.reset 为准
 *
 * @param {any} [payload = list.filter]
 * @param {'data'|'params'|'config'} [payloadAs] 指定 payload 的用途
 * @returns {Promise<any>} 接口返回值
 */
list.reset() // 手动重置

list.proxy.read

你可以使用 list.proxy.read 来代理 list.read,以便在 list.read 前后执行一些操作,或改变 list.read 的行为

useAdmate({
  list: {
    proxy: {
      /**
       * @param {Function} readList 被代理的原始 readList
       * @param {string} trigger 调用动机 可能的值:
       *   'immediate': 初始化立即查询列表
       *   'pageNumberChange': 页码改变触发查询列表
       *   'filterChange': 筛选项(含分页大小)改变触发查询列表
       *   'create': 表单新增触发查询列表
       *   'update': 表单编辑触发查询列表
       *   'delete': 表单删除触发查询列表
       *   'switch': 表单状态变更触发查询列表
       */
      read(readList, trigger) {},
    },
  },
})
// 示例: 读取列表之前,校验参数

useAdmate({
  list: {
    proxy: {
      read(readList, trigger) {
        if (trigger === 'filterChange') {
          listFilterRef.value.validate().then(() => {
            readList()
          })
        }
        else {
          readList()
        }
      },
    }
  }
})
// 示例: 单条记录操作成功后,弹出提示

useAdmate({
  list: {
    proxy: {
      read(readList, trigger) {
        readList()
        if (['create', 'upadte', 'delete', 'switch'].includes(trigger)) {
          currentInstance.value.$message.success('操作成功')
        }
      },
    }
  }
})
// 示例: 读取列表后,修改列表数据

const { list } = useAdmate({
  list: {
    proxy: {
      read(readList, trigger) {
        readList().then((response) => {
          // response 为 axiosConfig.list.read 的接口返回值
          list.data = response.data?.filter(v => !v.disabled)
        })
      },
    }
  }
})

list.proxy.reset

你可以使用 list.proxy.reset 来代理 list.reset,以便在 list.reset 前后执行一些操作,或改变 list.reset 的行为

useAdmate({
  list: {
    proxy: {
      /**
       * @param {Function} resetList 被代理的原始 resetList
       */
      reset(resetList) {},
    },
  },
})
// 示例: 使用 UI 组件库的表单重置函数来重置筛选条件

useAdmate({
  list: {
    proxy: {
      reset(resetList) {
        listFilterElFormRef.value.resetFields()
        // 如果分页组件不归属于表单,则表单重置时页码不会被重置,需调用 list.search
        if (!list.watchFilter) {
          list.search()
        }
      },
    }
  }
})

读取状态

list.loading

axiosConfig.list.read 被调用时值为 true,否则为 false

<!-- 示例 -->

<script setup>
import useAdmate from 'admate'
import { getCurrentInstance } from 'vue'

const { proxy } = getCurrentInstance()
const { list } = useAdmate()

function handleTable() {
  list.value.loading = true
  proxy.$POST('').finally(() => {
    list.value.loading = false
  })
}
</script>

<template>
  <el-table v-loading="list.loading" />
</template>

表单

表单风格

表单默认是对话框/抽屉的风格,但也支持独立页面的风格

对比

  • 对话框/抽屉:体验好,割裂感低,表单的开闭不影响父页面状态
  • 独立页面:体验较差,从表单返回父页面时,父页面的状态会丢失,比如列表筛选状态

表单显隐

form.show: boolean

Tip

表单关闭时,表单数据自动恢复至初始状态(不是直接清空)


表单数据

form.data

useAdmate({
  form: {
    // 可以在这里提供表单数据的默认值
    data: {},

    // 在查看、编辑表单时,可能需要调用接口(axiosConfig.form.read)回显表单的数据
    // dataAt 用于指定接口返回值中表单数据的位置
    // 支持属性名,如 `'detail'`
    // 支持属性路径,如 `'data[0].detail'`
    // 支持 symbol 类型的属性名
    // 支持 Function,如 `response => response.detail`
    dataAt: undefined,

    // 接口(axiosConfig.form.read)返回值与 form.data 合并的方式
    mergeData: 'deep',
  },
})

mergeData:

  • 'deep':深合并 (默认)
  • 'shallow':浅合并
  • (newFormData: any) => any:自定义合并方式
  • false:不合并,直接替换

为什么默认是深合并?

在 Vue 2 中,template 不支持 ?. 语法,要在 template 中判空,代码写起来会非常冗余,通常的做法是在 data 中声明空对象

比如给 form.data 提供默认值:

<script setup>
import useAdmate from 'admate'

const { form } = useAdmate({
  form: {
    data: {
      a: {
        b: {}
      }
    }
  }
})
</script>

<template>
  {{ form.data.a.b.c }}
</template>

如果 axiosConfig.form.read 的返回值为: { a: {} }

如果与默认值浅合并后将得到: { a: {} } —— 默认值中的对象 b 丢失了,引发空指针异常。

如果与默认值深合并后将得到: { a: { b: {} } } —— 代码正常工作。

// 示例: 自定义合并方式

import { mergeWith } from 'lodash'

function defaultFormData() {
  return {
    a: {
      b: {}
    }
  }
}

const { form } = useAdmate({
  form: {
    data: defaultFormData(),
    // 接口返回值中嵌套的对象可能为 null,会覆盖默认值中的空对象
    mergeData(
      // 接口返回值在通过 form.dataAt 计算过后的值
      newFormData
    ) {
      // Vue 3 中不需要赋值,mergeWith 的改动是响应式的
      form.data = mergeWith(
        defaultFormData(),
        newFormData,
        (oldObj, newObj) => [undefined, null].includes(newObj) ? oldObj : undefined
      )
    },
  },
})

表单形态

form.status: 'create' | 'read' | 'update'


表单标题

form.title: string

// 示例: 根据表态形态生成对应的标题

import { computed } from 'vue'

const { form } = useAdmate({
  form: {
    title: computed(() => ({ create: '新增', read: '查看', update: '编辑' }[form.status])),
  },
})

新增

打开表单,提交时会调用 axiosConfig.form.create

const { form } = useAdmate()

form.create()

// 等价于:将表单形态设置为“新增”,然后打开表单
form.status = 'create'
form.open()

复制新增

表单的初始数据不是空白,而是复制一条已有的记录

  1. 打开表单时,和查看/编辑一样,需要调接口回显
  2. 提交表单时调用的是新增的接口
const { form } = useAdmate()

form.create(row)

// 等价于:将表单形态设置为“新增”,然后打开表单并传参
form.status = 'create'
form.open(row)

查看

打开表单,并调用 axiosConfig.form.read 回显表单内容

const { form } = useAdmate()

form.read()

// 等价于:将表单形态设置为“查看”,然后打开表单
form.status = 'read'
/**
 * PS: 以下为原始 openForm 的函数签名,如果你配置了 form.proxy.open ,则以 form.proxy.open 为准
 *
 * @param {any} [payload] 如果 payload 不为空,则会调用 axiosConfig.form.read
 * @param {'data'|'params'|'config'|'cache'} [payloadAs] 指定 payload 的用途
 *   'data': 将 payload 用作请求配置的 `data` 参数(请求方式为 POST / PATCH / PUT / DELETE 时默认)
 *   'params': 将 payload 用作请求配置的 `params` 参数(请求方式为 GET / HEAD 时默认)
 *   'config': 将 payload 仅用于构建请求配置(详见 RESTful 章节)
 *   'cache': 将 payload 直接用作表单数据(不调用读取单条记录的接口)
 * @returns {Promise<any>} axiosConfig.form.read 的返回值
 */
form.open()

编辑

打开表单,并调用 axiosConfig.form.read 回显表单内容,提交时会调用 axiosConfig.form.update

const { form } = useAdmate()

form.update()

// 等价于:将表单形态设置为“编辑”,然后打开表单
form.status = 'update'
/**
 * PS: 以下为原始 form.open 的函数签名,如果你配置了 form.proxy.open ,则以 form.proxy.open 为准
 *
 * @param {any} [payload] 如果 payload 不为空,则会调用 axiosConfig.form.read
 * @param {'data'|'params'|'config'|'cache'} [payloadAs] 指定 payload 的用途
 *   'data': 将 payload 用作请求配置的 `data` 参数(请求方式为 POST / PATCH / PUT / DELETE 时默认)
 *   'params': 将 payload 用作请求配置的 `params` 参数(请求方式为 GET / HEAD 时默认)
 *   'config': 将 payload 仅用于构建请求配置(详见 RESTful 章节)
 *   'cache': 将 payload 直接用作表单数据(不调用读取单条记录的接口)
 * @returns {Promise<any>} axiosConfig.form.read 的返回值
 */
form.open()

删除

const { form } = useAdmate()

/**
 * @param {any} [payload]
 * @param {'data'|'params'|'config'} [payloadAs] 指定 payload 的用途
 *   'data': 将 payload 用作请求配置的 `data` 参数(请求方式为 POST / PATCH / PUT / DELETE 时默认)
 *   'params': 将 payload 用作请求配置的 `params` 参数(请求方式为 GET / HEAD 时默认)
 *   'config': 将 payload 仅用于构建请求配置(详见 RESTful 章节)
 * @returns {Promise<any>} axiosConfig.form.delete 的返回值
 */
form.delete()

Tip

删除非首页最后一条记录时,自动切换至上一页


状态变更

状态变更有三种方式:

  1. 后端提供一个统一的接口,传参指定新的状态
<script setup>
import useAdmate from 'admate'

const { form } = useAdmate()

/**
 * @param {any} [payload]
 * @param {'data'|'params'|'config'} [payloadAs] 指定 payload 的用途
 *   'data': 将 payload 用作请求配置的 `data` 参数(请求方式为 POST / PATCH / PUT / DELETE 时默认)
 *   'params': 将 payload 用作请求配置的 `params` 参数(请求方式为 GET / HEAD 时默认)
 *   'config': 将 payload 仅用于构建请求配置(详见 RESTful 章节)
 * @returns {Promise<any>} axiosConfig.form.switch 的返回值
 */
form.switch()
</script>

<template>
  <el-table>
    <el-table-column
      label="操作"
      align="center"
    >
      <template #default="{ row: { id, status } }">
        <el-switch @change="form.switch({ id, status: status ^ 1 })" />
      </template>
    </el-table-column>
  </el-table>
</template>
  1. 后端提供启用和停用两个接口
<script setup>
import useAdmate from 'admate'

const { form } = useAdmate({
  axiosConfig: {
    form: {
      switch: ({ id, status }) => ({
        method: 'PUT',
        url: `${status === 1 ? 'enable' : `disable`}/${id}`,
      }),
    }
  },
})

/**
 * @param {any} [payload]
 * @param {'data'|'params'|'config'} [payloadAs] 指定 payload 的用途
 *   'data': 将 payload 用作请求配置的 `data` 参数(请求方式为 POST / PATCH / PUT / DELETE 时默认)
 *   'params': 将 payload 用作请求配置的 `params` 参数(请求方式为 GET / HEAD 时默认)
 *   'config': 将 payload 仅用于构建请求配置(详见 RESTful 章节)
 * @returns {Promise<any>} axiosConfig.form.switch 的返回值
 */
form.switch()
</script>

<template>
  <el-table>
    <el-table-column
      label="操作"
      align="center"
    >
      <template #default="{ row: { id, status } }">
        <el-switch @change="form.switch({ id, status: status ^ 1 }, 'config')" />
      </template>
    </el-table-column>
  </el-table>
</template>
  1. 后端未提供独立的接口,使用编辑接口改变状态
<script setup>
import useAdmate from 'admate'

const { form } = useAdmate({
  axiosConfig: {
    form: {
      update: {
        // ...
      },
      switch: {
        // 按编辑接口进行配置
      },
    }
  },
})

/**
 * @param {any} [payload]
 * @param {'data'|'params'|'config'} [payloadAs] 指定 payload 的用途
 *   'data': 将 payload 用作请求配置的 `data` 参数(请求方式为 POST / PATCH / PUT / DELETE 时默 认)
 *   'params': 将 payload 用作请求配置的 `params` 参数(请求方式为 GET / HEAD 时默认)
 *   'config': 将 payload 仅用于构建请求配置(详见 RESTful 章节)
 * @returns {Promise<any>} axiosConfig.form.switch 的返回值
 */
form.switch()
</script>

<template>
  <el-table>
    <el-table-column
      label="操作"
      align="center"
    >
      <template #default="{ row }">
        <el-switch @change="form.switch({ ...row, status: row.status ^ 1 })" />
      </template>
    </el-table-column>
  </el-table>
</template>

打开表单

form.open

打开表单,函数签名要分情况:

form.proxy.open

你可以使用 form.proxy.open 来代理 form.open,以便在 form.open 前后执行一些操作,或改变 form.open 的行为

useAdmate({
  form: {
    proxy: {
      /**
       * @param {Function} openForm 被代理的原始 openForm
       * @returns {Promise<object> | object | void} object 为打开表单后 form 的终态
       */
      open(openForm) {},
    }
  }
})
// 示例: 回显表单后,修改表单数据

const { form } = useAdmate({
  form: {
    proxy: {
      open(openForm) {
        // 新增时 openForm 没有返回值
        return new Promise((resolve, reject) => {
          openForm()?.then((response) => {
            // response 为 axiosConfig.r 的接口返回值
            // 修改表单数据
            form.data.status = 1
            resolve()
          }).catch((e) => {
            reject(e)
          })
        })
      },
    }
  }
})
// 示例: 回显表单后,清除校验

useAdmate({
  form: {
    proxy: {
      open(openForm) {
        return new Promise((resolve, reject) => {
          openForm()?.finally(() => {
            formRef.value.clearValidate()
          }).then(() => {
            resolve()
          }).catch((e) => {
            reject(e)
          })
        })
      },
    }
  }
})
// 示例: 回显表单后,自定义表单的开闭和读取状态
useAdmate({
  form: {
    proxy: {
      open(openForm) {
        return new Promise((resolve, reject) => {
          // 可以在 finally 中 resolve
          openForm().then(() => {
            // 回显成功后,默认停止加载
            resolve({
              loading: false,
            })
          }).catch(() => {
            // 回显失败后,默认关闭表单并停止加载
            resolve({
              show: false,
              loading: false,
            })
          })
        })
      }
    }
  }
})

// 也可以返回一个对象(如果没有异步操作)
useAdmate({
  form: {
    proxy: {
      open(openForm) {
        return {
          loading: false
        }
      }
    }
  }
})

读取状态

form.loading

axiosConfig.form.read 被调用时值为 true,否则为 false

不能将该值当作表单回显结束的标志,因为复用列表数据时不会调用 axiosConfig.r

<!-- 示例 -->

<script setup>
import useAdmate from 'admate'

const { form } = useAdmate()
</script>

<template>
  <el-dialog>
    <el-form v-loading="form.loading" />
  </el-dialog>
</template>

提交表单

form.submit

提交表单,新增时调用 axiosConfig.form.create,编辑时调用 axiosConfig.form.update

const { form } = useAdmate()

/**
 * PS: 以下为原始 form.submit 的函数签名,如果你配置了 form.proxy.submit ,则以 form.proxy.submit 为准
 *
 * @param {any} [payload = form.data]
 * @param {'data'|'params'|'config'} [payloadAs] 指定 payload 的用途
 * @returns {Promise<any>} 接口返回值
 */
form.submit()

form.proxy.submit

你可以使用 form.proxy.submit 来代理 form.submit ,以便在 form.submit 前后执行一些操作,或改变 form.submit 的行为

useAdmate({
  form: {
    proxy: {
      /**
       * @param {Function} submitForm 被代理的原始 submitForm
       * @returns {Promise<object> | object | void} object 为提交表单后 form 的终态
       */
      submit(submitForm) {}
    }
  }
})
// 示例: 指定提交参数

form.submit({
  ...form.data,
  status: 1,
})

// form.submit 被代理时
useAdmate({
  form: {
    proxy: {
      submit(submitForm) {
        return new Promise((resolve, reject) => {
          submitForm({
            ...form.data,
            status: 1,
          }).then(() => {
            resolve()
          }).catch((e) => {
            reject(e)
          })
        })
      }
    }
  }
})
// 示例: 提交前校验表单

useAdmate({
  form: {
    proxy: {
      submit(submitForm) {
        return new Promise((resolve, reject) => {
          formRef.value.validate().then(() => {
            submitForm().then(() => {
              resolve()
            }).catch((e) => {
              reject(e)
            })
          })
        })
      }
    }
  }
})
// 示例: 提交表单后,自定义表单的开闭和提交状态

// 返回一个 promise
useAdmate({
  form: {
    proxy: {
      submit(submitForm) {
        return new Promise((resolve, reject) => {
          formRef.value.validate().then(() => {
            submitForm().then(() => {
              // 提交成功后,默认关闭表单,并停止加载
              resolve({
                show: false,
                submitting: false,
              })
            }).catch(() => {
              // 提交失败后,默认仅停止加载
              resolve({
                show: true,
                submitting: false,
              })
            })
          })
        })
      }
    }
  }
})

// 也可以返回一个对象(如果没有异步操作)
useAdmate({
  form: {
    proxy: {
      submit(submitForm) {
        return {
          show: false,
          submitting: false,
        }
      }
    }
  }
})

提交状态

form.submitting

axiosConfig.form.createaxiosConfig.form.update 被调用时值为 true,否则为 false

<!-- 示例 -->

<script setup>
import useAdmate from 'admate'

const { form } = useAdmate()
</script>

<template>
  <el-dialog>
    <template #footer>
      <el-button :loading="form.submitting">
        确 定
      </el-button>
    </template>
  </el-dialog>
</template>

生命周期

读取列表

useAdmateAdapter({}, {
  onListRead(res, trigger) {
    // res 为接口返回值,trigger 为调用动机
    // 可访问 this(组件实例)
  }
})

打开 / 读取表单

  • 读取表单前
watch(() => form.value.show, (n) => {
  if (n) {
    // 打开表单
  }
})
  • 读取表单后
// 示例: 适配层提供 onFormOpened

useAdmateAdapter({}, {
  onFormOpened(res) {
    // res 为接口返回值(新增时为空)
    // 可访问 this(组件实例)
  }
})
  • 读取表单后 (不含新增)
// 示例: 适配层提供 onFormRead

useAdmateAdapter({}, {
  onFormRead(res) {
    // res 为接口返回值
    // 可访问 this(组件实例)
  }
})

提交表单

  • 提交表单前
useAdmateAdapter({}, {
  onFormSubmit(form) {
    // 可访问 this(组件实例)
  }
})
  • 提交表单后
useAdmateAdapter({}, {
  onFormSubmitted(res) {
    // res 为接口返回值
    // 可访问 this(组件实例)
  }
})

关闭表单

watch(() => form.value.show, (n) => {
  if (!n) {
    // 关闭表单
  }
})

场景

表单是子组件

将表单抽离为子组件

示例


表单是独立页面

操作单条记录时,跳转到专用的表单页面,操作完毕后返回

示例


只有表单没有列表

表单默认打开,且无法关闭,通常用于列表中只有一条数据,故列表被省略的场景

示例


嵌套使用

当前页面的对话框也使用 Admate

示例


更新日志

各版本详细改动请参考 release notes