Skip to content

Crud Form

Introduction to the form part of the z-crud component.

If you only want to use the component's filter form and table features, please refer to the following examples.

Basic Usage

Configure search in the column item to generate form items.

  • label and field can be omitted, defaulting to prop and label
  • The component configures clearable, placeholder and filterable by default
  • For the specific configuration of the search attribute, refer to the column items of z-form
  • The form layout is fixed as { xs: 24, sm: 12, md: 8, lg: 8, xl: 6 }
<!-- eslint-disable no-console -->
<script lang="ts" setup>
import { ref } from 'vue'

interface RowData {
  name: string
  gender: string
  age: number
  time: string
}

interface GetTableDataRes { result: { page: number, pageSize: number, list: RowData[], total: number } }

const loading = ref(false)
const formData = ref({
  name: '',
  gender: '',
  age: '',
})
const tableData = ref<RowData[]>([])

const columns = ref([
  {
    prop: 'name',
    label: 'Name',
    search: {
      component: 'input',
      label: 'Name',
      field: 'name',
    },
  },
  {
    prop: 'gender',
    label: 'Gender',
    search: {
      component: 'select',
      label: 'Gender',
      field: 'gender',
    },
  },
  {
    prop: 'age',
    label: 'Age',
    search: {
      component: 'input',
      label: 'Age',
      field: 'age',
    },
  },
  {
    prop: 'time',
    label: 'Date',
  },
])

const options = {
  gender: [{ label: 'male', value: 'male' }, { label: 'female', value: 'female' }],
}
const pagination = ref({
  page: 1,
  pageSize: 2,
  total: 4,
})

function mockApi(params: any): Promise<GetTableDataRes> {
  console.log(params, 'params')
  return new Promise((resolve) => {
    setTimeout(() => {
      const data = [
        {
          name: 'Steven',
          gender: 'male',
          age: 22,
          time: '2020-01-01',
        },
        {
          name: 'Helen',
          gender: 'male',
          age: 12,
          time: '2012-01-01',
        },
        {
          name: 'Nancy',
          gender: 'female',
          age: 18,
          time: '2018-01-01',
        },
        {
          name: 'Jack',
          gender: 'male',
          age: 28,
          time: '2028-01-01',
        },
      ]

      resolve({
        result: {
          page: 1,
          pageSize: 10,
          total: 4,
          list: data.slice((pagination.value.page - 1) * pagination.value.pageSize, pagination.value.page * pagination.value.pageSize),
        },
      })
    }, 100)
  })
}

async function getTableData() {
  loading.value = true
  try {
    const params = {
      ...pagination.value,
      ...formData.value,
    }
    const res = await mockApi(params)
    tableData.value = res.result.list
    pagination.value.total = res.result.total
  }
  catch (error) {
    console.log(error)
  }
  loading.value = false
}

function handleSearch() {
  pagination.value.page = 1
  getTableData()
}

getTableData()
</script>

<template>
  <z-crud
    v-model:pagination="pagination"
    v-model:data="tableData"
    v-model:formData="formData"
    :columns="columns"
    :options="options"
    :loading="loading"
    :action="false"
    @refresh="getTableData"
    @search="handleSearch"
    @reset="handleSearch"
  />
</template>

Configuring the columns field of search can also achieve the same effect. If you want to configure other attributes such as the form's labelWidth, you can also put them in search.

<script lang="ts" setup>
import { ref } from 'vue'

const loading = ref(false)
const formData = ref({
  name: '',
  gender: '',
  age: '',
})
const tableData = ref([])

const columns = ref([
  {
    prop: 'name',
    label: 'Name',
  },
  {
    prop: 'gender',
    label: 'Gender',
  },
  {
    prop: 'age',
    label: 'Age',
  },
  {
    prop: 'time',
    label: 'Date',
  },
])

const searchFormConfig = ref({
  labelWith: '80px',
  columns: [
    {
      component: 'input',
      label: 'Name',
      field: 'name',
    },
    {
      component: 'select',
      label: 'Gender',
      field: 'gender',
    },
    {
      component: 'input',
      label: 'Age',
      field: 'age',
    },
  ],
})

const options = {
  gender: [{ label: 'male', value: 'male' }, { label: 'female', value: 'female' }],
}
const pagination = ref({
  page: 1,
  pageSize: 2,
  total: 4,
})
const request = ref({
  searchApi: mockApi,
})

function mockApi() {
  return new Promise((resolve) => {
    setTimeout(() => {
      const data = [
        {
          name: 'Steven',
          gender: 'male',
          age: 22,
          time: '2020-01-01',
        },
        {
          name: 'Helen',
          gender: 'male',
          age: 12,
          time: '2012-01-01',
        },
        {
          name: 'Nancy',
          gender: 'female',
          age: 18,
          time: '2018-01-01',
        },
        {
          name: 'Jack',
          gender: 'male',
          age: 28,
          time: '2028-01-01',
        },
      ]

      resolve({
        data: {
          page: 1,
          pageSize: 10,
          total: 4,
          list: data.slice((pagination.value.page - 1) * pagination.value.pageSize, pagination.value.page * pagination.value.pageSize),
        },
      })
    }, 100)
  })
}
</script>

