基础使用

姓名
性别
Male
Female
生日
Aug   1958
Sun
Mon
Tue
Wed
Thu
Fri
Sat
27
28
29
30
31
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
1
2
3
4
5
6
行业
IT
Medical
Entertainment
Transportation
<script setup>
import { ref } from 'vue'

const formData = ref({
  name: 'Micheal Jackson',
  gender: 'male',
  birthday: new Date('August 29, 1958'),
  industry: 'Entertainment'
})
const genderOptions = [
  { label: 'Male', value: 'male' },
  { label: 'Female', value: 'female' },
]
const industryOptions = [
  { label: 'IT', value: 'IT' },
  { label: 'Medical', value: 'Medical' },
  { label: 'Entertainment', value: 'Entertainment' },
  { label: 'Transportation', value: 'Transportation' }
]
</script>

<template>
  <c-form>
    <c-form-item label="姓名">
      <c-input v-model="formData.name" />
    </c-form-item>
    <c-form-item label="性别">
      <c-radio-group v-model="formData.gender" :options="genderOptions" />
    </c-form-item>
    <c-form-item label="生日">
      <c-date-picker v-model="formData.birthday" format="MMM DD, YYYY" />
    </c-form-item>
    <c-form-item label="行业">
      <c-select v-model="formData.industry" :options="industryOptions" />
    </c-form-item>
  </c-form>
</template>
点击展开/收起代码
点击打开交互式编辑器

配置式

注意

如果使用配置式,请确保全局使用了CasualUI,或者你也可以全局手动注册所有的表单相关组件

姓名
性别
Female
Male
生日
Aug   1958
Sun
Mon
Tue
Wed
Thu
Fri
Sat
27
28
29
30
31
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
1
2
3
4
5
6
行业
IT
Medical
Entertainment
Transportation
<script setup>
import { ref } from 'vue'
const formData = ref({
  name: 'Micheal Jackson',
  gender: 'male',
  birthday: new Date('August 29, 1958'),
  industry: 'Entertainment'
})

const formItems = [
  { field: 'name', label: '姓名' },
  {
    label: '性别',
    field: 'gender',
    component: 'radio-group',
    componentProps: {
      options: [
        { label: 'Female', value: 'female' },
        { label: 'Male', value: 'male' },
      ]
    }
  },
  {
    label: '生日',
    field: 'birthday',
    component: 'date-picker',
    componentProps: {
      format: 'MMM DD, YYYY'
    }
  },
  {
    label: '行业',
    field: 'industry',
    component: 'select',
    componentProps: {
      options: [
        { label: 'IT', value: 'IT' },
        { label: 'Medical', value: 'Medical' },
        { label: 'Entertainment', value: 'Entertainment' },
        { label: 'Transportation', value: 'Transportation' }
      ]
    }
  }
]
</script>

<template>
  <c-form v-model="formData" :items="formItems" />
</template>
点击展开/收起代码
点击打开交互式编辑器

尺寸

xs
sm
md
lg
xl
姓名
性别
Female
Male
生日
Aug   1958
Sun
Mon
Tue
Wed
Thu
Fri
Sat
27
28
29
30
31
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
1
2
3
4
5
6
行业
IT
Medical
Entertainment
Transportation
<script setup>
import { ref } from 'vue'
const formData = ref({
  name: 'Micheal Jackson',
  gender: 'male',
  birthday: new Date('August 29, 1958'),
  industry: 'Entertainment'
})

const formItems = [
  { field: 'name', label: '姓名' },
  {
    label: '性别',
    field: 'gender',
    component: 'radio-group',
    componentProps: {
      options: [
        { label: 'Female', value: 'female' },
        { label: 'Male', value: 'male' },
      ]
    }
  },
  {
    label: '生日',
    field: 'birthday',
    component: 'date-picker',
    componentProps: {
      format: 'MMM DD, YYYY'
    }
  },
  {
    label: '行业',
    field: 'industry',
    component: 'select',
    componentProps: {
      options: [
        { label: 'IT', value: 'IT' },
        { label: 'Medical', value: 'Medical' },
        { label: 'Entertainment', value: 'Entertainment' },
        { label: 'Transportation', value: 'Transportation' }
      ]
    }
  }
]
const size = ref('md')
const sizes = ['xs', 'sm', 'md', 'lg', 'xl']
</script>