<template>
  <z-crud
    v-model:pagination="pagination"
    v-model:data="tableData"
    v-model:formData="formData"
    v-model:loading="loading"
    :options="options"
    :columns="columns"
    :search="searchFormConfig"
    :action="false"
    :request="request"
  />
</template>

Configuring form in column can also generate query form items, but it will also generate add, edit, and query form items at the same time. If not needed, you can set the add and edit fields to false.

<!-- eslint-disable no-console -->
<script lang="ts" setup>
import { ref } from 'vue'

interface RowData {
  name: string
  gender: string
  age: number
  time: string
}

interface GetTableDataRes { result: { page: number, pageSize: number, list: RowData[], total: number } }

const loading = ref(false)
const formData = ref({
  name: '',
  gender: '',
  age: '',
})
const tableData = ref<RowData[]>([])

const columns = ref([
  {
    prop: 'name',
    label: 'Name',
    add: false,
    edit: false,
    form: {
      component: 'input',
      label: 'Name',
      field: 'name',
    },
  },
  {
    prop: 'gender',
    label: 'Gender',
    add: false,
    edit: false,
    form: {
      component: 'select',
      label: 'Gender',
      field: 'gender',
    },
  },
  {
    prop: 'age',
    label: 'Age',
    add: false,
    edit: false,
    form: {
      component: 'input',
      label: 'Age',
      field: 'age',
    },
  },
  {
    prop: 'time',
    label: 'Date',
  },
])

const options = {
  gender: [{ label: 'male', value: 'male' }, { label: 'female', value: 'female' }],
}
const pagination = ref({
  page: 1,
  pageSize: 2,
  total: 4,
})

function mockApi(params: any): Promise<GetTableDataRes> {
  console.log(params, 'any')
  return new Promise((resolve) => {
    setTimeout(() => {
      const data = [
        {
          name: 'Steven',
          gender: 'male',
          age: 22,
          time: '2020-01-01',
        },
        {
          name: 'Helen',
          gender: 'male',
          age: 12,
          time: '2012-01-01',
        },
        {
          name: 'Nancy',
          gender: 'female',
          age: 18,
          time: '2018-01-01',
        },
        {
          name: 'Jack',
          gender: 'male',
          age: 28,
          time: '2028-01-01',
        },
      ]

      resolve({
        result: {
          page: 1,
          pageSize: 10,
          total: 4,
          list: data.slice((pagination.value.page - 1) * pagination.value.pageSize, pagination.value.page * pagination.value.pageSize),
        },
      })
    }, 100)
  })
}

async function getTableData() {
  loading.value = true
  try {
    const params = {
      ...pagination.value,
      ...formData.value,
    }
    const res = await mockApi(params)
    tableData.value = res.result.list
    pagination.value.total = res.result.total
  }
  catch (error) {
    console.log(error)
  }
  loading.value = false
}

function handleSearch() {
  pagination.value.page = 1
  getTableData()
}

getTableData()
</script>

<template>
  <z-crud
    v-model:pagination="pagination"
    v-model:data="tableData"
    v-model:formData="formData"
    :columns="columns"
    :options="options"
    :loading="loading"
    @refresh="getTableData"
    @search="handleSearch"
    @reset="handleSearch"
  />
</template>

If a column item does not want a table column and only wants a form item, do not configure fields such as type, slot, render, label, prop, component in column, and then configure form-related fields such as search, form to achieve it.

<!-- eslint-disable no-console -->
<script lang="ts" setup>
import { ref } from 'vue'

interface RowData {
  name: string
  gender: string
  age: number
  time: string
}

interface GetTableDataRes { result: { page: number, pageSize: number, list: RowData[], total: number } }

const loading = ref(false)
const formData = ref({
  name: '',
  gender: '',
  age: '',
})
const tableData = ref<RowData[]>([])

const columns = ref([
  {
  // Only search is configured
    search: {
      component: 'input',
      label: 'Name',
      field: 'name',
    },
  },
  {
    prop: 'gender',
    label: 'Gender',
    search: {
      component: 'select',
      label: 'Gender',
      field: 'gender',
    },
  },
  {
    prop: 'age',
    label: 'Age',
    search: {
      component: 'input',
      label: 'Age',
      field: 'age',
    },
  },
  {
    prop: 'time',
    label: 'Date',
  },
])

const options = {
  gender: [{ label: 'male', value: 'male' }, { label: 'female', value: 'female' }],
}
const pagination = ref({
  page: 1,
  pageSize: 2,
  total: 4,
})

function mockApi(params: any): Promise<GetTableDataRes> {
  console.log(params, 'params')
  return new Promise((resolve) => {
    setTimeout(() => {
      const data = [
        {
          name: 'Steven',
          gender: 'male',
          age: 22,
          time: '2020-01-01',
        },
        {
          name: 'Helen',
          gender: 'male',
          age: 12,
          time: '2012-01-01',
        },
        {
          name: 'Nancy',
          gender: 'female',
          age: 18,
          time: '2018-01-01',
        },
        {
          name: 'Jack',
          gender: 'male',
          age: 28,
          time: '2028-01-01',
        },
      ]

      resolve({
        result: {
          page: 1,
          pageSize: 10,
          total: 4,
          list: data.slice((pagination.value.page - 1) * pagination.value.pageSize, pagination.value.page * pagination.value.pageSize),
        },
      })
    }, 100)
  })
}

async function getTableData() {
  loading.value = true
  try {
    const params = {
      ...pagination.value,
      ...formData.value,
    }
    const res = await mockApi(params)
    tableData.value = res.result.list
    pagination.value.total = res.result.total
  }
  catch (error) {
    console.log(error)
  }
  loading.value = false
}

function handleSearch() {
  pagination.value.page = 1
  getTableData()
}

getTableData()
</script>

<template>
  <z-crud
    v-model:pagination="pagination"
    v-model:data="tableData"
    v-model:formData="formData"
    :columns="columns"
    :options="options"
    :loading="loading"
    :action="false"
    @refresh="getTableData"
    @search="handleSearch"
    @reset="handleSearch"
  />
</template>

Condition Caching

Configure the name field to enable caching by default. The filter form data and pagination data will be cached in sessionStorage, and the condition data will be retrieved from the cache first on the next load.

Data retrieval needs to be called in the onMounted lifecycle.

<!-- eslint-disable no-console -->
<script lang="ts" setup>
import { onMounted, ref } from 'vue'

interface RowData {
  name: string
  gender: string
  age: number
  time: string
}

interface GetTableDataRes { result: { page: number, pageSize: number, list: RowData[], total: number } }

const loading = ref(false)
const formData = ref({
  name: '',
  gender: '',
  age: '',
})
const tableData = ref<RowData[]>([])

const columns = ref([
  {
    prop: 'name',
    label: 'Name',
    search: {
      component: 'input',
      label: 'Name',
      field: 'name',
    },
  },
  {
    prop: 'gender',
    label: 'Gender',
    search: {
      component: 'select',
      label: 'Gender',
      field: 'gender',
    },
  },
  {
    prop: 'age',
    label: 'Age',
    search: {
      component: 'input',
      label: 'Age',
      field: 'age',
    },
  },
  {
    prop: 'time',
    label: 'Date',
  },
])

const options = {
  gender: [{ label: 'male', value: 'male' }, { label: 'female', value: 'female' }],
}
const pagination = ref({
  page: 1,
  pageSize: 2,
  total: 4,
})

function mockApi(params: any): Promise<GetTableDataRes> {
  console.log(params, 'params')
  return new Promise((resolve) => {
    setTimeout(() => {
      const data = [
        {
          name: 'Steven',
          gender: 'male',
          age: 22,
          time: '2020-01-01',
        },
        {
          name: 'Helen',
          gender: 'male',
          age: 12,
          time: '2012-01-01',
        },
        {
          name: 'Nancy',
          gender: 'female',
          age: 18,
          time: '2018-01-01',
        },
        {
          name: 'Jack',
          gender: 'male',
          age: 28,
          time: '2028-01-01',
        },
      ]

      resolve({
        result: {
          page: 1,
          pageSize: 10,
          total: 4,
          list: data.slice((pagination.value.page - 1) * pagination.value.pageSize, pagination.value.page * pagination.value.pageSize),
        },
      })
    }, 100)
  })
}

async function getTableData() {
  loading.value = true
  try {
    const params = {
      ...pagination.value,
      ...formData.value,
    }
    console.log(params, 'cache params')
    const res = await mockApi(params)
    tableData.value = res.result.list
    pagination.value.total = res.result.total
  }
  catch (error) {
    console.log(error)
  }
  loading.value = false
}

function handleSearch() {
  pagination.value.page = 1
  getTableData()
}

onMounted(() => {
  getTableData()
})
</script>

<template>
  <z-crud
    v-model:pagination="pagination"
    v-model:data="tableData"
    v-model:formData="formData"
    :columns="columns"
    :options="options"
    :loading="loading"
    :action="false"
    name="crudCache"
    @refresh="getTableData"
    @search="handleSearch"
    @reset="handleSearch"
  />
</template>

After configuring request, cached conditions will be handled internally.

<!-- eslint-disable no-console -->
<script lang="ts" setup>
import { ref } from 'vue'

const loading = ref(false)
const formData = ref({
  name: '',
  gender: '',
  time: [],
})
const tableData = ref([])