<template>
  <div class="c-row c-gutter-md c-items-center">
    <c-radio
      v-for="s in sizes"
      :key="s"
      v-model="size"
      :size="s"
      :label="s"
      :value="s"
    />
  </div>
  <c-form v-model="formData" :items="formItems" :size="size" />
</template>
点击展开/收起代码
点击打开交互式编辑器

表单项间隔尺寸

xs
sm
md
lg
xl
姓名
性别
Female
Male
生日
Aug   1958
Sun
Mon
Tue
Wed
Thu
Fri
Sat
27
28
29
30
31
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
1
2
3
4
5
6
行业
IT
Medical
Entertainment
Transportation
<script setup>
import { ref } from 'vue'
const formData = ref({
  name: 'Micheal Jackson',
  gender: 'male',
  birthday: new Date('August 29, 1958'),
  industry: 'Entertainment'
})

const formItems = [
  { field: 'name', label: '姓名' },
  {
    label: '性别',
    field: 'gender',
    component: 'radio-group',
    componentProps: {
      options: [
        { label: 'Female', value: 'female' },
        { label: 'Male', value: 'male' },
      ]
    }
  },
  {
    label: '生日',
    field: 'birthday',
    component: 'date-picker',
    componentProps: {
      format: 'MMM DD, YYYY'
    }
  },
  {
    label: '行业',
    field: 'industry',
    component: 'select',
    componentProps: {
      options: [
        { label: 'IT', value: 'IT' },
        { label: 'Medical', value: 'Medical' },
        { label: 'Entertainment', value: 'Entertainment' },
        { label: 'Transportation', value: 'Transportation' }
      ]
    }
  }
]
const size = ref('md')
const sizes = ['xs', 'sm', 'md', 'lg', 'xl']
</script>

<template>
  <div class="c-row c-gutter-md c-items-center">
    <c-radio
      v-for="s in sizes"
      :key="s"
      v-model="size"
      :size="s"
      :label="s"
      :value="s"
    />
  </div>
  <c-form v-model="formData" :items="formItems" :gutter-size="size" />
</template>
点击展开/收起代码
点击打开交互式编辑器

label排列方向、宽度、对齐方式

编辑下方的label相关属性,查看实时效果

label宽度
label方向
row
row-reverse
column
column-reverse
label对齐方式
left
center
right
姓名
性别
Female
Male
生日
Aug   1958
Sun
Mon
Tue
Wed
Thu
Fri
Sat
27
28
29
30
31
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
1
2
3
4
5
6
行业
IT
Medical
Entertainment
Transportation
<script setup>
import { ref } from 'vue'
const formData = ref({
  labelWidth: 100,
  labelDirection: 'row',
  labelAlign: 'left',
  name: 'Micheal Jackson',
  gender: 'male',
  birthday: new Date('August 29, 1958'),
  industry: 'Entertainment'
})

const formItems = [
  {
    field: 'labelWidth',
    label: 'label宽度',
    col: 12
  },
  {
    field: 'labelDirection',
    label: 'label方向',
    col: 12,
    component: 'radio-group',
    componentProps: {
      options: [
        { label: 'row', value: 'row' },
        { label: 'row-reverse', value: 'row-reverse' },
        { label: 'column', value: 'column' },
        { label: 'column-reverse', value: 'column-reverse' },
      ]
    }
  },
  {
    field: 'labelAlign',
    label: 'label对齐方式',
    col: 12,
    component: 'radio-group',
    componentProps: {
      options: [
        { label: 'left', value: 'left' },
        { label: 'center', value: 'center' },
        { label: 'right', value: 'right' },
      ]
    }
  },
  { field: 'name', label: '姓名' },
  {
    label: '性别',
    field: 'gender',
    component: 'radio-group',
    componentProps: {
      options: [
        { label: 'Female', value: 'female' },
        { label: 'Male', value: 'male' },
      ]
    }
  },
  {
    label: '生日',
    field: 'birthday',
    component: 'date-picker',
    componentProps: {
      format: 'MMM DD, YYYY'
    }
  },
  {
    label: '行业',
    field: 'industry',
    component: 'select',
    componentProps: {
      options: [
        { label: 'IT', value: 'IT' },
        { label: 'Medical', value: 'Medical' },
        { label: 'Entertainment', value: 'Entertainment' },
        { label: 'Transportation', value: 'Transportation' }
      ]
    }
  }
]
</script>