const columns = ref([
  {
    prop: 'name',
    label: 'Name',
    search: {
      component: 'input',
      field: 'name',
      label: 'Name',
    },
  },
  {
    prop: 'gender',
    label: 'Gender',
    search: {
      component: 'select',
      field: 'gender',
    },
  },
  {
    prop: 'age',
    label: 'Age',
    search: {
      component: 'el-date-picker',
      field: 'time',
      label: 'Date',
      fieldProps: {
        type: 'daterange',
        startPlaceholder: 'Start date',
        endPlaceholder: 'End date',
      },
    },
  },
  {
    prop: 'time',
    label: 'Date',
  },
])

const options = {
  gender: [{ label: 'male', value: 'male' }, { label: 'female', value: 'female' }],
}
const pagination = ref({
  page: 1,
  pageSize: 2,
  total: 4,
})
const request = ref({
  searchApi: mockApi,
})

function mockApi(params: any) {
  console.log(params, 'cache params second')
  return new Promise((resolve) => {
    setTimeout(() => {
      const data = [
        {
          name: 'Steven',
          gender: 'male',
          age: 22,
          time: '2020-01-01',
        },
        {
          name: 'Helen',
          gender: 'male',
          age: 12,
          time: '2012-01-01',
        },
        {
          name: 'Nancy',
          gender: 'female',
          age: 18,
          time: '2018-01-01',
        },
        {
          name: 'Jack',
          gender: 'male',
          age: 28,
          time: '2028-01-01',
        },
      ]

      resolve({
        data: {
          page: 1,
          pageSize: 10,
          total: 4,
          list: data.slice((pagination.value.page - 1) * pagination.value.pageSize, pagination.value.page * pagination.value.pageSize),
        },
      })
    }, 100)
  })
}
</script>

<template>
  <z-crud
    v-model:pagination="pagination"
    v-model:data="tableData"
    v-model:formData="formData"
    v-model:loading="loading"
    :options="options"
    :columns="columns"
    :action="false"
    :request="request"
    name="nameCacheSecond"
  />
</template>

Validation

  • Add the required field in the form field, or set the required field in formItemProps to set it as required. The validation message will be automatically generated based on the label or can be customized.
  • Configure the rules field in the search object passed to z-crud to define form validation rules.
  • Configure rules in the form items in the form field to define the validation rules for the current form item.
<script lang="ts" setup>
import { ref } from 'vue'

const loading = ref(false)
const formData = ref({
  name: '',
  gender: '',
  time: [],
})
const tableData = ref([])
const searchFormConfig = ref({
  labelWidth: '80px',
  rules: {
    time: [{ required: true, message: 'Date is required' }],
  },
})

const columns = ref([
  {
    prop: 'name',
    label: 'Name',
    search: {
      component: 'input',
      field: 'name',
      label: 'Name',
      required: true,
      message: 'Please provide the full name',
    },
  },
  {
    prop: 'gender',
    label: 'Gender',
    search: {
      component: 'select',
      field: 'gender',
      formItemProps: {
        required: true,
        label: 'Gender',
      },
    },
  },
  {
    prop: 'age',
    label: 'Age',
    search: {
      component: 'el-date-picker',
      field: 'time',
      label: 'Date',
      rules: {
        required: true,
        message: 'Date is required',
      },
      fieldProps: {
        type: 'daterange',
        startPlaceholder: 'Start date',
        endPlaceholder: 'End date',
      },
    },
  },
  {
    prop: 'time',
    label: 'Date',
  },
])

const options = {
  gender: [{ label: 'male', value: 'male' }, { label: 'female', value: 'female' }],
}
const pagination = ref({
  page: 1,
  pageSize: 2,
  total: 4,
})
const request = ref({
  searchApi: mockApi,
})

function mockApi() {
  return new Promise((resolve) => {
    setTimeout(() => {
      const data = [
        {
          name: 'Steven',
          gender: 'male',
          age: 22,
          time: '2020-01-01',
        },
        {
          name: 'Helen',
          gender: 'male',
          age: 12,
          time: '2012-01-01',
        },
        {
          name: 'Nancy',
          gender: 'female',
          age: 18,
          time: '2018-01-01',
        },
        {
          name: 'Jack',
          gender: 'male',
          age: 28,
          time: '2028-01-01',
        },
      ]

      resolve({
        data: {
          page: 1,
          pageSize: 10,
          total: 4,
          list: data.slice((pagination.value.page - 1) * pagination.value.pageSize, pagination.value.page * pagination.value.pageSize),
        },
      })
    }, 100)
  })
}
</script>

<template>
  <z-crud
    v-model:pagination="pagination"
    v-model:data="tableData"
    v-model:formData="formData"
    v-model:loading="loading"
    :options="options"
    :columns="columns"
    :action="false"
    :search="searchFormConfig"
    :request="request"
  />
</template>

Custom Form Items

We can use slot or render to customize the content of form items.

<script lang="ts" setup>
import { h, ref } from 'vue'

const loading = ref(false)
const formData = ref({
  name: '',
  age: '',
  height: 'Custom height label',
})
const tableData = ref([])

const columns = ref([
  {
    prop: 'name',
    label: 'Name',
    search: {
      component: 'input',
      field: 'name',
      label: 'Name',
    },
  },
  {
    prop: 'gender',
    label: 'Gender',
    search: {
      slot: 'ageSlot',
      label: 'Age',
    },
  },
  {
    prop: 'age',
    label: 'Age',
    search: {
      render: () => h('span', {}, formData.value.height),
      label: 'Height',
    },
  },
  {
    prop: 'time',
    label: 'Date',
  },
])

const options = {
  gender: [{ label: 'male', value: 'male' }, { label: 'female', value: 'female' }],
}
const pagination = ref({
  page: 1,
  pageSize: 2,
  total: 4,
})
const request = ref({
  searchApi: mockApi,
})

function mockApi() {
  return new Promise((resolve) => {
    setTimeout(() => {
      const data = [
        {
          name: 'Steven',
          gender: 'male',
          age: 22,
          time: '2020-01-01',
        },
        {
          name: 'Helen',
          gender: 'male',
          age: 12,
          time: '2012-01-01',
        },
        {
          name: 'Nancy',
          gender: 'female',
          age: 18,
          time: '2018-01-01',
        },
        {
          name: 'Jack',
          gender: 'male',
          age: 28,
          time: '2028-01-01',
        },
      ]

      resolve({
        data: {
          page: 1,
          pageSize: 10,
          total: 4,
          list: data.slice((pagination.value.page - 1) * pagination.value.pageSize, pagination.value.page * pagination.value.pageSize),
        },
      })
    }, 100)
  })
}
</script>

<template>
  <z-crud
    v-model:pagination="pagination"
    v-model:data="tableData"
    v-model:formData="formData"
    v-model:loading="loading"
    :columns="columns"
    :options="options"
    :search="{ size: 'default' }"
    :action="false"
    :request="request"
  >
    <template #ageSlot>
      <el-input v-model="formData.age" placeholder="Please enter an age" clearable />
    </template>
  </z-crud>
</template>

Custom label and error

label and error support passing strings, render functions, or concatenated Slot strings.

<script lang="ts" setup>
import { h, ref } from 'vue'

const loading = ref(false)
const formData = ref({
  name: '',
  gender: '',
  time: [],
})
const tableData = ref([])

const columns = ref([
  {
    prop: 'name',
    label: 'Name',
    search: {
      component: 'input',
      field: 'name',
      label: () => h('span', {}, 'Name'),
      required: true,
      error: 'error message',
    },
  },
  {
    prop: 'gender',
    label: 'Gender',
    search: {
      component: 'select',
      field: 'gender',
      label: 'labelSlot',
      required: true,
      error: h('span', {}, 'errorSlot'),
    },
  },
  {
    prop: 'age',
    label: 'Age',
    search: {
      component: 'el-date-picker',
      field: 'time',
      label: 'Date',
      required: true,
      error: 'errorSlot',
      fieldProps: {
        type: 'daterange',
        startPlaceholder: 'Start date',
        endPlaceholder: 'End date',
      },
    },
  },
])

const options = {
  gender: [{ label: 'male', value: 'male' }, { label: 'female', value: 'female' }],
}
const pagination = ref({
  page: 1,
  pageSize: 2,
  total: 4,
})
const request = ref({
  searchApi: mockApi,
})

function mockApi() {
  return new Promise((resolve) => {
    setTimeout(() => {
      const data = [
        {
          name: 'Steven',
          gender: 'male',
          age: 22,
          time: '2020-01-01',
        },
        {
          name: 'Helen',
          gender: 'male',
          age: 12,
          time: '2012-01-01',
        },
        {
          name: 'Nancy',
          gender: 'female',
          age: 18,
          time: '2018-01-01',
        },
        {
          name: 'Jack',
          gender: 'male',
          age: 28,
          time: '2028-01-01',
        },
      ]

      resolve({
        data: {
          page: 1,
          pageSize: 10,
          total: 4,
          list: data.slice((pagination.value.page - 1) * pagination.value.pageSize, pagination.value.page * pagination.value.pageSize),
        },
      })
    }, 100)
  })
}
</script>

<template>
  <z-crud
    v-model:pagination="pagination"
    v-model:data="tableData"
    v-model:formData="formData"
    v-model:loading="loading"
    :columns="columns"
    :options="options"
    :action="false"
    :search="{ labelWidth: '80px' }"
    :request="request"
  >
    <template #labelSlot>
      <span>Gender</span>
    </template>
    <template #errorSlot>
      <span>Date is required</span>
    </template>
  </z-crud>