<template>
  <c-form
    v-model="formData"
    :items="formItems"
    :label-direction="formData.labelDirection"
    :label-width="`${formData.labelWidth}px`"
    :label-align="formData.labelAlign"
  />
</template>
点击展开/收起代码
点击打开交互式编辑器

自定义项列宽

2
3
4
6
12
姓名
性别
Female
Male
生日
Aug   1958
Sun
Mon
Tue
Wed
Thu
Fri
Sat
27
28
29
30
31
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
1
2
3
4
5
6
行业
IT
Medical
Entertainment
Transportation
勾选即同意XXX协议
<script setup>
import { ref } from 'vue'
const formData = ref({
  name: 'Micheal Jackson',
  gender: 'male',
  birthday: new Date('August 29, 1958'),
  industry: 'Entertainment',
  agree: false,
})

const formItems = [
  { field: 'name', label: '姓名' },
  {
    label: '性别',
    field: 'gender',
    component: 'radio-group',
    componentProps: {
      options: [
        { label: 'Female', value: 'female' },
        { label: 'Male', value: 'male' },
      ]
    }
  },
  {
    label: '生日',
    field: 'birthday',
    component: 'date-picker',
    componentProps: {
      format: 'MMM DD, YYYY'
    }
  },
  {
    label: '行业',
    field: 'industry',
    component: 'select',
    componentProps: {
      options: [
        { label: 'IT', value: 'IT' },
        { label: 'Medical', value: 'Medical' },
        { label: 'Entertainment', value: 'Entertainment' },
        { label: 'Transportation', value: 'Transportation' },
      ]
    }
  },
  {
    field: 'agree',
    component: 'checkbox',
    col: 12,
    componentProps: {
      label: '勾选即同意XXX协议',
    },
  }
]
const col = ref(6)
const spans = [2, 3, 4, 6, 12]
</script>

<template>
  <div class="c-row c-gutter-md c-items-center">
    <c-radio
      v-for="s in spans"
      :key="s"
      v-model="col"
      :label="s"
      :value="s"
    />
  </div>
  <c-form v-model="formData" :items="formItems" :col="col" />
</template>
点击展开/收起代码
点击打开交互式编辑器

表单验证 & 异步验证 & 验证状态

Casual UI 的表单验证,需要配合fieldrules属性
rules为验证函数数组,每个验证函数为一个函数,该函数接受当前表单项对应值,返回:
false | string | Promise<false | string>,返回含义如下:

  • 返回false则代表验证通过无错误
  • string则代表有错误,并且返回值为具体的错误信息
  • 返回Promise则代表异步验证,内容也是false或者具体的string类型错误信息

假设你想定义一个验证是否必填的验证规则,可以这样写:

const rule = v => (v ? false : '该项是必填的')

它的等价异步逻辑大概是这样:

const asyncRule = v =>
  new Promise((resolve) => {
    setTimeout(() => {
      resolve(v ? false : '该项是必填的')
    }, 1000)
  })

同时,在下面的示例中:
爱好(hobbies)一栏,具有异步验证规则

姓名
性别
Female
Male
生日
Jan   2023
Sun
Mon
Tue
Wed
Thu
Fri
Sat
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
1
2
3
4
5
6
7
8
9
10
11
行业
IT
Medical
Entertainment
Transportation
爱好
旅游
健身
美食
摇滚
肝剧
<script setup>
import { ref } from 'vue'
import { useNotifications } from '@casual-ui/vue'