</template>

Linkage

Use hide to configure the visibility of form items. When gender male is selected, the birth date filter item will be displayed.

<script lang="ts" setup>
import { ref } from 'vue'

const loading = ref(false)
const formData = ref({
  name: '',
  gender: '',
  time: [],
})
const tableData = ref([])
const visible = ref(false)

const columns = ref([
  {
    prop: 'name',
    label: 'Name',
    search: {
      component: 'input',
      field: 'name',
      label: 'Name',
    },
  },
  {
    prop: 'gender',
    label: 'Gender',
    search: {
      component: 'select',
      field: 'gender',
      label: 'Gender',
    },
  },
  {
    prop: 'age',
    label: 'Age',
    search: {
      component: 'el-date-picker',
      field: 'time',
      label: 'Date',
      hide: () => !visible.value,
      fieldProps: {
        type: 'daterange',
        startPlaceholder: 'Start date',
        endPlaceholder: 'End date',
      },
    },
  },
  {
    prop: 'time',
    label: 'Date',
  },
])

const options = {
  gender: [{ label: 'male', value: 'male' }, { label: 'female', value: 'female' }],
}
const pagination = ref({
  page: 1,
  pageSize: 2,
  total: 4,
})
const request = ref({
  searchApi: mockApi,
})

function mockApi() {
  return new Promise((resolve) => {
    setTimeout(() => {
      const data = [
        {
          name: 'Steven',
          gender: 'male',
          age: 22,
          time: '2020-01-01',
        },
        {
          name: 'Helen',
          gender: 'male',
          age: 12,
          time: '2012-01-01',
        },
        {
          name: 'Nancy',
          gender: 'female',
          age: 18,
          time: '2018-01-01',
        },
        {
          name: 'Jack',
          gender: 'male',
          age: 28,
          time: '2028-01-01',
        },
      ]

      resolve({
        data: {
          page: 1,
          pageSize: 10,
          total: 4,
          list: data.slice((pagination.value.page - 1) * pagination.value.pageSize, pagination.value.page * pagination.value.pageSize),
        },
      })
    }, 100)
  })
}

function handleClick() {
  visible.value = !visible.value
}
</script>

<template>
  <el-button @click="handleClick">
    Toggle visibility
  </el-button>
  <z-crud
    v-model:pagination="pagination"
    v-model:data="tableData"
    v-model:formData="formData"
    v-model:loading="loading"
    :options="options"
    :columns="columns"
    :search="{ labelWidth: '80px' }"
    :action="false"
    :request="request"
  />
</template>

Operation Buttons

Button operations will have search and reset events, with built-in reset and validation operations. Customization is supported.

For details, please refer to the z-filter-form component documentation.

<!-- eslint-disable no-console -->
<script lang="ts" setup>
import { ref } from 'vue'

const loading = ref(false)
const formData = ref({
  name: '',
  gender: '',
  time: [],
})
const tableData = ref([])

const columns = ref([
  {
    prop: 'name',
    label: 'Name',
    search: {
      component: 'input',
      field: 'name',
      label: 'Name',
      required: true,
    },
  },
  {
    prop: 'gender',
    label: 'Gender',
    search: {
      component: 'select',
      field: 'gender',
      label: 'Gender',
      required: true,
    },
  },
  {
    prop: 'age',
    label: 'Age',
    search: {
      component: 'el-date-picker',
      field: 'time',
      label: 'Date',
      required: true,
      fieldProps: {
        type: 'daterange',
        startPlaceholder: 'Start date',
        endPlaceholder: 'End date',
      },
    },
  },
  {
    prop: 'time',
    label: 'Date',
  },
])

const options = {
  gender: [{ label: 'male', value: 'male' }, { label: 'female', value: 'female' }],
}
const pagination = ref({
  page: 1,
  pageSize: 2,
  total: 4,
})
const request = ref({
  searchApi: mockApi,
})

function mockApi() {
  return new Promise((resolve) => {
    setTimeout(() => {
      const data = [
        {
          name: 'Steven',
          gender: 'male',
          age: 22,
          time: '2020-01-01',
        },
        {
          name: 'Helen',
          gender: 'male',
          age: 12,
          time: '2012-01-01',
        },
        {
          name: 'Nancy',
          gender: 'female',
          age: 18,
          time: '2018-01-01',
        },
        {
          name: 'Jack',
          gender: 'male',
          age: 28,
          time: '2028-01-01',
        },
      ]

      resolve({
        data: {
          page: 1,
          pageSize: 10,
          total: 4,
          list: data.slice((pagination.value.page - 1) * pagination.value.pageSize, pagination.value.page * pagination.value.pageSize),
        },
      })
    }, 100)
  })
}

function handleSearch() {
  console.log(formData.value, 'formData')
}

function handleReset() {
  console.log(formData.value, 'formData')
}
</script>

<template>
  <z-crud
    v-model:pagination="pagination"
    v-model:data="tableData"
    v-model:formData="formData"
    v-model:loading="loading"
    :columns="columns"
    :options="options"
    :search="{ labelWidth: '80px' }"
    :action="false"
    :request="request"
    @search="handleSearch"
    @reset="handleReset"
  />
</template>

Default Collapsed

Configure collapsed to false in search, and the form will be collapsed by default.

<script lang="ts" setup>
import { ref } from 'vue'

const loading = ref(false)
const formData = ref({
  name: '',
  gender: '',
  time: [],
})
const tableData = ref([])

const columns = ref([
  {
    prop: 'name',
    label: 'Name',
    search: {
      component: 'input',
      field: 'name',
      label: 'Name',
    },
  },
  {
    prop: 'gender',
    label: 'Gender',
    search: {
      component: 'select',
      field: 'gender',
      label: 'Gender',
    },
  },
  {
    prop: 'age',
    label: 'Age',
    search: {
      component: 'el-date-picker',
      field: 'time',
      label: 'Date',
      fieldProps: {
        type: 'daterange',
        startPlaceholder: 'Start date',
        endPlaceholder: 'End date',
      },
    },
  },
  {
    prop: 'time',
    label: 'Date',
  },
])

const options = {
  gender: [{ label: 'male', value: 'male' }, { label: 'female', value: 'female' }],
}
const pagination = ref({
  page: 1,
  pageSize: 2,
  total: 4,
})
const request = ref({
  searchApi: mockApi,
})

function mockApi() {
  return new Promise((resolve) => {
    setTimeout(() => {
      const data = [
        {
          name: 'Steven',
          gender: 'male',
          age: 22,
          time: '2020-01-01',
        },
        {
          name: 'Helen',
          gender: 'male',
          age: 12,
          time: '2012-01-01',
        },
        {
          name: 'Nancy',
          gender: 'female',
          age: 18,
          time: '2018-01-01',
        },
        {
          name: 'Jack',
          gender: 'male',
          age: 28,
          time: '2028-01-01',
        },
      ]

      resolve({
        data: {
          page: 1,
          pageSize: 10,
          total: 4,
          list: data.slice((pagination.value.page - 1) * pagination.value.pageSize, pagination.value.page * pagination.value.pageSize),
        },
      })
    }, 100)
  })
}
</script>

<template>
  <z-crud
    v-model:pagination="pagination"
    v-model:data="tableData"
    v-model:formData="formData"
    v-model:loading="loading"
    :columns="columns"
    :options="options"
    :search="{ labelWidth: '80px', collapsed: false }"
    :action="false"
    :request="request"
  />
</template>

Decorator Components

Configure formDecorator and crudDecorator to set the background of the filter form and table. The name field can be tags such as div, span, or the name of a globally registered component.

<!-- eslint-disable no-console -->
<script lang="ts" setup>
import { ref } from 'vue'

interface RowData {
  name: string
  gender: string
  age: number
  time: string
}

interface GetTableDataRes { result: { page: number, pageSize: number, list: RowData[], total: number } }

const loading = ref(false)
const formData = ref({
  name: '',
  gender: '',
  age: '',
})
const tableData = ref<RowData[]>([])

const columns = ref([
  {
    prop: 'name',
    label: 'Name',
    search: {
      component: 'input',
      label: 'Name',
      field: 'name',
    },
  },
  {
    prop: 'gender',
    label: 'Gender',
    search: {
      component: 'select',
      label: 'Gender',
      field: 'gender',
    },
  },
  {
    prop: 'age',
    label: 'Age',
    search: {
      component: 'input',
      label: 'Age',
      field: 'age',
    },
  },
  {
    prop: 'time',
    label: 'Date',
  },
])

const options = {
  gender: [{ label: 'male', value: 'male' }, { label: 'female', value: 'female' }],
}
const pagination = ref({
  page: 1,
  pageSize: 2,
  total: 4,
})

function mockApi(params: any): Promise<GetTableDataRes> {
  console.log(params, 'params')
  return new Promise((resolve) => {
    setTimeout(() => {
      const data = [
        {
          name: 'Steven',
          gender: 'male',
          age: 22,
          time: '2020-01-01',
        },
        {
          name: 'Helen',
          gender: 'male',
          age: 12,
          time: '2012-01-01',
        },
        {
          name: 'Nancy',
          gender: 'female',
          age: 18,
          time: '2018-01-01',
        },
        {
          name: 'Jack',
          gender: 'male',
          age: 28,
          time: '2028-01-01',
        },
      ]

      resolve({
        result: {
          page: 1,
          pageSize: 10,
          total: 4,
          list: data.slice((pagination.value.page - 1) * pagination.value.pageSize, pagination.value.page * pagination.value.pageSize),
        },
      })
    }, 100)
  })
}