const formData = ref({
  name: '',
  gender: '',
  birthday: null,
  industry: '',
  hobbies: []
})
const validating = ref(false)
const formItems = [
  {
    field: 'name',
    label: '姓名',
    rules: [
      v => !v ? '请输入姓名' : false
    ]
  },
  {
    label: '性别',
    field: 'gender',
    component: 'radio-group',
    componentProps: {
      options: [
        { label: 'Female', value: 'female' },
        { label: 'Male', value: 'male' },
      ]
    },
    rules: [
      v => v !== 'male' ? '只能选择Male!' : false
    ]
  },
  {
    label: '生日',
    field: 'birthday',
    component: 'date-picker',
    componentProps: {
      format: 'MMM DD, YYYY'
    },
    rules: [
      v => !v ? '请选择日期' : false
    ]
  },
  {
    label: '行业',
    field: 'industry',
    component: 'select',
    componentProps: {
      options: [
        { label: 'IT', value: 'IT' },
        { label: 'Medical', value: 'Medical' },
        { label: 'Entertainment', value: 'Entertainment' },
        { label: 'Transportation', value: 'Transportation' }
      ]
    },
    rules: [
      v => v !== 'IT' && v !== 'Entertainment' ? '只能选择IT或者Entertainment' : false
    ]
  },
  {
    label: '爱好',
    field: 'hobbies',
    component: 'checkbox-group',
    col: 12,
    componentProps: {
      options: [
        { label: '旅游', value: 'Travel' },
        { label: '健身', value: 'Work out' },
        { label: '美食', value: 'Food' },
        { label: '摇滚', value: 'Rock' },
        { label: '肝剧', value: 'TV Series' },
      ]
    },
    rules: [
      // 异步验证
      v => new Promise((resolve) => {
        setTimeout(() => {
          resolve(v.length < 2 ? '请至少选择两项' : false)
        }, 3000)
      })
    ]
  }
]

const { open } = useNotifications()
const form = ref(null)
const doValidate = () => {
  form.value?.validate()
    .then(() => {
      open({
        title: '提示',
        message: '验证通过!'
      })
    })
}
const clearValidate = () => {
  form.value?.clearAll()
}
</script>

<template>
  <c-form
    ref="form"
    v-model="formData"
    v-model:validating="validating"
    :items="formItems"
    class="c-pa-md"
  />
  <div class="c-mt-xl">
    <c-button
      label="重置"
      outlined
      :loading="validating"
      class="c-mr-md"
      @click="clearValidate"
    />
    <c-button label="提交" :loading="validating" @click="doValidate" />
  </div>
</template>
点击展开/收起代码
点击打开交互式编辑器

自定义表单项

通过#[field]插槽来自定义某个表单项,并且通过{ validate, clearValidate, hasError }来自定义触发验证、清除验证的调用时机,以及错误状态的样式表现

姓名
性别
Female
Male
生日
Jan   2023
Sun
Mon
Tue
Wed
Thu
Fri
Sat
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
1
2
3
4
5
6
7
8
9
10
11
行业
IT
Medical
Entertainment
Transportation
自定义项
<script setup>
import { ref } from 'vue'
import { useNotifications } from '@casual-ui/vue'
const formData = ref({
  name: '',
  gender: '',
  birthday: null,
  industry: '',
  customField: ''
})

const formItems = [
  {
    field: 'name',
    label: '姓名',
    rules: [
      v => !v ? '请输入姓名' : false
    ]
  },
  {
    label: '性别',
    field: 'gender',
    component: 'radio-group',
    componentProps: {
      options: [
        { label: 'Female', value: 'female' },
        { label: 'Male', value: 'male' },
      ]
    },
    rules: [
      v => v !== 'male' ? '只能选择Male!' : false
    ]
  },
  {
    label: '生日',
    field: 'birthday',
    component: 'date-picker',
    componentProps: {
      format: 'MMM DD, YYYY'
    },
    rules: [
      v => !v ? '请选择日期' : false
    ]
  },
  {
    label: '行业',
    field: 'industry',
    component: 'select',
    componentProps: {
      options: [
        { label: 'IT', value: 'IT' },
        { label: 'Medical', value: 'Medical' },
        { label: 'Entertainment', value: 'Entertainment' },
        { label: 'Transportation', value: 'Transportation' }
      ]
    },
    rules: [
      v => v !== 'IT' && v !== 'Entertainment' ? '只能选择IT或者Entertainment' : false
    ]
  },
  {
    label: '自定义项',
    field: 'customField',
    rules: [
      v => v === 'custom value' ? false : '只能输入“custom value”'
    ]
  }
]
const { open } = useNotifications()
const form = ref(null)
const doValidate = () => {
  form.value?.validate()
    .then(() => {
      open({
        title: '提示',
        message: '验证通过!'
      })
    })
}
const clearAll = () => {
  form.value?.clearAll()
}
const validating = ref(false)
</script>

<template>
  <c-form
    ref="form"
    v-model="formData"
    v-model:validating="validating"
    :items="formItems"
    class="c-pa-md"
  >
    <template #customField="{ validate, clearValidate, hasError }">
      <input
        v-model="formData.customField"
        :style="{ color: hasError ? 'red' : 'inherit' }"
        @blur="validate"
        @focus="clearValidate"
      >
    </template>
  </c-form>
  <div class="c-mt-xl">
    <c-button
      label="重置"
      outlined
      :loading="validating"
      class="c-mr-md"
      @click="clearAll"
    />
    <c-button label="提交" :loading="validating" @click="doValidate" />
  </div>
</template>
点击展开/收起代码
点击打开交互式编辑器

自定义表单组件高级

自写组件,可以使用useValidator获取当前表单项所处表单验证上下文,从而自定义验证方法、清除验证状态调用时机,以及错误状态的样式表现

CustomInput.vue
使用
<script setup lang="ts">
import { useDefaultVModel, useValidator } from '@casual-ui/vue'

const props = defineProps<{
  modelValue: string
}>()

const emit = defineEmits<{
  (e: 'update:modelValue', newValue: string): void
}>()

const innerValue = useDefaultVModel(props, emit)

const { validate, clearValidate, hasError } = useValidator()
</script>

<template>
  <div class="custom-input" :class="[{ 'custom-input--has-error': hasError }]">
    <input
      v-model="innerValue"
      @focus="clearValidate"
      @blur="validate(innerValue)"
    >
  </div>
</template>

<style lang="scss" scoped>
.custom-input {
  input {
    outline: none;
    border: 1px solid var(--casual-copywriting-normal);
  }
  &--has-error {
    input {
      color: var(--casual-warning);
      border-color: var(--casual-warning);
    }
  }
}
</style>

提示

表单项的所有与表单整体同名的配置可以覆盖表单整体的配置

CForm API

Props
Slots
Events
items
Array<CFormItemConfig>
默认值  () => []

表单项配置

modelValue
Record
默认值  () => ({})

表单绑定值,用于v-model

labelWidth
string
默认值  '100px'

表单项提示文案长度

col
number
默认值  6

每个表单项占用的列数

labelDirection
LabelDirection
默认值  'row'

文本排列方向,表现为flex-direction的对应值

labelAlign
"left" | "center" | "right"
默认值  'left'

文字对齐方式,表现为text-align的对应值

size
CSize
默认值  'md'

尺寸

gutterSize
CSize
默认值  'md'

表单项间隔尺寸

validating
boolean
默认值  false

是否在验证中,可用于v-model:validating绑定,在具有异步验证规则时可能会需要这个属性用作一些状态展示

CFormItem API

Props
Slots
Events
field
string
默认值  ''

对应表单中的项的名称

label
string
默认值  ''

文本提示

labelWidth
string
默认值  '100px'

文本提示宽度

col
number
默认值  6

表单项占用的列数,可覆盖表单整体的col属性,可用于为项定制列宽

size
CSize
默认值  'md'

尺寸

rules
Array<CRule>
默认值  () => []

验证规则

labelDirection
LabelDirection
默认值  'row'

提示文字与表单项排列方向,可覆盖表单整体的label-direction属性

labelAlign
"left" | "center" | "right"
默认值  'left'

文字对齐方式,表现为text-align的对应值,可覆盖表单整体的label-align属性