async function getTableData() {
  loading.value = true
  try {
    const params = {
      ...pagination.value,
      ...formData.value,
    }
    const res = await mockApi(params)
    tableData.value = res.result.list
    pagination.value.total = res.result.total
  }
  catch (error) {
    console.log(error)
  }
  loading.value = false
}

function handleSearch() {
  pagination.value.page = 1
  getTableData()
}

getTableData()
</script>

<template>
  <z-crud
    v-model:pagination="pagination"
    v-model:data="tableData"
    v-model:formData="formData"
    :columns="columns"
    :options="options"
    :loading="loading"
    :action="false"
    :form-decorator="{ name: 'div', style: { border: '1px solid #e4e7ed', paddingTop: '18px' } }"
    :table-decorator="{ name: 'el-card', shadow: 'hover' }"
    @refresh="getTableData"
    @search="handleSearch"
    @reset="handleSearch"
  />
</template>
AttributeDescriptionTypeDefault
modelValue:formDataQuery form dataobject
detailDetail configurationboolean / object / ({ row, tableRef }) => voidtrue
formConfiguration of query, add, edit and view form attributesobject
actionWhether to display action items (built-in delete, edit and other buttons)booleantrue
editEdit configurationboolean / objecttrue
addAdd configurationboolean / objecttrue
deleteDelete configurationboolean / ({ row, tableRef, getTableData }) => void / object
searchSearch configurationboolean / objecttrue
requestAPI configurationobject
paginationStoragePagination cachingbooleanfalse
formStorageQuery form data cachingbooleanfalse
formDecoratorForm backgroundobject{ name: 'el-card' }
tableDecoratorTable backgroundobject{ name: 'el-card' }

search Attributes

AttributeDescriptionTypeDefault
collapsedDefault expand/collapse of the formbooleantrue
searchButtonPropsSearch button attribute configurationobject
searchButtonLabelSearch button textstringSearch
searchButtonLoadingSearch button loading stateboolean
resetButtonPropsReset button attribute configurationobject
resetButtonLabelReset button textstringReset
resetButtonLoadingReset button loading stateboolean
rulesForm validation rulesobject
label-positionPosition of the form field label. When set to left or right, you also need to set label-widthenumright
label-widthLength of the label, e.g. '50px'. Form-items that are direct children of Form inherit this value. auto can be used.string / number''
label-suffixSuffix of the form field labelstring''
hide-required-asteriskWhether to hide the red asterisk next to required field labelsbooleanfalse
require-asterisk-positionPosition of the asteriskleft / rightleft
show-messageWhether to display validation error messagesbooleantrue
inline-messageWhether to display validation messages inlinebooleanfalse
status-iconWhether to display validation result feedback icons in the input boxbooleanfalse
validate-on-rule-changeWhether to trigger a validation immediately after the rules attribute changesbooleantrue
sizeSize used to control the components within this formlarge / default / small
disabledWhether to disable all components in this form. If set to true, it overrides internal components' disabledbooleanfalse

z-crud Query Form Methods

NameDescriptionType
validateValidate the entire form. Accepts a callback function or returns a Promise.Function
validateFieldValidate a specific field.Function
resetFieldsReset this form item, restore its value to the initial value, and remove the validation resultFunction
scrollToFieldScroll to the specified fieldFunction
clearValidateClear the validation information of a certain field.Function

Form Item Attributes

AttributeDescriptionTypeDefault
componentForm item componentstring / () => VNode / Component
fieldField namestring
fieldPropscomponent attributesobject
formItemPropsformItem attributesobject
labelForm label namestring / () => VNode
hideShow / hideboolean / (formData) => boolean
showShow / hide using v-showboolean / (formData) => boolean
slotCustom content slot of itemstring
renderCustom content render of item() => VNode
requiredWhether the field is requiredboolean
rulesValidation rules of the fieldboolean
errorError messagestring / () => VNode
tooltipTooltip messagestring / () => VNode
extraExtra informationstring / () => VNode

formItemProps Attributes

AttributeDescriptionTypeDefault
tooltipTooltip textstring / () => VNode
extraExtra informationstring / () => VNode
colonColonboolean
labelLabel textstring / () => VNode
label-widthLabel width, e.g. '50px'. auto can be used.string / number''
requiredWhether it is required. If not set, it will be confirmed according to the validation rulesboolean
rulesForm validation rules. See table below; more at async-validatorobject
errorPrompt information when the form field validation fails. Setting this value will cause the form validation state to become error and display this error message.string / () => VNode
show-messageWhether to display validation error messagesbooleantrue
inline-messageWhether to display validation messages inlinestring / boolean''
sizeDefault size of components under this form fieldlarge / default / small
forSame capability as the native labelstring
validate-statusValidation status of formItemerror / validating / success
Event NameDescriptionType
update:formDataForm item dataFunction
resetResetFunction
searchSearchFunction

Released under the MIT License.