介绍
用于数据采集,由各种类型的表单域组成,可对数据进行校验、清除、重置、提交等操作。
代码演示
典型表单
最基础的表单包括各种输入表单项,比如 input、picker-input、radio、checkbox 等。
在每一个 form 组件中,你需要一个 form-item 字段作为输入项的容器,用于获取值与验证值。
<template>
<doc-page gray title="典型表单">
<s-form :model="form" label-width="120px">
<s-form-item label="Activity name">
<s-input inlaid v-model="form.name" placeholder="Please input Activity name" />
</s-form-item>
<s-form-item label="Activity zone">
<s-picker-input
v-model="form.region"
placeholder="please select your zone"
title="please select your zone"
:columns="[
{
label: 'Zone one',
value: 'shanghai',
},
{
label: 'Zone two',
value: 'beijing',
},
]"
/>
</s-form-item>
<s-form-item label="Activity time">
<s-datetime-picker-input v-model="form.date1" type="yMd" placeholder="Pick a date" />
</s-form-item>
<s-form-item label="">
<s-datetime-picker-input v-model="form.date2" type="hms" placeholder="Pick a time" />
</s-form-item>
<s-form-item label="Active date range">
<s-datetime-range-picker-input
v-model="form.date3"
type="hms"
:tabs="['start', 'end']"
placeholder="Pick a time"
/>
</s-form-item>
<s-form-item label="Instant delivery">
<s-switch v-model="form.delivery" />
</s-form-item>
<s-form-item label="Activity type">
<s-checkbox-input
v-model="form.type"
placeholder="Pick Activity type"
:options="[
{ label: 'Online activities', value: 'Online activities' },
{ label: 'Promotion activities', value: 'Promotion activities' },
{ label: 'Offline activities', value: 'Offline activities' },
{ label: 'Simple brand exposure', value: 'Simple brand exposure' },
]"
/>
</s-form-item>
<s-form-item label="Resources">
<s-radio-input
v-model="form.resource"
placeholder="Pick Resources"
:options="[
{ label: 'Sponsor', value: 'Sponsor' },
{ label: 'Venue', value: 'Venue' },
]"
/>
</s-form-item>
<s-form-item label="Activity form" label-valign="start">
<s-input
inlaid
v-model="form.desc"
type="textarea"
placeholder="Please input Activity form"
show-count
/>
</s-form-item>
<s-form-item>
<s-button @click="onSubmit">Create</s-button>
<s-button style="margin-top: 10px" variant="outlined">Cancel</s-button>
</s-form-item>
</s-form>
</doc-page>
</template>
<script setup lang="ts">
import { reactive } from 'vue'
import { toast } from 'sard'
// do not use same name with ref
const form = reactive({
name: '',
region: '',
date1: undefined,
date2: undefined,
date3: undefined,
delivery: false,
type: [],
resource: '',
desc: '',
})
const onSubmit = () => {
toast('submit!')
}
</script><template>
<doc-page gray title="典型表单">
<s-form :model="form" label-width="120px">
<s-form-item label="Activity name">
<s-input inlaid v-model="form.name" placeholder="Please input Activity name" />
</s-form-item>
<s-form-item label="Activity zone">
<s-picker-input
v-model="form.region"
placeholder="please select your zone"
title="please select your zone"
:columns="[
{
label: 'Zone one',
value: 'shanghai',
},
{
label: 'Zone two',
value: 'beijing',
},
]"
/>
</s-form-item>
<s-form-item label="Activity time">
<s-datetime-picker-input v-model="form.date1" type="yMd" placeholder="Pick a date" />
</s-form-item>
<s-form-item label="">
<s-datetime-picker-input v-model="form.date2" type="hms" placeholder="Pick a time" />
</s-form-item>
<s-form-item label="Active date range">
<s-datetime-range-picker-input
v-model="form.date3"
type="hms"
:tabs="['start', 'end']"
placeholder="Pick a time"
/>
</s-form-item>
<s-form-item label="Instant delivery">
<s-switch v-model="form.delivery" />
</s-form-item>
<s-form-item label="Activity type">
<s-checkbox-input
v-model="form.type"
placeholder="Pick Activity type"
:options="[
{ label: 'Online activities', value: 'Online activities' },
{ label: 'Promotion activities', value: 'Promotion activities' },
{ label: 'Offline activities', value: 'Offline activities' },
{ label: 'Simple brand exposure', value: 'Simple brand exposure' },
]"
/>
</s-form-item>
<s-form-item label="Resources">
<s-radio-input
v-model="form.resource"
placeholder="Pick Resources"
:options="[
{ label: 'Sponsor', value: 'Sponsor' },
{ label: 'Venue', value: 'Venue' },
]"
/>
</s-form-item>
<s-form-item label="Activity form" label-valign="start">
<s-input
inlaid
v-model="form.desc"
type="textarea"
placeholder="Please input Activity form"
show-count
/>
</s-form-item>
<s-form-item>
<s-button @click="onSubmit">Create</s-button>
<s-button style="margin-top: 10px" variant="outlined">Cancel</s-button>
</s-form-item>
</s-form>
</doc-page>
</template>
<script setup lang="js">
import { reactive } from 'vue'
import { toast } from 'sard'
// do not use same name with ref
const form = reactive({
name: '',
region: '',
date1: undefined,
date2: undefined,
date3: undefined,
delivery: false,
type: [],
resource: '',
desc: '',
})
const onSubmit = () => {
toast('submit!')
}
</script>方向与对齐
使用 direction 设置表单域标签水平或垂直排列; 使用 label-align 或 label-valign 设置标签表单域标签水平或垂直方向的对齐方式; 使用 star-position 属性设置星号居左或居右。
<template>
<doc-page gray title="方向与对齐">
<doc-demo>
<doc-title>排列方向</doc-title>
<s-radio-group v-model="direction" direction="horizontal">
<s-radio value="vertical">vertical</s-radio>
<s-radio value="horizontal">horizontal</s-radio>
</s-radio-group>
<doc-title>水平对齐</doc-title>
<s-radio-group v-model="labelAlign" direction="horizontal">
<s-radio value="start">start</s-radio>
<s-radio value="center">center</s-radio>
<s-radio value="end">end</s-radio>
</s-radio-group>
<doc-title>垂直对齐</doc-title>
<s-radio-group v-model="labelValign" direction="horizontal">
<s-radio value="start">start</s-radio>
<s-radio value="center">center</s-radio>
<s-radio value="end">end</s-radio>
</s-radio-group>
<doc-title>星号位置</doc-title>
<s-radio-group v-model="starPosition" direction="horizontal">
<s-radio value="start">start</s-radio>
<s-radio value="end">end</s-radio>
</s-radio-group>
</doc-demo>
<doc-demo>
<s-form
label-width="100px"
:direction="direction"
:label-align="labelAlign"
:label-valign="labelValign"
:star-position="starPosition"
:model="formLabelAlign"
card
>
<s-form-item label="Name" required>
<s-input v-model="formLabelAlign.name" />
</s-form-item>
<s-form-item label="Activity zone" required>
<s-input v-model="formLabelAlign.region" />
</s-form-item>
<s-form-item label="Activity form" required>
<s-input v-model="formLabelAlign.type" type="textarea" />
</s-form-item>
</s-form>
</doc-demo>
</doc-page>
</template>
<script setup lang="ts">
import { reactive, ref } from 'vue'
const labelAlign = ref<any>('start')
const labelValign = ref<any>('center')
const direction = ref<any>('horizontal')
const starPosition = ref<any>('start')
const formLabelAlign = reactive({
name: '',
region: '',
type: '',
})
</script><template>
<doc-page gray title="方向与对齐">
<doc-demo>
<doc-title>排列方向</doc-title>
<s-radio-group v-model="direction" direction="horizontal">
<s-radio value="vertical">vertical</s-radio>
<s-radio value="horizontal">horizontal</s-radio>
</s-radio-group>
<doc-title>水平对齐</doc-title>
<s-radio-group v-model="labelAlign" direction="horizontal">
<s-radio value="start">start</s-radio>
<s-radio value="center">center</s-radio>
<s-radio value="end">end</s-radio>
</s-radio-group>
<doc-title>垂直对齐</doc-title>
<s-radio-group v-model="labelValign" direction="horizontal">
<s-radio value="start">start</s-radio>
<s-radio value="center">center</s-radio>
<s-radio value="end">end</s-radio>
</s-radio-group>
<doc-title>星号位置</doc-title>
<s-radio-group v-model="starPosition" direction="horizontal">
<s-radio value="start">start</s-radio>
<s-radio value="end">end</s-radio>
</s-radio-group>
</doc-demo>
<doc-demo>
<s-form
label-width="100px"
:direction="direction"
:label-align="labelAlign"
:label-valign="labelValign"
:star-position="starPosition"
:model="formLabelAlign"
card
>
<s-form-item label="Name" required>
<s-input v-model="formLabelAlign.name" />
</s-form-item>
<s-form-item label="Activity zone" required>
<s-input v-model="formLabelAlign.region" />
</s-form-item>
<s-form-item label="Activity form" required>
<s-input v-model="formLabelAlign.type" type="textarea" />
</s-form-item>
</s-form>
</doc-demo>
</doc-page>
</template>
<script setup lang="js">
import { reactive, ref } from 'vue'
const labelAlign = ref('start')
const labelValign = ref('center')
const direction = ref('horizontal')
const starPosition = ref('start')
const formLabelAlign = reactive({
name: '',
region: '',
type: '',
})
</script>表单校验
Form 组件允许你验证用户的输入是否符合规范,来帮助你找到和纠正错误。
Form 组件提供了表单验证的功能,只需为 rules 属性传入约定的验证规则,并将 FormItem 的 name 属性设置为需要验证的特殊键值即可。
<template>
<doc-page gray title="表单校验">
<s-form ref="ruleFormRef" :model="ruleForm" :rules="rules" label-width="8em">
<s-form-item label="Activity name" name="name">
<s-input v-model="ruleForm.name" clearable inlaid placeholder="Activity name" />
</s-form-item>
<s-form-item label="Activity zone" name="region">
<s-picker-input
v-model="ruleForm.region"
clearable
placeholder="Activity zone"
:columns="[
{ label: 'Zone one', value: 'shanghai' },
{ label: 'Zone two', value: 'beijing' },
]"
/>
</s-form-item>
<s-form-item label="Activity count" name="count">
<s-picker-input
v-model="ruleForm.count"
clearable
placeholder="Activity count"
:columns="options"
/>
</s-form-item>
<s-form-item label="Activity time" required name="date1">
<s-datetime-picker-input
v-model="ruleForm.date1"
clearable
type="yMd"
placeholder="Pick a date"
/>
</s-form-item>
<s-form-item label="" hide-star name="date2">
<s-datetime-picker-input
v-model="ruleForm.date2"
clearable
type="hms"
placeholder="Pick a time"
/>
</s-form-item>
<s-form-item label="Instant delivery" name="delivery">
<s-switch v-model="ruleForm.delivery" />
</s-form-item>
<s-form-item label="Activity type" name="type">
<s-checkbox-input
v-model="ruleForm.type"
clearable
placeholder="Pick Activity type"
:options="[
{ label: 'Online activities', value: 'Online activities' },
{ label: 'Promotion activities', value: 'Promotion activities' },
{ label: 'Offline activities', value: 'Offline activities' },
{ label: 'Simple brand exposure', value: 'Simple brand exposure' },
]"
/>
</s-form-item>
<s-form-item label="Resources" name="resource">
<s-radio-input
v-model="ruleForm.resource"
clearable
placeholder="Pick Resources"
:options="[
{ label: 'Sponsor', value: 'Sponsor' },
{ label: 'Venue', value: 'Venue' },
]"
/>
</s-form-item>
<s-form-item label="Activity form" name="desc">
<s-input
v-model="ruleForm.desc"
clearable
type="textarea"
inlaid
placeholder="Activity form"
/>
</s-form-item>
<s-form-item>
<s-button @click="submitForm(ruleFormRef)">Create</s-button>
<s-button variant="outlined" @click="resetForm(ruleFormRef)" style="margin-top: 10px">
Reset
</s-button>
</s-form-item>
</s-form>
</doc-page>
</template>
<script setup lang="ts">
import { reactive, ref } from 'vue'
import { toast, type FormRules, type FormExpose, type FieldValidateError } from 'sard'
interface RuleForm {
name: string
region: string
count: string
date1: Date | undefined
date2: Date | undefined
delivery: boolean
type: string[]
resource: string
desc: string
}
const ruleFormRef = ref<FormExpose>()
const ruleForm = reactive<RuleForm>({
name: 'Hello',
region: '',
count: '',
date1: undefined,
date2: undefined,
delivery: false,
type: [],
resource: '',
desc: '',
})
const rules = reactive<FormRules>({
name: [
{ required: true, message: 'Please input Activity name', trigger: 'blur' },
{ min: 3, max: 5, message: 'Length should be 3 to 5', trigger: 'blur' },
],
region: [
{
required: true,
message: 'Please select Activity zone',
trigger: 'change',
},
],
count: [
{
required: true,
message: 'Please select Activity count',
trigger: 'change',
},
],
date1: [
{
type: 'date',
required: true,
message: 'Please pick a date',
trigger: 'change',
},
],
date2: [
{
type: 'date',
required: true,
message: 'Please pick a time',
trigger: 'change',
},
],
type: [
{
type: 'array',
required: true,
message: 'Please select at least one activity type',
trigger: 'change',
},
],
resource: [
{
required: true,
message: 'Please select activity resource',
trigger: 'change',
},
],
desc: [{ required: true, message: 'Please input activity form', trigger: 'blur' }],
})
const submitForm = (formEl?: FormExpose) => {
if (!formEl) {
return
}
formEl
.validate()
.then(() => {
toast('Success!')
console.log('Success!')
})
.catch((error: FieldValidateError[]) => {
console.log('error submit!', error)
})
}
const resetForm = (formEl?: FormExpose) => {
if (!formEl) return
formEl.reset()
}
const options = Array.from({ length: 100 }).map((_, idx) => ({
value: `${idx + 1}`,
label: `${idx + 1}`,
}))
</script><template>
<doc-page gray title="表单校验">
<s-form ref="ruleFormRef" :model="ruleForm" :rules="rules" label-width="8em">
<s-form-item label="Activity name" name="name">
<s-input v-model="ruleForm.name" clearable inlaid placeholder="Activity name" />
</s-form-item>
<s-form-item label="Activity zone" name="region">
<s-picker-input
v-model="ruleForm.region"
clearable
placeholder="Activity zone"
:columns="[
{ label: 'Zone one', value: 'shanghai' },
{ label: 'Zone two', value: 'beijing' },
]"
/>
</s-form-item>
<s-form-item label="Activity count" name="count">
<s-picker-input
v-model="ruleForm.count"
clearable
placeholder="Activity count"
:columns="options"
/>
</s-form-item>
<s-form-item label="Activity time" required name="date1">
<s-datetime-picker-input
v-model="ruleForm.date1"
clearable
type="yMd"
placeholder="Pick a date"
/>
</s-form-item>
<s-form-item label="" hide-star name="date2">
<s-datetime-picker-input
v-model="ruleForm.date2"
clearable
type="hms"
placeholder="Pick a time"
/>
</s-form-item>
<s-form-item label="Instant delivery" name="delivery">
<s-switch v-model="ruleForm.delivery" />
</s-form-item>
<s-form-item label="Activity type" name="type">
<s-checkbox-input
v-model="ruleForm.type"
clearable
placeholder="Pick Activity type"
:options="[
{ label: 'Online activities', value: 'Online activities' },
{ label: 'Promotion activities', value: 'Promotion activities' },
{ label: 'Offline activities', value: 'Offline activities' },
{ label: 'Simple brand exposure', value: 'Simple brand exposure' },
]"
/>
</s-form-item>
<s-form-item label="Resources" name="resource">
<s-radio-input
v-model="ruleForm.resource"
clearable
placeholder="Pick Resources"
:options="[
{ label: 'Sponsor', value: 'Sponsor' },
{ label: 'Venue', value: 'Venue' },
]"
/>
</s-form-item>
<s-form-item label="Activity form" name="desc">
<s-input
v-model="ruleForm.desc"
clearable
type="textarea"
inlaid
placeholder="Activity form"
/>
</s-form-item>
<s-form-item>
<s-button @click="submitForm(ruleFormRef)">Create</s-button>
<s-button variant="outlined" @click="resetForm(ruleFormRef)" style="margin-top: 10px">
Reset
</s-button>
</s-form-item>
</s-form>
</doc-page>
</template>
<script setup lang="js">
import { reactive, ref } from 'vue'
import { toast } from 'sard'
const ruleFormRef = ref()
const ruleForm = reactive({
name: 'Hello',
region: '',
count: '',
date1: undefined,
date2: undefined,
delivery: false,
type: [],
resource: '',
desc: '',
})
const rules = reactive({
name: [
{
required: true,
message: 'Please input Activity name',
trigger: 'blur',
},
{
min: 3,
max: 5,
message: 'Length should be 3 to 5',
trigger: 'blur',
},
],
region: [
{
required: true,
message: 'Please select Activity zone',
trigger: 'change',
},
],
count: [
{
required: true,
message: 'Please select Activity count',
trigger: 'change',
},
],
date1: [
{
type: 'date',
required: true,
message: 'Please pick a date',
trigger: 'change',
},
],
date2: [
{
type: 'date',
required: true,
message: 'Please pick a time',
trigger: 'change',
},
],
type: [
{
type: 'array',
required: true,
message: 'Please select at least one activity type',
trigger: 'change',
},
],
resource: [
{
required: true,
message: 'Please select activity resource',
trigger: 'change',
},
],
desc: [
{
required: true,
message: 'Please input activity form',
trigger: 'blur',
},
],
})
const submitForm = (formEl) => {
if (!formEl) {
return
}
formEl
.validate()
.then(() => {
toast('Success!')
console.log('Success!')
})
.catch((error) => {
console.log('error submit!', error)
})
}
const resetForm = (formEl) => {
if (!formEl) return
formEl.reset()
}
const options = Array.from({ length: 100 }).map((_, idx) => ({
value: `${idx + 1}`,
label: `${idx + 1}`,
}))
</script>弹出框中的表单
可以将表单组件放到弹出框中。
<template>
<doc-page gray title="弹出框中的表单">
<div class="mx-4">
<s-button block @click="showForm = true">显示表单</s-button>
</div>
<s-popout
v-model:visible="showForm"
title="表单弹出框"
:beforeClose="beforeClose"
@confirm="onConfirm"
>
<div class="overflow-y-auto" style="height: 60vh">
<s-form
ref="ruleFormRef"
:model="ruleForm"
:rules="rules"
scroll-to-first-error
:scroll-into-view-options="{
block: 'center',
behavior: 'smooth',
}"
>
<s-form-item label="Activity name" name="name">
<s-input v-model="ruleForm.name" clearable inlaid placeholder="Activity name" />
</s-form-item>
<s-form-item label="Activity zone" name="region">
<s-picker-input
v-model="ruleForm.region"
clearable
placeholder="Activity zone"
:columns="[
{ label: 'Zone one', value: 'shanghai' },
{ label: 'Zone two', value: 'beijing' },
]"
/>
</s-form-item>
<s-form-item label="Activity count" name="count">
<s-picker-input
v-model="ruleForm.count"
clearable
placeholder="Activity count"
:columns="options"
/>
</s-form-item>
<s-form-item label="Activity time" required name="date1">
<s-datetime-picker-input
v-model="ruleForm.date1"
clearable
type="yMd"
placeholder="Pick a date"
/>
</s-form-item>
<s-form-item label="" hide-star name="date2">
<s-datetime-picker-input
v-model="ruleForm.date2"
clearable
type="hms"
placeholder="Pick a time"
/>
</s-form-item>
<s-form-item label="Instant delivery" name="delivery">
<s-switch v-model="ruleForm.delivery" />
</s-form-item>
<s-form-item label="Activity type" name="type">
<s-checkbox-input
v-model="ruleForm.type"
clearable
placeholder="Pick Activity type"
:options="[
{ label: 'Online activities', value: 'Online activities' },
{
label: 'Promotion activities',
value: 'Promotion activities',
},
{ label: 'Offline activities', value: 'Offline activities' },
{
label: 'Simple brand exposure',
value: 'Simple brand exposure',
},
]"
/>
</s-form-item>
<s-form-item label="Resources" name="resource">
<s-radio-input
v-model="ruleForm.resource"
clearable
placeholder="Pick Resources"
:options="[
{ label: 'Sponsor', value: 'Sponsor' },
{ label: 'Venue', value: 'Venue' },
]"
/>
</s-form-item>
<s-form-item label="Activity form" name="desc">
<s-input
v-model="ruleForm.desc"
clearable
type="textarea"
inlaid
placeholder="Activity form"
/>
</s-form-item>
</s-form>
</div>
</s-popout>
</doc-page>
</template>
<script setup lang="ts">
import { type PopoutBeforeClose } from 'sard'
import { reactive, ref } from 'vue'
import { toast, type FormRules, type FormExpose, type FieldValidateError } from 'sard'
const showForm = ref(false)
interface RuleForm {
name: string
region: string
count: string
date1: Date | undefined
date2: Date | undefined
delivery: boolean
type: string[]
resource: string
desc: string
}
const ruleFormRef = ref<FormExpose>()
const ruleForm = reactive<RuleForm>({
name: 'Hello',
region: '',
count: '',
date1: undefined,
date2: undefined,
delivery: false,
type: [],
resource: '',
desc: '',
})
const rules = reactive<FormRules>({
name: [
{ required: true, message: 'Please input Activity name', trigger: 'blur' },
{ min: 3, max: 5, message: 'Length should be 3 to 5', trigger: 'blur' },
],
region: [
{
required: true,
message: 'Please select Activity zone',
trigger: 'change',
},
],
count: [
{
required: true,
message: 'Please select Activity count',
trigger: 'change',
},
],
date1: [
{
type: 'date',
required: true,
message: 'Please pick a date',
trigger: 'change',
},
],
date2: [
{
type: 'date',
required: true,
message: 'Please pick a time',
trigger: 'change',
},
],
type: [
{
type: 'array',
required: true,
message: 'Please select at least one activity type',
trigger: 'change',
},
],
resource: [
{
required: true,
message: 'Please select activity resource',
trigger: 'change',
},
],
desc: [{ required: true, message: 'Please input activity form', trigger: 'blur' }],
})
const submitForm = (formEl?: FormExpose) => {
if (!formEl) {
return
}
formEl
.validate()
.then(() => {
toast('Success!')
console.log('Success!')
})
.catch((error: FieldValidateError[]) => {
console.log('error submit!', error)
})
}
const options = Array.from({ length: 100 }).map((_, idx) => ({
value: `${idx + 1}`,
label: `${idx + 1}`,
}))
const beforeClose: PopoutBeforeClose = (type) => {
if (type === 'confirm') {
return false
}
}
const onConfirm = () => {
submitForm(ruleFormRef.value)
// showForm.value = false
}
</script><template>
<doc-page gray title="弹出框中的表单">
<div class="mx-4">
<s-button block @click="showForm = true">显示表单</s-button>
</div>
<s-popout
v-model:visible="showForm"
title="表单弹出框"
:beforeClose="beforeClose"
@confirm="onConfirm"
>
<div class="overflow-y-auto" style="height: 60vh">
<s-form
ref="ruleFormRef"
:model="ruleForm"
:rules="rules"
scroll-to-first-error
:scroll-into-view-options="{
block: 'center',
behavior: 'smooth',
}"
>
<s-form-item label="Activity name" name="name">
<s-input v-model="ruleForm.name" clearable inlaid placeholder="Activity name" />
</s-form-item>
<s-form-item label="Activity zone" name="region">
<s-picker-input
v-model="ruleForm.region"
clearable
placeholder="Activity zone"
:columns="[
{ label: 'Zone one', value: 'shanghai' },
{ label: 'Zone two', value: 'beijing' },
]"
/>
</s-form-item>
<s-form-item label="Activity count" name="count">
<s-picker-input
v-model="ruleForm.count"
clearable
placeholder="Activity count"
:columns="options"
/>
</s-form-item>
<s-form-item label="Activity time" required name="date1">
<s-datetime-picker-input
v-model="ruleForm.date1"
clearable
type="yMd"
placeholder="Pick a date"
/>
</s-form-item>
<s-form-item label="" hide-star name="date2">
<s-datetime-picker-input
v-model="ruleForm.date2"
clearable
type="hms"
placeholder="Pick a time"
/>
</s-form-item>
<s-form-item label="Instant delivery" name="delivery">
<s-switch v-model="ruleForm.delivery" />
</s-form-item>
<s-form-item label="Activity type" name="type">
<s-checkbox-input
v-model="ruleForm.type"
clearable
placeholder="Pick Activity type"
:options="[
{ label: 'Online activities', value: 'Online activities' },
{
label: 'Promotion activities',
value: 'Promotion activities',
},
{ label: 'Offline activities', value: 'Offline activities' },
{
label: 'Simple brand exposure',
value: 'Simple brand exposure',
},
]"
/>
</s-form-item>
<s-form-item label="Resources" name="resource">
<s-radio-input
v-model="ruleForm.resource"
clearable
placeholder="Pick Resources"
:options="[
{ label: 'Sponsor', value: 'Sponsor' },
{ label: 'Venue', value: 'Venue' },
]"
/>
</s-form-item>
<s-form-item label="Activity form" name="desc">
<s-input
v-model="ruleForm.desc"
clearable
type="textarea"
inlaid
placeholder="Activity form"
/>
</s-form-item>
</s-form>
</div>
</s-popout>
</doc-page>
</template>
<script setup lang="js">
import 'sard'
import { reactive, ref } from 'vue'
import { toast } from 'sard'
const showForm = ref(false)
const ruleFormRef = ref()
const ruleForm = reactive({
name: 'Hello',
region: '',
count: '',
date1: undefined,
date2: undefined,
delivery: false,
type: [],
resource: '',
desc: '',
})
const rules = reactive({
name: [
{
required: true,
message: 'Please input Activity name',
trigger: 'blur',
},
{
min: 3,
max: 5,
message: 'Length should be 3 to 5',
trigger: 'blur',
},
],
region: [
{
required: true,
message: 'Please select Activity zone',
trigger: 'change',
},
],
count: [
{
required: true,
message: 'Please select Activity count',
trigger: 'change',
},
],
date1: [
{
type: 'date',
required: true,
message: 'Please pick a date',
trigger: 'change',
},
],
date2: [
{
type: 'date',
required: true,
message: 'Please pick a time',
trigger: 'change',
},
],
type: [
{
type: 'array',
required: true,
message: 'Please select at least one activity type',
trigger: 'change',
},
],
resource: [
{
required: true,
message: 'Please select activity resource',
trigger: 'change',
},
],
desc: [
{
required: true,
message: 'Please input activity form',
trigger: 'blur',
},
],
})
const submitForm = (formEl) => {
if (!formEl) {
return
}
formEl
.validate()
.then(() => {
toast('Success!')
console.log('Success!')
})
.catch((error) => {
console.log('error submit!', error)
})
}
const options = Array.from({ length: 100 }).map((_, idx) => ({
value: `${idx + 1}`,
label: `${idx + 1}`,
}))
const beforeClose = (type) => {
if (type === 'confirm') {
return false
}
}
const onConfirm = () => {
submitForm(ruleFormRef.value)
// showForm.value = false
}
</script>自定义校验规则
这个例子中展示了如何使用自定义验证规则来完成密码的二次验证。
本例还使用 validate 插槽为输入框添加了表示校验中状态的反馈图标。
<template>
<doc-page gray title="自定义校验规则">
<s-form ref="ruleFormRef" :model="ruleForm" :rules="rules">
<s-form-item label="Password" name="pass">
<s-input v-model="ruleForm.pass" type="password" />
</s-form-item>
<s-form-item label="Confirm" name="checkPass">
<s-input v-model="ruleForm.checkPass" type="password" />
</s-form-item>
<s-form-item label="Age" name="age">
<template #validate="{ state }">
<s-input v-model="ruleForm.age">
<template #append>
<s-loading v-if="state === 'validating'" color="var(--s-text-color-tertiary)" />
</template>
</s-input>
</template>
</s-form-item>
<s-form-item>
<s-button @click="submitForm(ruleFormRef)">Submit</s-button>
<s-button variant="outlined" style="margin-top: 10px" @click="resetForm(ruleFormRef)">
Reset
</s-button>
</s-form-item>
</s-form>
</doc-page>
</template>
<script setup lang="ts">
import { reactive, ref } from 'vue'
import { toast, type FormRules, type FormExpose } from 'sard'
const ruleFormRef = ref<FormExpose>()
const checkAge = ({ value }: { value: any }) => {
return new Promise<void>((resolve, reject) => {
if (!value) {
return reject('Please input the age')
}
setTimeout(() => {
if (!Number.isInteger(Number(value))) {
reject('Please input digits')
} else {
if (value < 18) {
reject('Age must be greater than 18')
} else {
resolve()
}
}
}, 1000)
})
}
const validatePass = ({ value }: { value: any }) => {
if (value === '') {
return 'Please input the password'
} else {
if (ruleForm.checkPass !== '') {
if (!ruleFormRef.value) {
return true
}
ruleFormRef.value.validate(['checkPass']).catch(() => void 0)
}
return true
}
}
const validatePass2 = ({ value }: { value: any }) => {
if (value === '') {
return 'Please input the password again'
} else if (value !== ruleForm.pass) {
return "Two inputs don't match!"
} else {
return true
}
}
const ruleForm = reactive({
pass: '',
checkPass: '',
age: '',
})
const rules = reactive<FormRules>({
pass: [{ validator: validatePass, trigger: 'blur' }],
checkPass: [{ validator: validatePass2, trigger: 'blur' }],
age: [{ validator: checkAge, trigger: 'blur' }],
})
const submitForm = (formEl?: FormExpose) => {
if (!formEl) return
formEl
.validate()
.then(() => {
toast('Success!')
console.log('Success!')
})
.catch(() => {
console.log('error submit!')
})
}
const resetForm = (formEl: any) => {
if (!formEl) return
formEl.reset()
}
</script><template>
<doc-page gray title="自定义校验规则">
<s-form ref="ruleFormRef" :model="ruleForm" :rules="rules">
<s-form-item label="Password" name="pass">
<s-input v-model="ruleForm.pass" type="password" />
</s-form-item>
<s-form-item label="Confirm" name="checkPass">
<s-input v-model="ruleForm.checkPass" type="password" />
</s-form-item>
<s-form-item label="Age" name="age">
<template #validate="{ state }">
<s-input v-model="ruleForm.age">
<template #append>
<s-loading v-if="state === 'validating'" color="var(--s-text-color-tertiary)" />
</template>
</s-input>
</template>
</s-form-item>
<s-form-item>
<s-button @click="submitForm(ruleFormRef)">Submit</s-button>
<s-button variant="outlined" style="margin-top: 10px" @click="resetForm(ruleFormRef)">
Reset
</s-button>
</s-form-item>
</s-form>
</doc-page>
</template>
<script setup lang="js">
import { reactive, ref } from 'vue'
import { toast } from 'sard'
const ruleFormRef = ref()
const checkAge = ({ value }) => {
return new Promise((resolve, reject) => {
if (!value) {
return reject('Please input the age')
}
setTimeout(() => {
if (!Number.isInteger(Number(value))) {
reject('Please input digits')
} else {
if (value < 18) {
reject('Age must be greater than 18')
} else {
resolve()
}
}
}, 1e3)
})
}
const validatePass = ({ value }) => {
if (value === '') {
return 'Please input the password'
} else {
if (ruleForm.checkPass !== '') {
if (!ruleFormRef.value) {
return true
}
ruleFormRef.value.validate(['checkPass']).catch(() => void 0)
}
return true
}
}
const validatePass2 = ({ value }) => {
if (value === '') {
return 'Please input the password again'
} else if (value !== ruleForm.pass) {
return "Two inputs don't match!"
} else {
return true
}
}
const ruleForm = reactive({
pass: '',
checkPass: '',
age: '',
})
const rules = reactive({
pass: [
{
validator: validatePass,
trigger: 'blur',
},
],
checkPass: [
{
validator: validatePass2,
trigger: 'blur',
},
],
age: [
{
validator: checkAge,
trigger: 'blur',
},
],
})
const submitForm = (formEl) => {
if (!formEl) return
formEl
.validate()
.then(() => {
toast('Success!')
console.log('Success!')
})
.catch(() => {
console.log('error submit!')
})
}
const resetForm = (formEl) => {
if (!formEl) return
formEl.reset()
}
</script>添加/删除表单项
除了一次通过表单组件上的所有验证规则外. 您也可以动态地通过验证规则或删除单个表单字段的规则。
<template>
<doc-page gray title="添加/删除表单项">
<s-form ref="formRef" :model="dynamicValidateForm">
<s-form-item
name="email"
label="Email"
:rules="[
{
required: true,
message: 'Please input email address',
trigger: 'blur',
},
{
type: 'email',
message: 'Please input correct email address',
trigger: ['blur', 'change'],
},
]"
>
<s-input v-model="dynamicValidateForm.email" />
</s-form-item>
<s-form-item
v-for="(domain, index) in dynamicValidateForm.domains"
:key="domain.key"
:label="'Domain' + index"
:name="['domains', index, 'value']"
:rules="{
required: true,
message: 'domain can not be null',
}"
>
<div style="display: flex; flex-direction: row; gap: 5px">
<s-input v-model="domain.value" />
<s-button variant="text" color="danger" @click.prevent="removeDomain(domain)" #icon>
<Trash />
</s-button>
</div>
</s-form-item>
<s-form-item>
<s-button @click="submitForm(formRef)">Submit</s-button>
<s-button variant="outlined" style="margin-top: 10px" @click="addDomain">
New domain
</s-button>
<s-button variant="outlined" style="margin-top: 10px" @click="resetForm(formRef)">
Reset
</s-button>
</s-form-item>
</s-form>
</doc-page>
</template>
<script setup lang="ts">
import { reactive, ref } from 'vue'
import { toast, type FormExpose } from 'sard'
import { Trash } from '@sard/icons'
const formRef = ref<FormExpose>()
const dynamicValidateForm = reactive<{
domains: DomainItem[]
email: string
}>({
domains: [
{
key: 1,
value: '',
},
],
email: '',
})
interface DomainItem {
key: number
value: string
}
const removeDomain = (item: DomainItem) => {
const index = dynamicValidateForm.domains.indexOf(item)
if (index !== -1) {
dynamicValidateForm.domains.splice(index, 1)
}
}
const addDomain = () => {
dynamicValidateForm.domains.push({
key: Date.now(),
value: '',
})
}
const submitForm = (formEl?: FormExpose) => {
if (!formEl) return
formEl
.validate()
.then(() => {
toast('Success!')
console.log('Success!')
})
.catch(() => {
console.log('error submit!')
})
}
const resetForm = (formEl?: FormExpose) => {
if (!formEl) return
formEl.reset()
}
</script><template>
<doc-page gray title="添加/删除表单项">
<s-form ref="formRef" :model="dynamicValidateForm">
<s-form-item
name="email"
label="Email"
:rules="[
{
required: true,
message: 'Please input email address',
trigger: 'blur',
},
{
type: 'email',
message: 'Please input correct email address',
trigger: ['blur', 'change'],
},
]"
>
<s-input v-model="dynamicValidateForm.email" />
</s-form-item>
<s-form-item
v-for="(domain, index) in dynamicValidateForm.domains"
:key="domain.key"
:label="'Domain' + index"
:name="['domains', index, 'value']"
:rules="{
required: true,
message: 'domain can not be null',
}"
>
<div style="display: flex; flex-direction: row; gap: 5px">
<s-input v-model="domain.value" />
<s-button variant="text" color="danger" @click.prevent="removeDomain(domain)" #icon>
<Trash />
</s-button>
</div>
</s-form-item>
<s-form-item>
<s-button @click="submitForm(formRef)">Submit</s-button>
<s-button variant="outlined" style="margin-top: 10px" @click="addDomain">
New domain
</s-button>
<s-button variant="outlined" style="margin-top: 10px" @click="resetForm(formRef)">
Reset
</s-button>
</s-form-item>
</s-form>
</doc-page>
</template>
<script setup lang="js">
import { reactive, ref } from 'vue'
import { toast } from 'sard'
import { Trash } from '@sard/icons'
const formRef = ref()
const dynamicValidateForm = reactive({
domains: [
{
key: 1,
value: '',
},
],
email: '',
})
const removeDomain = (item) => {
const index = dynamicValidateForm.domains.indexOf(item)
if (index !== -1) {
dynamicValidateForm.domains.splice(index, 1)
}
}
const addDomain = () => {
dynamicValidateForm.domains.push({
key: Date.now(),
value: '',
})
}
const submitForm = (formEl) => {
if (!formEl) return
formEl
.validate()
.then(() => {
toast('Success!')
console.log('Success!')
})
.catch(() => {
console.log('error submit!')
})
}
const resetForm = (formEl) => {
if (!formEl) return
formEl.reset()
}
</script>简单登录框
基本的表单数据域控制展示,包含布局、初始化、验证、提交。
<template>
<doc-page gray title="简单登录框">
<s-form :model="formState" ref="formRef">
<s-form-item
label="Username"
name="username"
:rules="[{ required: true, message: 'Please input your username!' }]"
>
<s-input v-model="formState.username" />
</s-form-item>
<s-form-item
label="Password"
name="password"
:rules="[{ required: true, message: 'Please input your password!' }]"
>
<s-input type="password" v-model="formState.password" />
</s-form-item>
<s-form-item label="" name="remember">
<s-checkbox v-model:checked="formState.remember">Remember me</s-checkbox>
</s-form-item>
<s-form-item>
<s-button @click="submitForm">Submit</s-button>
</s-form-item>
</s-form>
</doc-page>
</template>
<script setup lang="ts">
import { ref, reactive, toRaw } from 'vue'
import { toast, type FormExpose, type FieldValidateError } from 'sard'
const formRef = ref<FormExpose>()
interface FormState {
username: string
password: string
remember: boolean
}
const formState = reactive<FormState>({
username: '',
password: '',
remember: true,
})
const submitForm = () => {
formRef.value
?.validate()
.then(() => {
toast('Success!')
console.log('Success:', toRaw(formState))
})
.catch((error: FieldValidateError[]) => {
console.log('Failed:', error)
})
}
</script><template>
<doc-page gray title="简单登录框">
<s-form :model="formState" ref="formRef">
<s-form-item
label="Username"
name="username"
:rules="[{ required: true, message: 'Please input your username!' }]"
>
<s-input v-model="formState.username" />
</s-form-item>
<s-form-item
label="Password"
name="password"
:rules="[{ required: true, message: 'Please input your password!' }]"
>
<s-input type="password" v-model="formState.password" />
</s-form-item>
<s-form-item label="" name="remember">
<s-checkbox v-model:checked="formState.remember">Remember me</s-checkbox>
</s-form-item>
<s-form-item>
<s-button @click="submitForm">Submit</s-button>
</s-form-item>
</s-form>
</doc-page>
</template>
<script setup lang="js">
import { ref, reactive, toRaw } from 'vue'
import { toast } from 'sard'
const formRef = ref()
const formState = reactive({
username: '',
password: '',
remember: true,
})
const submitForm = () => {
formRef.value
?.validate()
.then(() => {
toast('Success!')
console.log('Success:', toRaw(formState))
})
.catch((error) => {
console.log('Failed:', error)
})
}
</script>Label 宽度
通过 label-width 属性设置标签宽度。
<template>
<doc-page gray title="Label 宽度">
<s-form :model="formState" ref="formRef" label-width="100px">
<s-form-item label="Activity name">
<s-input v-model="formState.name" />
</s-form-item>
<s-form-item label="Instant delivery">
<s-switch v-model="formState.delivery" />
</s-form-item>
<s-form-item label="Activity type">
<s-checkbox-group v-model="formState.type">
<s-checkbox value="1">Online</s-checkbox>
<s-checkbox value="2">Promotion</s-checkbox>
<s-checkbox value="3">Offline</s-checkbox>
</s-checkbox-group>
</s-form-item>
<s-form-item label="Resources">
<s-radio-group v-model="formState.resource">
<s-radio value="1">Sponsor</s-radio>
<s-radio value="2">Venue</s-radio>
</s-radio-group>
</s-form-item>
<s-form-item label="Activity form">
<s-input type="textarea" v-model="formState.desc" />
</s-form-item>
<s-form-item>
<s-button @click="onSubmit">Create</s-button>
<s-button variant="outlined" style="margin-top: 10px">Cancel</s-button>
</s-form-item>
</s-form>
</doc-page>
</template>
<script setup lang="ts">
import { reactive, toRaw, ref, type UnwrapRef } from 'vue'
import { type FormExpose } from 'sard'
const formRef = ref<FormExpose>()
interface FormState {
name: string
delivery: boolean
type: string[]
resource: string
desc: string
}
const formState: UnwrapRef<FormState> = reactive({
name: '',
delivery: false,
type: [],
resource: '',
desc: '',
})
const onSubmit = () => {
formRef.value?.validate().then(() => {
console.log('submit!', toRaw(formState))
})
}
</script><template>
<doc-page gray title="Label 宽度">
<s-form :model="formState" ref="formRef" label-width="100px">
<s-form-item label="Activity name">
<s-input v-model="formState.name" />
</s-form-item>
<s-form-item label="Instant delivery">
<s-switch v-model="formState.delivery" />
</s-form-item>
<s-form-item label="Activity type">
<s-checkbox-group v-model="formState.type">
<s-checkbox value="1">Online</s-checkbox>
<s-checkbox value="2">Promotion</s-checkbox>
<s-checkbox value="3">Offline</s-checkbox>
</s-checkbox-group>
</s-form-item>
<s-form-item label="Resources">
<s-radio-group v-model="formState.resource">
<s-radio value="1">Sponsor</s-radio>
<s-radio value="2">Venue</s-radio>
</s-radio-group>
</s-form-item>
<s-form-item label="Activity form">
<s-input type="textarea" v-model="formState.desc" />
</s-form-item>
<s-form-item>
<s-button @click="onSubmit">Create</s-button>
<s-button variant="outlined" style="margin-top: 10px">Cancel</s-button>
</s-form-item>
</s-form>
</doc-page>
</template>
<script setup lang="js">
import { reactive, toRaw, ref } from 'vue'
import 'sard'
const formRef = ref()
const formState = reactive({
name: '',
delivery: false,
type: [],
resource: '',
desc: '',
})
const onSubmit = () => {
formRef.value?.validate().then(() => {
console.log('submit!', toRaw(formState))
})
}
</script>表单只读和禁用
设置表单组件禁用或只读,仅对 sard 组件有效。
<template>
<doc-page gray title="表单只读和禁用">
<s-list>
<s-list-item title="只读">
<template #value>
<s-switch v-model="formReadonly" />
</template>
</s-list-item>
<s-list-item title="禁用">
<template #value>
<s-switch v-model="formDisabled" />
</template>
</s-list-item>
</s-list>
<s-form :disabled="formDisabled" :readonly="formReadonly" label-width="100px">
<s-form-item label="Checkbox">
<s-checkbox-group v-model="checkboxValue">
<s-checkbox value="apple">Apple</s-checkbox>
<s-checkbox value="pear">Pear</s-checkbox>
</s-checkbox-group>
</s-form-item>
<s-form-item label="Radio">
<s-radio-group v-model="radioValue">
<s-radio value="apple">Apple</s-radio>
<s-radio value="pear">Pear</s-radio>
</s-radio-group>
</s-form-item>
<s-form-item label="Input">
<s-input />
</s-form-item>
<s-form-item label="Picker">
<s-picker-input
placeholder="Picker"
:columns="[
{
label: 'Demo',
value: 'demo',
},
]"
/>
</s-form-item>
<s-form-item label="Cascader">
<s-cascader-input placeholder="Cascader" :options="options" />
</s-form-item>
<s-form-item label="DatetimePicker">
<s-datetime-picker-input placeholder="DatetimePicker" />
</s-form-item>
<s-form-item label="Calendar">
<s-calendar-input placeholder="Calendar" type="range" />
</s-form-item>
<s-form-item label="Stepper">
<s-stepper />
</s-form-item>
<s-form-item label="Input[Textarea]">
<s-input type="textarea" />
</s-form-item>
<s-form-item label="PasswordInput">
<s-password-input gap="0" model-value="123" style="height: 40px" />
</s-form-item>
<s-form-item label="Switch">
<s-switch :model-value="true" />
</s-form-item>
<s-form-item label="Rate">
<s-rate :model-value="3" />
</s-form-item>
<s-form-item label="Slider">
<s-slider :model-value="50" />
</s-form-item>
<s-form-item label="Upload">
<s-upload />
</s-form-item>
<s-form-item label="Button">
<s-button>Button</s-button>
</s-form-item>
</s-form>
</doc-page>
</template>
<script setup lang="ts">
import { ref, reactive } from 'vue'
const formDisabled = ref(true)
const formReadonly = ref(false)
const checkboxValue = ref(['apple'])
const radioValue = ref('apple')
const options = reactive([
{
value: 'zhejiang',
label: 'Zhejiang',
children: [
{
value: 'hangzhou',
label: 'Hangzhou',
},
],
},
])
</script><template>
<doc-page gray title="表单只读和禁用">
<s-list>
<s-list-item title="只读">
<template #value>
<s-switch v-model="formReadonly" />
</template>
</s-list-item>
<s-list-item title="禁用">
<template #value>
<s-switch v-model="formDisabled" />
</template>
</s-list-item>
</s-list>
<s-form :disabled="formDisabled" :readonly="formReadonly" label-width="100px">
<s-form-item label="Checkbox">
<s-checkbox-group v-model="checkboxValue">
<s-checkbox value="apple">Apple</s-checkbox>
<s-checkbox value="pear">Pear</s-checkbox>
</s-checkbox-group>
</s-form-item>
<s-form-item label="Radio">
<s-radio-group v-model="radioValue">
<s-radio value="apple">Apple</s-radio>
<s-radio value="pear">Pear</s-radio>
</s-radio-group>
</s-form-item>
<s-form-item label="Input">
<s-input />
</s-form-item>
<s-form-item label="Picker">
<s-picker-input
placeholder="Picker"
:columns="[
{
label: 'Demo',
value: 'demo',
},
]"
/>
</s-form-item>
<s-form-item label="Cascader">
<s-cascader-input placeholder="Cascader" :options="options" />
</s-form-item>
<s-form-item label="DatetimePicker">
<s-datetime-picker-input placeholder="DatetimePicker" />
</s-form-item>
<s-form-item label="Calendar">
<s-calendar-input placeholder="Calendar" type="range" />
</s-form-item>
<s-form-item label="Stepper">
<s-stepper />
</s-form-item>
<s-form-item label="Input[Textarea]">
<s-input type="textarea" />
</s-form-item>
<s-form-item label="PasswordInput">
<s-password-input gap="0" model-value="123" style="height: 40px" />
</s-form-item>
<s-form-item label="Switch">
<s-switch :model-value="true" />
</s-form-item>
<s-form-item label="Rate">
<s-rate :model-value="3" />
</s-form-item>
<s-form-item label="Slider">
<s-slider :model-value="50" />
</s-form-item>
<s-form-item label="Upload">
<s-upload />
</s-form-item>
<s-form-item label="Button">
<s-button>Button</s-button>
</s-form-item>
</s-form>
</doc-page>
</template>
<script setup lang="js">
import { ref, reactive } from 'vue'
const formDisabled = ref(true)
const formReadonly = ref(false)
const checkboxValue = ref(['apple'])
const radioValue = ref('apple')
const options = reactive([
{
value: 'zhejiang',
label: 'Zhejiang',
children: [
{
value: 'hangzhou',
label: 'Hangzhou',
},
],
},
])
</script>登录框
下面实现了一个简单的登录框组件。
<template>
<doc-page gray title="登录框">
<s-form :model="formState" ref="formRef">
<s-form-item
label="Username"
name="username"
:rules="[{ required: true, message: 'Please input your username!' }]"
>
<s-input v-model="formState.username">
<template #prepend>
<demo-icon name="person" />
</template>
</s-input>
</s-form-item>
<s-form-item
label="Password"
name="password"
:rules="[{ required: true, message: 'Please input your password!' }]"
>
<s-input type="password" v-model="formState.password">
<template #prepend>
<demo-icon name="key" />
</template>
</s-input>
</s-form-item>
<s-form-item>
<s-flex justify="between" gap="medium">
<s-form-item name="remember" inlaid>
<s-checkbox v-model:checked="formState.remember" class="my-auto">
Remember me
</s-checkbox>
</s-form-item>
<s-button variant="link">Forgot password</s-button>
</s-flex>
</s-form-item>
<s-form-item>
<s-button @click="submitForm" :disabled="disabled">Log in</s-button>
<s-flex align="center" justify="end" gap="medium">
<span>Or</span>
<s-button variant="link">register now!</s-button>
</s-flex>
</s-form-item>
</s-form>
</doc-page>
</template>
<script setup lang="ts">
import { reactive, computed, ref } from 'vue'
import { toast, type FormExpose, type FieldValidateError } from 'sard'
const formRef = ref<FormExpose>()
interface FormState {
username: string
password: string
remember: boolean
}
const formState = reactive<FormState>({
username: '',
password: '',
remember: true,
})
const submitForm = () => {
formRef.value
?.validate()
.then(() => {
toast('Success')
console.log('Success:', formState)
})
.catch((error: FieldValidateError[]) => {
console.log('Failed:', error)
})
}
const disabled = computed(() => {
return !(formState.username && formState.password)
})
</script><template>
<doc-page gray title="登录框">
<s-form :model="formState" ref="formRef">
<s-form-item
label="Username"
name="username"
:rules="[{ required: true, message: 'Please input your username!' }]"
>
<s-input v-model="formState.username">
<template #prepend>
<demo-icon name="person" />
</template>
</s-input>
</s-form-item>
<s-form-item
label="Password"
name="password"
:rules="[{ required: true, message: 'Please input your password!' }]"
>
<s-input type="password" v-model="formState.password">
<template #prepend>
<demo-icon name="key" />
</template>
</s-input>
</s-form-item>
<s-form-item>
<s-flex justify="between" gap="medium">
<s-form-item name="remember" inlaid>
<s-checkbox v-model:checked="formState.remember" class="my-auto">
Remember me
</s-checkbox>
</s-form-item>
<s-button variant="link">Forgot password</s-button>
</s-flex>
</s-form-item>
<s-form-item>
<s-button @click="submitForm" :disabled="disabled">Log in</s-button>
<s-flex align="center" justify="end" gap="medium">
<span>Or</span>
<s-button variant="link">register now!</s-button>
</s-flex>
</s-form-item>
</s-form>
</doc-page>
</template>
<script setup lang="js">
import { reactive, computed, ref } from 'vue'
import { toast } from 'sard'
const formRef = ref()
const formState = reactive({
username: '',
password: '',
remember: true,
})
const submitForm = () => {
formRef.value
?.validate()
.then(() => {
toast('Success')
console.log('Success:', formState)
})
.catch((error) => {
console.log('Failed:', error)
})
}
const disabled = computed(() => {
return !(formState.username && formState.password)
})
</script>嵌套结构与校验信息
name 属性支持嵌套数据结构。
<template>
<doc-page gray title="嵌套结构与校验信息">
<s-form :model="formState" ref="formRef">
<s-form-item :name="['user', 'name']" label="Name" :rules="[{ required: true }]">
<s-input v-model="formState.user.name" />
</s-form-item>
<s-form-item :name="['user', 'email']" label="Email" :rules="[{ type: 'email' }]">
<s-input v-model="formState.user.email" />
</s-form-item>
<s-form-item
:name="['user', 'age']"
label="Age"
:rules="[{ type: 'number', min: 0, max: 99 }]"
>
<s-stepper v-model="formState.user.age" />
</s-form-item>
<s-form-item :name="['user', 'website']" label="Website">
<s-input v-model="formState.user.website" />
</s-form-item>
<s-form-item :name="['user', 'introduction']" label="Introduction">
<s-input type="textarea" v-model="formState.user.introduction" />
</s-form-item>
<s-form-item>
<s-button @click="onSubmit">Submit</s-button>
</s-form-item>
</s-form>
</doc-page>
</template>
<script setup lang="ts">
import { reactive, ref, toRaw } from 'vue'
import { toast, type FormExpose } from 'sard'
const formRef = ref<FormExpose>()
const formState = reactive({
user: {
name: '',
age: undefined,
email: '',
website: '',
introduction: '',
},
})
const onSubmit = () => {
formRef.value
?.validate()
.then(() => {
toast('Success')
console.log('Success:', toRaw(formState))
})
.catch(() => {
toast('fail!')
})
}
</script><template>
<doc-page gray title="嵌套结构与校验信息">
<s-form :model="formState" ref="formRef">
<s-form-item :name="['user', 'name']" label="Name" :rules="[{ required: true }]">
<s-input v-model="formState.user.name" />
</s-form-item>
<s-form-item :name="['user', 'email']" label="Email" :rules="[{ type: 'email' }]">
<s-input v-model="formState.user.email" />
</s-form-item>
<s-form-item
:name="['user', 'age']"
label="Age"
:rules="[{ type: 'number', min: 0, max: 99 }]"
>
<s-stepper v-model="formState.user.age" />
</s-form-item>
<s-form-item :name="['user', 'website']" label="Website">
<s-input v-model="formState.user.website" />
</s-form-item>
<s-form-item :name="['user', 'introduction']" label="Introduction">
<s-input type="textarea" v-model="formState.user.introduction" />
</s-form-item>
<s-form-item>
<s-button @click="onSubmit">Submit</s-button>
</s-form-item>
</s-form>
</doc-page>
</template>
<script setup lang="js">
import { reactive, ref, toRaw } from 'vue'
import { toast } from 'sard'
const formRef = ref()
const formState = reactive({
user: {
name: '',
age: undefined,
email: '',
website: '',
introduction: '',
},
})
const onSubmit = () => {
formRef.value
?.validate()
.then(() => {
toast('Success')
console.log('Success:', toRaw(formState))
})
.catch(() => {
toast('fail!')
})
}
</script>自定义表单控件
自定义或第三方的表单控件,也可以与 Form 组件一起使用。只要该组件注入 useFormItemContext 并调用相应方法。
<template>
<doc-page gray title="自定义表单控件">
<s-form :model="formState" ref="formRef">
<s-form-item name="price" label="Price" :rules="[{ validator: checkPrice }]">
<price-input v-model="formState.price" />
</s-form-item>
<s-form-item>
<s-button @click="onSubmit">Submit</s-button>
</s-form-item>
</s-form>
</doc-page>
</template>
<script setup lang="ts">
import { reactive, ref, toRaw } from 'vue'
import PriceInput, { type Currency } from './PriceInput.vue'
import { toast, type FormExpose } from 'sard'
const formRef = ref<FormExpose>()
const formState = reactive({
price: {
number: 0,
currency: 'rmb' as Currency,
},
})
const onSubmit = () => {
formRef.value
?.validate()
.then(() => {
toast('Success')
console.log('Received values from form: ', toRaw(formState))
})
.catch(() => {
toast('Fail')
})
}
const checkPrice = ({ value }: { value: { number: number } }) => {
if (value.number > 0) {
return true
}
return 'Price must be greater than zero!'
}
</script><template>
<doc-page gray title="自定义表单控件">
<s-form :model="formState" ref="formRef">
<s-form-item name="price" label="Price" :rules="[{ validator: checkPrice }]">
<price-input v-model="formState.price" />
</s-form-item>
<s-form-item>
<s-button @click="onSubmit">Submit</s-button>
</s-form-item>
</s-form>
</doc-page>
</template>
<script setup lang="js">
import { reactive, ref, toRaw } from 'vue'
import PriceInput from './PriceInput.vue'
import { toast } from 'sard'
const formRef = ref()
const formState = reactive({
price: {
number: 0,
currency: 'rmb',
},
})
const onSubmit = () => {
formRef.value
?.validate()
.then(() => {
toast('Success')
console.log('Received values from form: ', toRaw(formState))
})
.catch(() => {
toast('Fail')
})
}
const checkPrice = ({ value }) => {
if (value.number > 0) {
return true
}
return 'Price must be greater than zero!'
}
</script>PriceInput 组件:
<template>
<s-flex align="center" gap="medium">
<s-input
variant="text"
:model-value="modelValue.number"
@update:modelValue="onNumberChange"
:validate-event="false"
/>
<s-picker-input
:model-value="modelValue.currency"
placeholder="currency"
:columns="[
{ value: 'rmb', label: 'RMB' },
{ value: 'dollar', label: 'Dollar' },
]"
@update:modelValue="onCurrencyChange"
:validate-event="false"
/>
</s-flex>
</template>
<script setup lang="ts">
import { useFormItemContext, type FormItemContext } from 'sard'
export type Currency = 'rmb' | 'dollar'
interface PriceValue {
number: number
currency: Currency
}
const props = defineProps<{
modelValue: PriceValue
}>()
const emit = defineEmits<{
(e: 'update:modelValue', value: PriceValue): void
}>()
const formItemContext = useFormItemContext() as FormItemContext
const triggerChange = (changedValue: { number?: number; currency?: Currency }) => {
emit('update:modelValue', { ...props.modelValue, ...changedValue })
formItemContext.onChange()
}
const onNumberChange = (value: string | number) => {
const newNumber = parseInt(String(value) || '0', 10)
triggerChange({ number: newNumber })
}
const onCurrencyChange = (newCurrency: Currency) => {
triggerChange({ currency: newCurrency })
}
</script><template>
<s-flex align="center" gap="medium">
<s-input
variant="text"
:model-value="modelValue.number"
@update:modelValue="onNumberChange"
:validate-event="false"
/>
<s-picker-input
:model-value="modelValue.currency"
placeholder="currency"
:columns="[
{ value: 'rmb', label: 'RMB' },
{ value: 'dollar', label: 'Dollar' },
]"
@update:modelValue="onCurrencyChange"
:validate-event="false"
/>
</s-flex>
</template>
<script setup lang="js">
import { useFormItemContext } from 'sard'
const props = defineProps()
const emit = defineEmits()
const formItemContext = useFormItemContext()
const triggerChange = (changedValue) => {
emit('update:modelValue', {
...props.modelValue,
...changedValue,
})
formItemContext.onChange()
}
const onNumberChange = (value) => {
const newNumber = parseInt(String(value) || '0', 10)
triggerChange({ number: newNumber })
}
const onCurrencyChange = (newCurrency) => {
triggerChange({ currency: newCurrency })
}
</script>复杂的动态增减表单项
这个例子演示了一个表单中包含多个表单控件的情况。
<template>
<doc-page gray title="复杂的动态增减表单项">
<s-form ref="formRef" :model="dynamicValidateForm">
<s-form-item name="area" label="Area" :rules="[{ required: true, message: 'Missing area' }]">
<s-picker-input v-model="dynamicValidateForm.area" placeholder="Area" :columns="areas" />
</s-form-item>
<s-form-item v-for="(sight, index) in dynamicValidateForm.sights" :key="sight.id">
<s-flex align="center" gap="medium">
<s-form-item
:name="['sights', index, 'value']"
label="Sight"
label-width="50px"
:rules="{
required: true,
message: 'Missing sight',
}"
inlaid
>
<s-picker-input
v-model="sight.value"
placeholder="Sight"
:disabled="!dynamicValidateForm.area"
:columns="
(sights[dynamicValidateForm.area] || []).map((a) => ({
value: a,
label: a,
}))
"
/>
</s-form-item>
<s-form-item
:name="['sights', index, 'price']"
label="Price"
label-width="50px"
:rules="{
required: true,
message: 'Missing price',
}"
inlaid
>
<s-input inlaid placeholder="Price" v-model="sight.price" />
</s-form-item>
<s-button variant="text" size="small" color="danger" @click="removeSight(sight)" #icon>
<Trash />
</s-button>
</s-flex>
</s-form-item>
<s-form-item>
<s-button variant="outlined" @click="addSight()">
<template #icon>
<Plus />
</template>
Add sights
</s-button>
</s-form-item>
<s-form-item>
<s-button @click="onSubmit">Submit</s-button>
</s-form-item>
</s-form>
</doc-page>
</template>
<script setup lang="ts">
import { toRaw } from 'vue'
import { reactive, ref, watch } from 'vue'
import { toast, type FormExpose } from 'sard'
import { Plus, Trash } from '@sard/icons'
interface Sights {
value: string
price: string
id: number
}
const areas = [
{ label: 'Beijing', value: 'Beijing' },
{ label: 'Shanghai', value: 'Shanghai' },
]
const sights: { [key: string]: string[] } = {
Beijing: ['Tiananmen', 'Great Wall'],
Shanghai: ['Oriental Pearl', 'The Bund'],
}
const formRef = ref<FormExpose>()
const dynamicValidateForm = reactive<{ sights: Sights[]; area: string }>({
sights: [],
area: '',
})
watch(
() => dynamicValidateForm.area,
() => {
dynamicValidateForm.sights = []
},
)
const removeSight = (item: Sights) => {
const index = dynamicValidateForm.sights.indexOf(item)
if (index !== -1) {
dynamicValidateForm.sights.splice(index, 1)
}
}
const addSight = () => {
dynamicValidateForm.sights.push({
value: '',
price: '',
id: Date.now(),
})
}
const onSubmit = () => {
formRef.value
?.validate()
.then(() => {
console.log('Received values of form:', toRaw(dynamicValidateForm))
toast('success')
})
.catch(() => {
console.log('fail')
})
}
</script><template>
<doc-page gray title="复杂的动态增减表单项">
<s-form ref="formRef" :model="dynamicValidateForm">
<s-form-item name="area" label="Area" :rules="[{ required: true, message: 'Missing area' }]">
<s-picker-input v-model="dynamicValidateForm.area" placeholder="Area" :columns="areas" />
</s-form-item>
<s-form-item v-for="(sight, index) in dynamicValidateForm.sights" :key="sight.id">
<s-flex align="center" gap="medium">
<s-form-item
:name="['sights', index, 'value']"
label="Sight"
label-width="50px"
:rules="{
required: true,
message: 'Missing sight',
}"
inlaid
>
<s-picker-input
v-model="sight.value"
placeholder="Sight"
:disabled="!dynamicValidateForm.area"
:columns="
(sights[dynamicValidateForm.area] || []).map((a) => ({
value: a,
label: a,
}))
"
/>
</s-form-item>
<s-form-item
:name="['sights', index, 'price']"
label="Price"
label-width="50px"
:rules="{
required: true,
message: 'Missing price',
}"
inlaid
>
<s-input inlaid placeholder="Price" v-model="sight.price" />
</s-form-item>
<s-button variant="text" size="small" color="danger" @click="removeSight(sight)" #icon>
<Trash />
</s-button>
</s-flex>
</s-form-item>
<s-form-item>
<s-button variant="outlined" @click="addSight()">
<template #icon>
<Plus />
</template>
Add sights
</s-button>
</s-form-item>
<s-form-item>
<s-button @click="onSubmit">Submit</s-button>
</s-form-item>
</s-form>
</doc-page>
</template>
<script setup lang="js">
import { toRaw } from 'vue'
import { reactive, ref, watch } from 'vue'
import { toast } from 'sard'
import { Plus, Trash } from '@sard/icons'
const areas = [
{
label: 'Beijing',
value: 'Beijing',
},
{
label: 'Shanghai',
value: 'Shanghai',
},
]
const sights = {
Beijing: ['Tiananmen', 'Great Wall'],
Shanghai: ['Oriental Pearl', 'The Bund'],
}
const formRef = ref()
const dynamicValidateForm = reactive({
sights: [],
area: '',
})
watch(
() => dynamicValidateForm.area,
() => {
dynamicValidateForm.sights = []
},
)
const removeSight = (item) => {
const index = dynamicValidateForm.sights.indexOf(item)
if (index !== -1) {
dynamicValidateForm.sights.splice(index, 1)
}
}
const addSight = () => {
dynamicValidateForm.sights.push({
value: '',
price: '',
id: Date.now(),
})
}
const onSubmit = () => {
formRef.value
?.validate()
.then(() => {
console.log('Received values of form:', toRaw(dynamicValidateForm))
toast('success')
})
.catch(() => {
console.log('fail')
})
}
</script>动态增减嵌套字段
通过数组 name 绑定嵌套字段。
<template>
<doc-page gray title="动态增减嵌套字段">
<s-form ref="formRef" :model="dynamicValidateForm">
<s-form-item v-for="(user, index) in dynamicValidateForm.users" :key="user.id">
<s-flex align="center" gap="medium">
<s-form-item
:name="['users', index, 'first']"
:rules="{
required: true,
message: 'Missing first name',
}"
inlaid
>
<s-input inlaid v-model="user.first" placeholder="First Name" />
</s-form-item>
<s-form-item
:name="['users', index, 'last']"
:rules="{
required: true,
message: 'Missing last name',
}"
inlaid
>
<s-input inlaid v-model="user.last" placeholder="Last Name" />
</s-form-item>
<s-button variant="text" size="small" color="danger" @click="removeUser(user)" #icon>
<Trash />
</s-button>
</s-flex>
</s-form-item>
<s-form-item>
<s-button variant="outlined" @click="addUser()">
<template #icon>
<Plus />
</template>
Add user
</s-button>
</s-form-item>
<s-form-item>
<s-button @click="onSubmit">Submit</s-button>
</s-form-item>
</s-form>
</doc-page>
</template>
<script setup lang="ts">
import { toRaw, reactive, ref } from 'vue'
import { toast, type FormExpose } from 'sard'
import { Plus, Trash } from '@sard/icons'
interface User {
first: string
last: string
id: number
}
const formRef = ref<FormExpose>()
const dynamicValidateForm = reactive<{ users: User[] }>({
users: [],
})
const removeUser = (item: User) => {
const index = dynamicValidateForm.users.indexOf(item)
if (index !== -1) {
dynamicValidateForm.users.splice(index, 1)
}
}
const addUser = () => {
dynamicValidateForm.users.push({
first: '',
last: '',
id: Date.now(),
})
}
const onSubmit = () => {
formRef.value
?.validate()
.then(() => {
toast('Success')
console.log('Received values of form:', toRaw(dynamicValidateForm))
})
.catch(() => {
console.log('fail')
})
}
</script><template>
<doc-page gray title="动态增减嵌套字段">
<s-form ref="formRef" :model="dynamicValidateForm">
<s-form-item v-for="(user, index) in dynamicValidateForm.users" :key="user.id">
<s-flex align="center" gap="medium">
<s-form-item
:name="['users', index, 'first']"
:rules="{
required: true,
message: 'Missing first name',
}"
inlaid
>
<s-input inlaid v-model="user.first" placeholder="First Name" />
</s-form-item>
<s-form-item
:name="['users', index, 'last']"
:rules="{
required: true,
message: 'Missing last name',
}"
inlaid
>
<s-input inlaid v-model="user.last" placeholder="Last Name" />
</s-form-item>
<s-button variant="text" size="small" color="danger" @click="removeUser(user)" #icon>
<Trash />
</s-button>
</s-flex>
</s-form-item>
<s-form-item>
<s-button variant="outlined" @click="addUser()">
<template #icon>
<Plus />
</template>
Add user
</s-button>
</s-form-item>
<s-form-item>
<s-button @click="onSubmit">Submit</s-button>
</s-form-item>
</s-form>
</doc-page>
</template>
<script setup lang="js">
import { toRaw, reactive, ref } from 'vue'
import { toast } from 'sard'
import { Plus, Trash } from '@sard/icons'
const formRef = ref()
const dynamicValidateForm = reactive({ users: [] })
const removeUser = (item) => {
const index = dynamicValidateForm.users.indexOf(item)
if (index !== -1) {
dynamicValidateForm.users.splice(index, 1)
}
}
const addUser = () => {
dynamicValidateForm.users.push({
first: '',
last: '',
id: Date.now(),
})
}
const onSubmit = () => {
formRef.value
?.validate()
.then(() => {
toast('Success')
console.log('Received values of form:', toRaw(dynamicValidateForm))
})
.catch(() => {
console.log('fail')
})
}
</script>动态校验规则
根据不同情况执行不同的校验规则。
<template>
<doc-page gray title="动态校验规则">
<s-form ref="formRef" :model="formState">
<s-form-item
label="Username"
name="username"
:rules="[{ required: true, message: 'Please input your username!' }]"
>
<s-input v-model="formState.username" />
</s-form-item>
<s-form-item
label="Nickname"
name="nickname"
:rules="[
{
required: formState.checkNick,
message: 'Please input your nickname!',
},
]"
>
<s-input v-model="formState.nickname" />
</s-form-item>
<s-form-item name="checkNick">
<s-checkbox v-model:checked="formState.checkNick">Nickname is required</s-checkbox>
</s-form-item>
<s-form-item>
<s-button @click="onCheck">Check</s-button>
</s-form-item>
</s-form>
</doc-page>
</template>
<script setup lang="ts">
import { reactive, ref, watch, toRaw } from 'vue'
import { toast, type FormExpose, type FieldValidateError } from 'sard'
interface FormState {
username: string
nickname: string
checkNick: boolean
}
const formRef = ref<FormExpose>()
const formState = reactive<FormState>({
username: '',
nickname: '',
checkNick: false,
})
watch(
() => formState.checkNick,
() => {
formRef.value?.validate(['nickname']).catch(() => void 0)
},
{ flush: 'post' },
)
const onCheck = async () => {
formRef.value
?.validate()
.then(() => {
console.log('Success:', toRaw(formState))
toast('success')
})
.catch((error: FieldValidateError[]) => {
console.log('Failed:', error)
})
}
</script><template>
<doc-page gray title="动态校验规则">
<s-form ref="formRef" :model="formState">
<s-form-item
label="Username"
name="username"
:rules="[{ required: true, message: 'Please input your username!' }]"
>
<s-input v-model="formState.username" />
</s-form-item>
<s-form-item
label="Nickname"
name="nickname"
:rules="[
{
required: formState.checkNick,
message: 'Please input your nickname!',
},
]"
>
<s-input v-model="formState.nickname" />
</s-form-item>
<s-form-item name="checkNick">
<s-checkbox v-model:checked="formState.checkNick">Nickname is required</s-checkbox>
</s-form-item>
<s-form-item>
<s-button @click="onCheck">Check</s-button>
</s-form-item>
</s-form>
</doc-page>
</template>
<script setup lang="js">
import { reactive, ref, watch, toRaw } from 'vue'
import { toast } from 'sard'
const formRef = ref()
const formState = reactive({
username: '',
nickname: '',
checkNick: false,
})
watch(
() => formState.checkNick,
() => {
formRef.value?.validate(['nickname']).catch(() => void 0)
},
{ flush: 'post' },
)
const onCheck = async () => {
formRef.value
?.validate()
.then(() => {
console.log('Success:', toRaw(formState))
toast('success')
})
.catch((error) => {
console.log('Failed:', error)
})
}
</script>多表单联动
把一个表单的数据添加到另一个表单。
<template>
<doc-page gray title="多表单联动">
<s-form ref="formRef" :model="formState" label-width="100px">
<s-form-item
name="group"
label="Group Name"
:rules="[{ required: true, message: 'Please input group name!' }]"
>
<s-input v-model="formState.group" />
</s-form-item>
<s-form-item label="User List">
<template v-if="formState.users.length">
<s-list inlaid>
<template v-for="user in formState.users" :key="user.key">
<s-list-item :title="`${user.name} - ${user.age}`" #icon>
<s-avatar size="32px" icon-size="20px" />
</s-list-item>
</template>
</s-list>
</template>
<template v-else>
<span>( No user yet. )</span>
</template>
</s-form-item>
<s-form-item>
<s-button @click="onSubmit">Submit</s-button>
<s-button variant="outlined" style="margin-top: 10px" @click="visible = true">
Add User
</s-button>
</s-form-item>
</s-form>
<s-dialog v-model:visible="visible" title="Basic Drawer" :before-close="beforeClose">
<s-form ref="modalFormRef" :model="modalFormState">
<s-form-item name="name" label="User Name" :rules="[{ required: true }]">
<s-input v-model="modalFormState.name" inlaid placeholder="Input User Name" />
</s-form-item>
<s-form-item name="age" label="User Age" :rules="[{ required: true }]">
<s-stepper v-model="modalFormState.age" />
</s-form-item>
</s-form>
</s-dialog>
</doc-page>
</template>
<script setup lang="ts">
import { reactive, ref, toRaw } from 'vue'
import { toast, type FormExpose, type FieldValidateError } from 'sard'
interface UserType {
name?: string
age?: number
key?: number
}
interface FormState {
group: string
users: UserType[]
}
const formRef = ref<FormExpose>()
const modalFormRef = ref<FormExpose>()
const visible = ref(false)
const formState = reactive<FormState>({
group: '',
users: [],
})
const modalFormState = ref<UserType>({})
const beforeClose = (type: string) => {
if (type === 'confirm') {
return modalFormRef.value?.validate().then(() => {
formState.users.push({ ...modalFormState.value, key: Date.now() })
modalFormRef.value?.reset()
})
}
}
const onSubmit = () => {
formRef.value
?.validate()
.then(() => {
console.log('Finish:', toRaw(formState))
toast('Success')
})
.catch((error: FieldValidateError[]) => {
console.log('error', error)
})
}
</script><template>
<doc-page gray title="多表单联动">
<s-form ref="formRef" :model="formState" label-width="100px">
<s-form-item
name="group"
label="Group Name"
:rules="[{ required: true, message: 'Please input group name!' }]"
>
<s-input v-model="formState.group" />
</s-form-item>
<s-form-item label="User List">
<template v-if="formState.users.length">
<s-list inlaid>
<template v-for="user in formState.users" :key="user.key">
<s-list-item :title="`${user.name} - ${user.age}`" #icon>
<s-avatar size="32px" icon-size="20px" />
</s-list-item>
</template>
</s-list>
</template>
<template v-else>
<span>( No user yet. )</span>
</template>
</s-form-item>
<s-form-item>
<s-button @click="onSubmit">Submit</s-button>
<s-button variant="outlined" style="margin-top: 10px" @click="visible = true">
Add User
</s-button>
</s-form-item>
</s-form>
<s-dialog v-model:visible="visible" title="Basic Drawer" :before-close="beforeClose">
<s-form ref="modalFormRef" :model="modalFormState">
<s-form-item name="name" label="User Name" :rules="[{ required: true }]">
<s-input v-model="modalFormState.name" inlaid placeholder="Input User Name" />
</s-form-item>
<s-form-item name="age" label="User Age" :rules="[{ required: true }]">
<s-stepper v-model="modalFormState.age" />
</s-form-item>
</s-form>
</s-dialog>
</doc-page>
</template>
<script setup lang="js">
import { reactive, ref, toRaw } from 'vue'
import { toast } from 'sard'
const formRef = ref()
const modalFormRef = ref()
const visible = ref(false)
const formState = reactive({
group: '',
users: [],
})
const modalFormState = ref({})
const beforeClose = (type) => {
if (type === 'confirm') {
return modalFormRef.value?.validate().then(() => {
formState.users.push({
...modalFormState.value,
key: Date.now(),
})
modalFormRef.value?.reset()
})
}
}
const onSubmit = () => {
formRef.value
?.validate()
.then(() => {
console.log('Finish:', toRaw(formState))
toast('Success')
})
.catch((error) => {
console.log('error', error)
})
}
</script>弹出层中的新建表单
当用户访问一个展示了某个列表的页面,想新建一项但又不想跳转页面时,可以用 Dialog 弹出一个表单,用户填写必要信息后创建新的项。
<template>
<doc-page gray title="弹出层中的新建表单">
<div class="mx-4">
<s-button block @click="visible = true">New Collection</s-button>
</div>
<s-dialog
v-model:visible="visible"
title="Create a new collection"
:headed="false"
confirm-text="Create"
cancel-text="Cancel"
:before-close="beforeClose"
>
<s-form ref="formRef" :model="formState">
<s-form-item
name="title"
label="Title"
:rules="[
{
required: true,
message: 'Please input the title of collection!',
},
]"
>
<s-input v-model="formState.title" />
</s-form-item>
<s-form-item name="description" label="Description">
<s-input type="textarea" v-model="formState.description" />
</s-form-item>
<s-form-item name="modifier">
<s-radio-group v-model="formState.modifier" direction="horizontal" class="ms-auto">
<s-radio value="public">Public</s-radio>
<s-radio value="private">Private</s-radio>
</s-radio-group>
</s-form-item>
</s-form>
<div style="margin-bottom: 10px"></div>
</s-dialog>
</doc-page>
</template>
<script setup lang="ts">
import { reactive, ref, toRaw } from 'vue'
import { toast, type FormExpose, type FieldValidateError } from 'sard'
interface Values {
title: string
description: string
modifier: string
}
const formRef = ref<FormExpose>()
const visible = ref(false)
const formState = reactive<Values>({
title: '',
description: '',
modifier: 'public',
})
const beforeClose = (type: string) => {
if (type === 'confirm') {
return formRef.value
?.validate()
.then(() => {
toast(JSON.stringify(formState))
console.log('formState: ', toRaw(formState))
formRef.value?.reset()
})
.catch((error: FieldValidateError[]) => {
console.log('Validate Failed:', error)
throw error
})
}
}
</script><template>
<doc-page gray title="弹出层中的新建表单">
<div class="mx-4">
<s-button block @click="visible = true">New Collection</s-button>
</div>
<s-dialog
v-model:visible="visible"
title="Create a new collection"
:headed="false"
confirm-text="Create"
cancel-text="Cancel"
:before-close="beforeClose"
>
<s-form ref="formRef" :model="formState">
<s-form-item
name="title"
label="Title"
:rules="[
{
required: true,
message: 'Please input the title of collection!',
},
]"
>
<s-input v-model="formState.title" />
</s-form-item>
<s-form-item name="description" label="Description">
<s-input type="textarea" v-model="formState.description" />
</s-form-item>
<s-form-item name="modifier">
<s-radio-group v-model="formState.modifier" direction="horizontal" class="ms-auto">
<s-radio value="public">Public</s-radio>
<s-radio value="private">Private</s-radio>
</s-radio-group>
</s-form-item>
</s-form>
<div style="margin-bottom: 10px"></div>
</s-dialog>
</doc-page>
</template>
<script setup lang="js">
import { reactive, ref, toRaw } from 'vue'
import { toast } from 'sard'
const formRef = ref()
const visible = ref(false)
const formState = reactive({
title: '',
description: '',
modifier: 'public',
})
const beforeClose = (type) => {
if (type === 'confirm') {
return formRef.value
?.validate()
.then(() => {
toast(JSON.stringify(formState))
console.log('formState: ', toRaw(formState))
formRef.value?.reset()
})
.catch((error) => {
console.log('Validate Failed:', error)
throw error
})
}
}
</script>滚动到第一个错误字段
当表单超过一屏,在验证失败后想要将验证错误的表单域定位到屏幕中,可以设置 scroll-to-first-error 属性。
<template>
<doc-page gray title="滚动到第一个错误字段" padding="0">
<s-form
:model="formState"
ref="formRef"
scroll-to-first-error
:scroll-into-view-options="{
block: position,
behavior: 'smooth',
}"
>
<s-form-item
v-for="(item, i) in formState"
:key="item.key"
:name="[i, 'value']"
:label="item.label"
required
:style="{
scrollMarginTop: '50px',
scrollMarginBottom: footerHeight + 'px',
}"
>
<s-input type="textarea" min-height="150px" v-model="item.value" />
</s-form-item>
</s-form>
<div :style="{ height: footerHeight + 'px' }"></div>
<div ref="footer" class="footer">
<s-row gap="10px">
<s-col>
<s-button block @click="onSubmit">Submit</s-button>
</s-col>
<s-col>
<s-button block variant="outlined" @click="onReset">reset</s-button>
</s-col>
</s-row>
<div style="padding: 10px 0">
<s-radio-group v-model="position" direction="horizontal">
<s-radio value="start">start</s-radio>
<s-radio value="center">center</s-radio>
<s-radio value="end">end</s-radio>
<s-radio value="nearest">nearest</s-radio>
</s-radio-group>
</div>
</div>
</doc-page>
</template>
<script setup lang="ts">
import { ref, toRaw, onMounted, useTemplateRef } from 'vue'
import { toast, type FormExpose, type ScrollIntoViewPosition, type FieldValidateError } from 'sard'
const formState = ref(
Array(10)
.fill(0)
.map((_, i) => {
return {
label: 'Item' + i,
name: 'item' + i,
value: '',
key: i,
}
}),
)
const formRef = ref<FormExpose>()
const onSubmit = () => {
formRef.value
?.validate()
.then(() => {
toast('success')
console.log('formState: ', toRaw(formState))
})
.catch((error: FieldValidateError[]) => {
console.log('Validate Failed:', error)
})
}
const onReset = () => {
formRef.value?.reset()
}
const position = ref<ScrollIntoViewPosition>('nearest')
const footerHeight = ref(0)
const footerRef = useTemplateRef('footer')
onMounted(() => {
footerHeight.value = footerRef.value!.getBoundingClientRect().height
})
</script>
<style lang="scss" scoped>
.footer {
position: fixed;
bottom: 0;
right: 0;
left: 0;
z-index: 1;
display: flex;
flex-direction: column;
gap: 15px;
padding: 10px 16px;
padding-bottom: calc(10px + var(--s-safe-bottom));
border-top: 1px var(--s-border-color) solid;
background: var(--s-bg-color-elevated);
}
</style><template>
<doc-page gray title="滚动到第一个错误字段" padding="0">
<s-form
:model="formState"
ref="formRef"
scroll-to-first-error
:scroll-into-view-options="{
block: position,
behavior: 'smooth',
}"
>
<s-form-item
v-for="(item, i) in formState"
:key="item.key"
:name="[i, 'value']"
:label="item.label"
required
:style="{
scrollMarginTop: '50px',
scrollMarginBottom: footerHeight + 'px',
}"
>
<s-input type="textarea" min-height="150px" v-model="item.value" />
</s-form-item>
</s-form>
<div :style="{ height: footerHeight + 'px' }"></div>
<div ref="footer" class="footer">
<s-row gap="10px">
<s-col>
<s-button block @click="onSubmit">Submit</s-button>
</s-col>
<s-col>
<s-button block variant="outlined" @click="onReset">reset</s-button>
</s-col>
</s-row>
<div style="padding: 10px 0">
<s-radio-group v-model="position" direction="horizontal">
<s-radio value="start">start</s-radio>
<s-radio value="center">center</s-radio>
<s-radio value="end">end</s-radio>
<s-radio value="nearest">nearest</s-radio>
</s-radio-group>
</div>
</div>
</doc-page>
</template>
<script setup lang="js">
import { ref, toRaw, onMounted, useTemplateRef } from 'vue'
import { toast } from 'sard'
const formState = ref(
Array(10)
.fill(0)
.map((_, i) => {
return {
label: 'Item' + i,
name: 'item' + i,
value: '',
key: i,
}
}),
)
const formRef = ref()
const onSubmit = () => {
formRef.value
?.validate()
.then(() => {
toast('success')
console.log('formState: ', toRaw(formState))
})
.catch((error) => {
console.log('Validate Failed:', error)
})
}
const onReset = () => {
formRef.value?.reset()
}
const position = ref('nearest')
const footerHeight = ref(0)
const footerRef = useTemplateRef('footer')
onMounted(() => {
footerHeight.value = footerRef.value.getBoundingClientRect().height
})
</script>
<style lang="scss" scoped>
.footer {
position: fixed;
bottom: 0;
right: 0;
left: 0;
z-index: 1;
display: flex;
flex-direction: column;
gap: 15px;
padding: 10px 16px;
padding-bottom: calc(10px + var(--s-safe-bottom));
border-top: 1px var(--s-border-color) solid;
background: var(--s-bg-color-elevated);
}
</style>toast 显示验证错误信息
通过设置 showError 属性隐藏默认验证错误信息,再通过 toast 显示 validate() 方法 catch 回调中的信息。
<template>
<doc-page gray title="toast显示验证错误信息">
<s-form ref="formRef" :model="formState" class="form" :show-error="false">
<s-form-item
name="username"
class="form-item"
:rules="[{ required: true, message: '请输入手机号!' }]"
>
<s-input v-model="formState.username" placeholder="请输入手机号" inlaid />
</s-form-item>
<s-form-item
name="password"
class="form-item"
:rules="[{ required: true, message: '请输入短信验证码!' }]"
>
<s-flex gap="medium">
<s-input
v-model="formState.password"
type="password"
placeholder="请输入短信验证码"
inlaid
class="flex-1"
/>
<s-button variant="link" compact>发送验证码</s-button>
</s-flex>
</s-form-item>
<s-form-item class="form-item">
<s-button @click="submitForm" round>登录</s-button>
</s-form-item>
</s-form>
</doc-page>
</template>
<script setup lang="ts">
import { ref, reactive, toRaw } from 'vue'
import { toast, type FormExpose, type FieldValidateError } from 'sard'
const formRef = ref<FormExpose>()
interface FormState {
username: string
password: string
}
const formState = reactive<FormState>({
username: '',
password: '',
})
const submitForm = () => {
formRef.value
?.validate()
.then(() => {
toast('Success!')
console.log('Success:', toRaw(formState))
})
.catch((error: FieldValidateError[]) => {
toast(error[0].message)
console.log('Failed:', error)
})
}
</script><template>
<doc-page gray title="toast显示验证错误信息">
<s-form ref="formRef" :model="formState" class="form" :show-error="false">
<s-form-item
name="username"
class="form-item"
:rules="[{ required: true, message: '请输入手机号!' }]"
>
<s-input v-model="formState.username" placeholder="请输入手机号" inlaid />
</s-form-item>
<s-form-item
name="password"
class="form-item"
:rules="[{ required: true, message: '请输入短信验证码!' }]"
>
<s-flex gap="medium">
<s-input
v-model="formState.password"
type="password"
placeholder="请输入短信验证码"
inlaid
class="flex-1"
/>
<s-button variant="link" compact>发送验证码</s-button>
</s-flex>
</s-form-item>
<s-form-item class="form-item">
<s-button @click="submitForm" round>登录</s-button>
</s-form-item>
</s-form>
</doc-page>
</template>
<script setup lang="js">
import { ref, reactive, toRaw } from 'vue'
import { toast } from 'sard'
const formRef = ref()
const formState = reactive({
username: '',
password: '',
})
const submitForm = () => {
formRef.value
?.validate()
.then(() => {
toast('Success!')
console.log('Success:', toRaw(formState))
})
.catch((error) => {
toast(error[0].message)
console.log('Failed:', error)
})
}
</script>手机号登录
演示了常用的手机号登录的表单实现。
<template>
<doc-page gray title="手机号登录">
<s-form ref="formRef" :model="formState" :show-error="false" class="auth-form">
<s-form-item
name="phone"
:rules="[{ required: true, message: '请输入手机号码' }]"
inlaid
class="auth-form-item"
>
<s-input v-model="formState.phone" placeholder="手机号码" inlaid class="auth-form-input">
<template #prepend>
<demo-icon name="phone" size="16px" color="var(--s-text-color-tertiary)" />
</template>
</s-input>
</s-form-item>
<s-form-item
name="password"
:rules="[{ required: true, message: '请输入密码' }]"
inlaid
class="auth-form-item"
>
<s-input
v-model="formState.password"
type="password"
placeholder="密码"
inlaid
class="auth-form-input"
>
<template #prepend>
<demo-icon name="key" size="16px" color="var(--s-text-color-tertiary)" />
</template>
</s-input>
</s-form-item>
<s-form-item inlaid>
<s-button round :loading="submitting" @click="submitForm">登录</s-button>
</s-form-item>
</s-form>
</doc-page>
</template>
<script setup lang="ts">
import { ref, reactive, toRaw } from 'vue'
import { toast, type FormExpose, type FieldValidateError } from 'sard'
const formRef = ref<FormExpose>()
interface FormState {
phone: string
password: string
}
const formState = reactive<FormState>({
phone: '',
password: '',
})
const submitting = ref(false)
const submit = async () => {
submitting.value = true
await new Promise((resolve) => setTimeout(resolve, 500))
submitting.value = false
}
const submitForm = () => {
formRef.value
?.validate()
.then(async () => {
await submit()
toast('登录成功')
console.log('Success:', toRaw(formState))
})
.catch((error: FieldValidateError[]) => {
toast(error[0].message)
console.log('Failed:', error)
})
}
</script>
<style lang="scss" scoped>
.auth-form {
padding: 16px;
.auth-form-item {
margin-bottom: 24px;
}
.auth-form-input {
height: 40px;
border-radius: 9999px;
padding: 0 24px;
background: rgba(var(--s-color-primary-rgb), 0.05);
}
}
</style><template>
<doc-page gray title="手机号登录">
<s-form ref="formRef" :model="formState" :show-error="false" class="auth-form">
<s-form-item
name="phone"
:rules="[{ required: true, message: '请输入手机号码' }]"
inlaid
class="auth-form-item"
>
<s-input v-model="formState.phone" placeholder="手机号码" inlaid class="auth-form-input">
<template #prepend>
<demo-icon name="phone" size="16px" color="var(--s-text-color-tertiary)" />
</template>
</s-input>
</s-form-item>
<s-form-item
name="password"
:rules="[{ required: true, message: '请输入密码' }]"
inlaid
class="auth-form-item"
>
<s-input
v-model="formState.password"
type="password"
placeholder="密码"
inlaid
class="auth-form-input"
>
<template #prepend>
<demo-icon name="key" size="16px" color="var(--s-text-color-tertiary)" />
</template>
</s-input>
</s-form-item>
<s-form-item inlaid>
<s-button round :loading="submitting" @click="submitForm">登录</s-button>
</s-form-item>
</s-form>
</doc-page>
</template>
<script setup lang="js">
import { ref, reactive, toRaw } from 'vue'
import { toast } from 'sard'
const formRef = ref()
const formState = reactive({
phone: '',
password: '',
})
const submitting = ref(false)
const submit = async () => {
submitting.value = true
await new Promise((resolve) => setTimeout(resolve, 500))
submitting.value = false
}
const submitForm = () => {
formRef.value
?.validate()
.then(async () => {
await submit()
toast('登录成功')
console.log('Success:', toRaw(formState))
})
.catch((error) => {
toast(error[0].message)
console.log('Failed:', error)
})
}
</script>
<style lang="scss" scoped>
.auth-form {
padding: 16px;
.auth-form-item {
margin-bottom: 24px;
}
.auth-form-input {
height: 40px;
border-radius: 9999px;
padding: 0 24px;
background: rgba(var(--s-color-primary-rgb), 0.05);
}
}
</style>修改密码
演示了常用的修改密码的表单实现。
<template>
<doc-page gray title="修改密码">
<s-form ref="formRef" :model="formState" class="auth-form" :show-error="false">
<s-form-item
name="oldPassword"
:rules="[{ required: true, message: '请输入旧密码' }]"
inlaid
class="auth-form-item"
>
<s-input
v-model="formState.oldPassword"
type="password"
placeholder="旧密码"
inlaid
class="auth-form-input"
>
<template #prepend>
<demo-icon name="key" size="16px" color="var(--s-text-color-tertiary)" />
</template>
</s-input>
</s-form-item>
<s-form-item
name="newPassword"
:rules="[{ required: true, message: '请输入新密码' }]"
inlaid
class="auth-form-item"
>
<s-input
v-model="formState.newPassword"
type="password"
placeholder="新密码"
inlaid
class="auth-form-input"
>
<template #prepend>
<demo-icon name="key" size="16px" color="var(--s-text-color-tertiary)" />
</template>
</s-input>
</s-form-item>
<s-form-item inlaid>
<s-button round :loading="submitting" @click="submitForm">提交</s-button>
</s-form-item>
</s-form>
</doc-page>
</template>
<script setup lang="ts">
import { ref, reactive, toRaw } from 'vue'
import { toast, type FormExpose, type FieldValidateError } from 'sard'
const formRef = ref<FormExpose>()
interface FormState {
oldPassword: string
newPassword: string
}
const formState = reactive<FormState>({
oldPassword: '',
newPassword: '',
})
const submitting = ref(false)
const submit = async () => {
submitting.value = true
await new Promise((resolve) => setTimeout(resolve, 500))
submitting.value = false
}
const submitForm = () => {
formRef.value
?.validate()
.then(async () => {
await submit()
toast('修改成功')
console.log('Success:', toRaw(formState))
})
.catch((error: FieldValidateError[]) => {
toast(error[0].message)
console.log('Failed:', error)
})
}
</script>
<style lang="scss" scoped>
.auth-form {
padding: 16px;
.auth-form-item {
margin-bottom: 24px;
}
.auth-form-input {
height: 40px;
border-radius: 9999px;
padding: 0 24px;
background: rgba(var(--s-color-primary-rgb), 0.05);
}
}
</style><template>
<doc-page gray title="修改密码">
<s-form ref="formRef" :model="formState" class="auth-form" :show-error="false">
<s-form-item
name="oldPassword"
:rules="[{ required: true, message: '请输入旧密码' }]"
inlaid
class="auth-form-item"
>
<s-input
v-model="formState.oldPassword"
type="password"
placeholder="旧密码"
inlaid
class="auth-form-input"
>
<template #prepend>
<demo-icon name="key" size="16px" color="var(--s-text-color-tertiary)" />
</template>
</s-input>
</s-form-item>
<s-form-item
name="newPassword"
:rules="[{ required: true, message: '请输入新密码' }]"
inlaid
class="auth-form-item"
>
<s-input
v-model="formState.newPassword"
type="password"
placeholder="新密码"
inlaid
class="auth-form-input"
>
<template #prepend>
<demo-icon name="key" size="16px" color="var(--s-text-color-tertiary)" />
</template>
</s-input>
</s-form-item>
<s-form-item inlaid>
<s-button round :loading="submitting" @click="submitForm">提交</s-button>
</s-form-item>
</s-form>
</doc-page>
</template>
<script setup lang="js">
import { ref, reactive, toRaw } from 'vue'
import { toast } from 'sard'
const formRef = ref()
const formState = reactive({
oldPassword: '',
newPassword: '',
})
const submitting = ref(false)
const submit = async () => {
submitting.value = true
await new Promise((resolve) => setTimeout(resolve, 500))
submitting.value = false
}
const submitForm = () => {
formRef.value
?.validate()
.then(async () => {
await submit()
toast('修改成功')
console.log('Success:', toRaw(formState))
})
.catch((error) => {
toast(error[0].message)
console.log('Failed:', error)
})
}
</script>
<style lang="scss" scoped>
.auth-form {
padding: 16px;
.auth-form-item {
margin-bottom: 24px;
}
.auth-form-input {
height: 40px;
border-radius: 9999px;
padding: 0 24px;
background: rgba(var(--s-color-primary-rgb), 0.05);
}
}
</style>忘记密码
演示了常用的忘记密码的表单实现。
<template>
<doc-page gray title="忘记密码">
<s-form ref="formRef" :model="formState" :show-error="false" class="auth-form">
<s-form-item
name="phone"
:rules="[{ required: true, message: '请输入手机号码' }]"
inlaid
class="auth-form-item"
>
<s-input v-model="formState.phone" placeholder="手机号码" inlaid class="auth-form-input">
<template #prepend>
<demo-icon name="phone" size="16px" color="var(--s-text-color-tertiary)" />
</template>
</s-input>
</s-form-item>
<s-form-item
name="captcha"
:rules="[{ required: true, message: '请输入验证码' }]"
inlaid
class="auth-form-item"
>
<s-input
v-model="formState.captcha"
placeholder="验证码"
inlaid
class="auth-form-input"
style="padding-inline-end: 0"
>
<template #prepend>
<demo-icon name="captcha" size="16px" color="var(--s-text-color-tertiary)" />
</template>
<template #append>
<s-button
variant="link"
:loading="captchaLoading"
:disabled="captchaDisabled"
@click="onCaptchaClick"
>
<s-count-down
v-if="captchaDisabled"
:time="1000 * 8"
format="重发验证码(s)"
@finish="captchaDisabled = false"
/>
<span v-else-if="captchaLoading">正在发送</span>
<span v-else>发送验证码</span>
</s-button>
</template>
</s-input>
</s-form-item>
<s-form-item
name="password"
:rules="[{ required: true, message: '请输入新密码' }]"
inlaid
class="auth-form-item"
>
<s-input
v-model="formState.password"
type="password"
placeholder="新密码"
inlaid
class="auth-form-input"
>
<template #prepend>
<demo-icon name="key" size="16px" color="var(--s-text-color-tertiary)" />
</template>
</s-input>
</s-form-item>
<s-form-item inlaid>
<s-button round :loading="submitting" @click="submitForm">提交</s-button>
</s-form-item>
</s-form>
</doc-page>
</template>
<script setup lang="ts">
import { ref, reactive, toRaw } from 'vue'
import { toast, type FormExpose, type FieldValidateError } from 'sard'
const formRef = ref<FormExpose>()
interface FormState {
phone: string
captcha: string
password: string
}
const formState = reactive<FormState>({
phone: '',
captcha: '',
password: '',
})
const submitting = ref(false)
const submit = async () => {
submitting.value = true
await new Promise((resolve) => setTimeout(resolve, 500))
submitting.value = false
}
const submitForm = () => {
formRef.value
?.validate()
.then(async () => {
await submit()
toast('修改成功')
console.log('Success:', toRaw(formState))
})
.catch((error: FieldValidateError[]) => {
toast(error[0].message)
console.log('Failed:', error)
})
}
const captchaLoading = ref(false)
const captchaDisabled = ref(false)
const sendCaptcha = () => {
return new Promise((resolve) => setTimeout(resolve, 1000))
}
const onCaptchaClick = () => {
captchaLoading.value = true
sendCaptcha()
.then(() => {
toast('已发送验证码')
captchaDisabled.value = true
})
.finally(() => {
captchaLoading.value = false
})
}
</script>
<style lang="scss" scoped>
.auth-form {
padding: 16px;
.auth-form-item {
margin-bottom: 24px;
}
.auth-form-input {
height: 40px;
border-radius: 9999px;
padding: 0 24px;
background: rgba(var(--s-color-primary-rgb), 0.05);
}
}
</style><template>
<doc-page gray title="忘记密码">
<s-form ref="formRef" :model="formState" :show-error="false" class="auth-form">
<s-form-item
name="phone"
:rules="[{ required: true, message: '请输入手机号码' }]"
inlaid
class="auth-form-item"
>
<s-input v-model="formState.phone" placeholder="手机号码" inlaid class="auth-form-input">
<template #prepend>
<demo-icon name="phone" size="16px" color="var(--s-text-color-tertiary)" />
</template>
</s-input>
</s-form-item>
<s-form-item
name="captcha"
:rules="[{ required: true, message: '请输入验证码' }]"
inlaid
class="auth-form-item"
>
<s-input
v-model="formState.captcha"
placeholder="验证码"
inlaid
class="auth-form-input"
style="padding-inline-end: 0"
>
<template #prepend>
<demo-icon name="captcha" size="16px" color="var(--s-text-color-tertiary)" />
</template>
<template #append>
<s-button
variant="link"
:loading="captchaLoading"
:disabled="captchaDisabled"
@click="onCaptchaClick"
>
<s-count-down
v-if="captchaDisabled"
:time="1000 * 8"
format="重发验证码(s)"
@finish="captchaDisabled = false"
/>
<span v-else-if="captchaLoading">正在发送</span>
<span v-else>发送验证码</span>
</s-button>
</template>
</s-input>
</s-form-item>
<s-form-item
name="password"
:rules="[{ required: true, message: '请输入新密码' }]"
inlaid
class="auth-form-item"
>
<s-input
v-model="formState.password"
type="password"
placeholder="新密码"
inlaid
class="auth-form-input"
>
<template #prepend>
<demo-icon name="key" size="16px" color="var(--s-text-color-tertiary)" />
</template>
</s-input>
</s-form-item>
<s-form-item inlaid>
<s-button round :loading="submitting" @click="submitForm">提交</s-button>
</s-form-item>
</s-form>
</doc-page>
</template>
<script setup lang="js">
import { ref, reactive, toRaw } from 'vue'
import { toast } from 'sard'
const formRef = ref()
const formState = reactive({
phone: '',
captcha: '',
password: '',
})
const submitting = ref(false)
const submit = async () => {
submitting.value = true
await new Promise((resolve) => setTimeout(resolve, 500))
submitting.value = false
}
const submitForm = () => {
formRef.value
?.validate()
.then(async () => {
await submit()
toast('修改成功')
console.log('Success:', toRaw(formState))
})
.catch((error) => {
toast(error[0].message)
console.log('Failed:', error)
})
}
const captchaLoading = ref(false)
const captchaDisabled = ref(false)
const sendCaptcha = () => {
return new Promise((resolve) => setTimeout(resolve, 1e3))
}
const onCaptchaClick = () => {
captchaLoading.value = true
sendCaptcha()
.then(() => {
toast('已发送验证码')
captchaDisabled.value = true
})
.finally(() => {
captchaLoading.value = false
})
}
</script>
<style lang="scss" scoped>
.auth-form {
padding: 16px;
.auth-form-item {
margin-bottom: 24px;
}
.auth-form-input {
height: 40px;
border-radius: 9999px;
padding: 0 24px;
background: rgba(var(--s-color-primary-rgb), 0.05);
}
}
</style>自定义表单样式和结构
如果对表单样式和结构有很强的定义性,可使用 FormPlain 和 FormItemPlain 组件来代替 Form 和 FormItem。
前者不包含任意样式,能让你自由发挥。
这两组组件几乎拥有一样的接口,除了 FormItemPlain 的插槽提供了一些属性用于获取当前表单项的状态和样式。
<template>
<doc-page title="服务人员满意度评分表" padding-bottom="0">
<s-form-plain
ref="formRef"
:model="formModel"
:rules="formRules"
scroll-to-first-error
:scroll-into-view-options="{
block: 'start',
behavior: 'smooth',
}"
>
<div class="form-description">
尊敬的客户:感谢您对我们服务的支持!请根据您的真实体验为服务人员评分。
</div>
<div class="section">
<div class="section-title">
<span class="section-index">1</span>
<span>基本信息</span>
</div>
<div class="section-content">
<s-form-item-plain name="staff_name" class="form-item" #default="{ shouldShowStar }">
<div class="form-label">
<span v-if="shouldShowStar" class="form-star">*</span>
服务人员姓名/工号:
</div>
<div class="form-input">
<s-input
v-model="formModel.staff_name"
placeholder="请输入服务人员姓名/工号"
clearable
inlaid
/>
</div>
</s-form-item-plain>
<s-form-item-plain name="service_date" class="form-item" #default="{ shouldShowStar }">
<div class="form-label">
<span v-if="shouldShowStar" class="form-star">*</span>
服务日期:
</div>
<div class="form-input">
<s-datetime-picker-input
v-model="formModel.service_date"
placeholder="请选择服务日期"
clearable
/>
</div>
</s-form-item-plain>
<s-form-item-plain name="contact" class="form-item" #default="{ shouldShowStar }">
<div class="form-label">
<span v-if="shouldShowStar" class="form-star">*</span>
您的联系方式(可选)
</div>
<div class="form-input">
<s-input
type="textarea"
v-model="formModel.contact"
placeholder="请输入联系方式"
clearable
inlaid
auto-height
min-height="24px"
/>
</div>
</s-form-item-plain>
</div>
</div>
<div class="section">
<div class="section-title">
<span class="section-index">2</span>
<span>服务人员评分</span>
</div>
<div class="section-content">
<s-form-item-plain name="professionalism" class="form-item" #default="{ shouldShowStar }">
<div class="form-label">
<span v-if="shouldShowStar" class="form-star">*</span>
专业能力:是否具备足够的知识和技能?
</div>
<RadioList v-model="formModel.professionalism" :options="ratingOptions" />
</s-form-item-plain>
<s-form-item-plain name="attitude" class="form-item" #default="{ shouldShowStar }">
<div class="form-label">
<span v-if="shouldShowStar" class="form-star">*</span>
服务态度:是否礼貌、耐心、热情?
</div>
<RadioList v-model="formModel.attitude" :options="ratingOptions" />
</s-form-item-plain>
<s-form-item-plain name="response_speed" class="form-item" #default="{ shouldShowStar }">
<div class="form-label">
<span v-if="shouldShowStar" class="form-star">*</span>
响应速度:问题处理是否及时?
</div>
<RadioList v-model="formModel.response_speed" :options="ratingOptions" />
</s-form-item-plain>
<s-form-item-plain name="communication" class="form-item" #default="{ shouldShowStar }">
<div class="form-label">
<span v-if="shouldShowStar" class="form-star">*</span>
沟通能力:表达是否清晰、易于理解?
</div>
<RadioList v-model="formModel.communication" :options="ratingOptions" />
</s-form-item-plain>
<s-form-item-plain name="problem_solving" class="form-item" #default="{ shouldShowStar }">
<div class="form-label">
<span v-if="shouldShowStar" class="form-star">*</span>
问题解决:是否有效解决您的需求?
</div>
<RadioList v-model="formModel.problem_solving" :options="ratingOptions" />
</s-form-item-plain>
</div>
</div>
<div class="section">
<div class="section-title">
<span class="section-index">3</span>
<span>整体评价</span>
</div>
<div class="section-content">
<s-form-item-plain
name="overall_satisfaction"
class="form-item"
#default="{ shouldShowStar }"
>
<div class="form-label">
<span v-if="shouldShowStar" class="form-star">*</span>
本次服务的整体满意度
</div>
<RadioList v-model="formModel.overall_satisfaction" :options="ratingOptions" />
</s-form-item-plain>
<s-form-item-plain name="would_return" class="form-item" #default="{ shouldShowStar }">
<div class="form-label">
<span v-if="shouldShowStar" class="form-star">*</span>
您是否会再次选择该服务人员?
</div>
<RadioList v-model="formModel.would_return" :options="returnOptions" />
</s-form-item-plain>
</div>
</div>
<div class="section">
<div class="section-title">
<span class="section-index">4</span>
<span>开放式反馈</span>
</div>
<div class="section-content">
<s-form-item-plain name="strengths" class="form-item">
<div class="form-label">服务人员的优点(可选)</div>
<div class="form-input">
<s-input
v-model="formModel.strengths"
type="textarea"
placeholder="请输入服务人员的优点"
clearable
inlaid
auto-height
min-height="24px"
/>
</div>
</s-form-item-plain>
<s-form-item-plain name="improvements" class="form-item">
<div class="form-label">需要改进的方面(可选)</div>
<div class="form-input">
<s-input
v-model="formModel.improvements"
type="textarea"
placeholder="请输入需要改进的方面"
clearable
inlaid
auto-height
min-height="24px"
/>
</div>
</s-form-item-plain>
<s-form-item-plain name="suggestions" class="form-item">
<div class="form-label">其他建议(可选)</div>
<div class="form-input">
<s-input
v-model="formModel.suggestions"
type="textarea"
placeholder="请输入其他建议"
clearable
inlaid
auto-height
min-height="24px"
/>
</div>
</s-form-item-plain>
</div>
</div>
<div class="footer">
<s-button block @click="onSubmit">提交评价</s-button>
</div>
</s-form-plain>
</doc-page>
</template>
<script setup lang="ts">
import { reactive, ref } from 'vue'
import { toast, type FormExpose, type FieldValidateError, type FormRules } from 'sard'
import RadioList from './RadioList.vue'
const formModel = reactive({
staff_name: '',
service_date: '',
contact: '',
professionalism: '',
attitude: '',
response_speed: '',
communication: '',
problem_solving: '',
overall_satisfaction: '',
would_return: '',
strengths: '',
improvements: '',
suggestions: '',
})
const formRules: FormRules = {
staff_name: {
required: true,
message: '请输入服务人员姓名/工号',
},
service_date: {
required: true,
message: '请选择服务日期',
},
professionalism: {
required: true,
message: '请打分',
},
attitude: {
required: true,
message: '请打分',
},
response_speed: {
required: true,
message: '请打分',
},
communication: {
required: true,
message: '请打分',
},
problem_solving: {
required: true,
message: '请打分',
},
overall_satisfaction: {
required: true,
message: '请选择整体满意度',
},
would_return: {
required: true,
message: '请选择',
},
}
const formRef = ref<FormExpose>()
const onSubmit = () => {
formRef.value
?.validate()
.then(() => {
toast('提交成功!')
})
.catch((error: FieldValidateError[]) => {
toast(error[0].message)
console.log('error submit!', error)
})
}
const ratingOptions = [
{ value: '1', label: '不满意' },
{ value: '2', label: '一般' },
{ value: '3', label: '基本满意' },
{ value: '4', label: '非常满意' },
]
const returnOptions = [
{ value: 'yes', label: '是' },
{ value: 'no', label: '否' },
{ value: 'unsure', label: '不确定' },
]
</script>
<style lang="scss" scoped>
.form-description {
padding: 10px 32px;
font-size: var(--s-font-size-sm);
color: var(--s-text-color-secondary);
text-align: center;
}
.section {
margin-top: 16px;
}
.section-title {
display: flex;
align-items: center;
font-size: var(--s-font-size-lg);
}
.section-index {
display: inline-flex;
justify-content: center;
align-items: center;
width: 16px;
height: 16px;
margin-inline-end: 4px;
font-size: var(--s-font-size-xs);
color: #fff;
background-color: var(--s-color-primary);
}
.section-content {
margin: 0 16px;
}
.form-item {
scroll-margin-top: 60px;
}
.form-label {
margin: 20px 0 5px;
}
.form-star {
color: var(--s-color-danger);
}
.form-input {
margin-top: 10px;
&::after {
display: block;
content: '';
margin-top: 10px;
border-top: 1px solid var(--s-border-color);
transform: scaleY(0.5);
}
}
.footer {
position: sticky;
bottom: 0;
margin-top: 20px;
padding: 10px 16px calc(10px + var(--s-safe-bottom));
border-top: 1px solid var(--s-border-color);
background-color: var(--s-bg-color-elevated);
}
</style><template>
<doc-page title="服务人员满意度评分表" padding-bottom="0">
<s-form-plain
ref="formRef"
:model="formModel"
:rules="formRules"
scroll-to-first-error
:scroll-into-view-options="{
block: 'start',
behavior: 'smooth',
}"
>
<div class="form-description">
尊敬的客户:感谢您对我们服务的支持!请根据您的真实体验为服务人员评分。
</div>
<div class="section">
<div class="section-title">
<span class="section-index">1</span>
<span>基本信息</span>
</div>
<div class="section-content">
<s-form-item-plain name="staff_name" class="form-item" #default="{ shouldShowStar }">
<div class="form-label">
<span v-if="shouldShowStar" class="form-star">*</span>
服务人员姓名/工号:
</div>
<div class="form-input">
<s-input
v-model="formModel.staff_name"
placeholder="请输入服务人员姓名/工号"
clearable
inlaid
/>
</div>
</s-form-item-plain>
<s-form-item-plain name="service_date" class="form-item" #default="{ shouldShowStar }">
<div class="form-label">
<span v-if="shouldShowStar" class="form-star">*</span>
服务日期:
</div>
<div class="form-input">
<s-datetime-picker-input
v-model="formModel.service_date"
placeholder="请选择服务日期"
clearable
/>
</div>
</s-form-item-plain>
<s-form-item-plain name="contact" class="form-item" #default="{ shouldShowStar }">
<div class="form-label">
<span v-if="shouldShowStar" class="form-star">*</span>
您的联系方式(可选)
</div>
<div class="form-input">
<s-input
type="textarea"
v-model="formModel.contact"
placeholder="请输入联系方式"
clearable
inlaid
auto-height
min-height="24px"
/>
</div>
</s-form-item-plain>
</div>
</div>
<div class="section">
<div class="section-title">
<span class="section-index">2</span>
<span>服务人员评分</span>
</div>
<div class="section-content">
<s-form-item-plain name="professionalism" class="form-item" #default="{ shouldShowStar }">
<div class="form-label">
<span v-if="shouldShowStar" class="form-star">*</span>
专业能力:是否具备足够的知识和技能?
</div>
<RadioList v-model="formModel.professionalism" :options="ratingOptions" />
</s-form-item-plain>
<s-form-item-plain name="attitude" class="form-item" #default="{ shouldShowStar }">
<div class="form-label">
<span v-if="shouldShowStar" class="form-star">*</span>
服务态度:是否礼貌、耐心、热情?
</div>
<RadioList v-model="formModel.attitude" :options="ratingOptions" />
</s-form-item-plain>
<s-form-item-plain name="response_speed" class="form-item" #default="{ shouldShowStar }">
<div class="form-label">
<span v-if="shouldShowStar" class="form-star">*</span>
响应速度:问题处理是否及时?
</div>
<RadioList v-model="formModel.response_speed" :options="ratingOptions" />
</s-form-item-plain>
<s-form-item-plain name="communication" class="form-item" #default="{ shouldShowStar }">
<div class="form-label">
<span v-if="shouldShowStar" class="form-star">*</span>
沟通能力:表达是否清晰、易于理解?
</div>
<RadioList v-model="formModel.communication" :options="ratingOptions" />
</s-form-item-plain>
<s-form-item-plain name="problem_solving" class="form-item" #default="{ shouldShowStar }">
<div class="form-label">
<span v-if="shouldShowStar" class="form-star">*</span>
问题解决:是否有效解决您的需求?
</div>
<RadioList v-model="formModel.problem_solving" :options="ratingOptions" />
</s-form-item-plain>
</div>
</div>
<div class="section">
<div class="section-title">
<span class="section-index">3</span>
<span>整体评价</span>
</div>
<div class="section-content">
<s-form-item-plain
name="overall_satisfaction"
class="form-item"
#default="{ shouldShowStar }"
>
<div class="form-label">
<span v-if="shouldShowStar" class="form-star">*</span>
本次服务的整体满意度
</div>
<RadioList v-model="formModel.overall_satisfaction" :options="ratingOptions" />
</s-form-item-plain>
<s-form-item-plain name="would_return" class="form-item" #default="{ shouldShowStar }">
<div class="form-label">
<span v-if="shouldShowStar" class="form-star">*</span>
您是否会再次选择该服务人员?
</div>
<RadioList v-model="formModel.would_return" :options="returnOptions" />
</s-form-item-plain>
</div>
</div>
<div class="section">
<div class="section-title">
<span class="section-index">4</span>
<span>开放式反馈</span>
</div>
<div class="section-content">
<s-form-item-plain name="strengths" class="form-item">
<div class="form-label">服务人员的优点(可选)</div>
<div class="form-input">
<s-input
v-model="formModel.strengths"
type="textarea"
placeholder="请输入服务人员的优点"
clearable
inlaid
auto-height
min-height="24px"
/>
</div>
</s-form-item-plain>
<s-form-item-plain name="improvements" class="form-item">
<div class="form-label">需要改进的方面(可选)</div>
<div class="form-input">
<s-input
v-model="formModel.improvements"
type="textarea"
placeholder="请输入需要改进的方面"
clearable
inlaid
auto-height
min-height="24px"
/>
</div>
</s-form-item-plain>
<s-form-item-plain name="suggestions" class="form-item">
<div class="form-label">其他建议(可选)</div>
<div class="form-input">
<s-input
v-model="formModel.suggestions"
type="textarea"
placeholder="请输入其他建议"
clearable
inlaid
auto-height
min-height="24px"
/>
</div>
</s-form-item-plain>
</div>
</div>
<div class="footer">
<s-button block @click="onSubmit">提交评价</s-button>
</div>
</s-form-plain>
</doc-page>
</template>
<script setup lang="js">
import { reactive, ref } from 'vue'
import { toast } from 'sard'
import RadioList from './RadioList.vue'
const formModel = reactive({
staff_name: '',
service_date: '',
contact: '',
professionalism: '',
attitude: '',
response_speed: '',
communication: '',
problem_solving: '',
overall_satisfaction: '',
would_return: '',
strengths: '',
improvements: '',
suggestions: '',
})
const formRules = {
staff_name: {
required: true,
message: '请输入服务人员姓名/工号',
},
service_date: {
required: true,
message: '请选择服务日期',
},
professionalism: {
required: true,
message: '请打分',
},
attitude: {
required: true,
message: '请打分',
},
response_speed: {
required: true,
message: '请打分',
},
communication: {
required: true,
message: '请打分',
},
problem_solving: {
required: true,
message: '请打分',
},
overall_satisfaction: {
required: true,
message: '请选择整体满意度',
},
would_return: {
required: true,
message: '请选择',
},
}
const formRef = ref()
const onSubmit = () => {
formRef.value
?.validate()
.then(() => {
toast('提交成功!')
})
.catch((error) => {
toast(error[0].message)
console.log('error submit!', error)
})
}
const ratingOptions = [
{
value: '1',
label: '不满意',
},
{
value: '2',
label: '一般',
},
{
value: '3',
label: '基本满意',
},
{
value: '4',
label: '非常满意',
},
]
const returnOptions = [
{
value: 'yes',
label: '是',
},
{
value: 'no',
label: '否',
},
{
value: 'unsure',
label: '不确定',
},
]
</script>
<style lang="scss" scoped>
.form-description {
padding: 10px 32px;
font-size: var(--s-font-size-sm);
color: var(--s-text-color-secondary);
text-align: center;
}
.section {
margin-top: 16px;
}
.section-title {
display: flex;
align-items: center;
font-size: var(--s-font-size-lg);
}
.section-index {
display: inline-flex;
justify-content: center;
align-items: center;
width: 16px;
height: 16px;
margin-inline-end: 4px;
font-size: var(--s-font-size-xs);
color: #fff;
background-color: var(--s-color-primary);
}
.section-content {
margin: 0 16px;
}
.form-item {
scroll-margin-top: 60px;
}
.form-label {
margin: 20px 0 5px;
}
.form-star {
color: var(--s-color-danger);
}
.form-input {
margin-top: 10px;
&::after {
display: block;
content: '';
margin-top: 10px;
border-top: 1px solid var(--s-border-color);
transform: scaleY(0.5);
}
}
.footer {
position: sticky;
bottom: 0;
margin-top: 20px;
padding: 10px 16px calc(10px + var(--s-safe-bottom));
border-top: 1px solid var(--s-border-color);
background-color: var(--s-bg-color-elevated);
}
</style>RadioList.vue
<template>
<s-radio-group v-model="value" #default="{ toggle }">
<s-list hide-border>
<s-list-item
v-for="option in options"
:key="option.value"
:title="option.label"
@click="toggle(option.value)"
#icon
>
<s-radio readonly :value="option.value" />
</s-list-item>
</s-list>
</s-radio-group>
</template>
<script setup lang="ts">
const value = defineModel<string>()
defineProps<{
options: {
label: string
value: string
}[]
}>()
</script><template>
<s-radio-group v-model="value" #default="{ toggle }">
<s-list hide-border>
<s-list-item
v-for="option in options"
:key="option.value"
:title="option.label"
@click="toggle(option.value)"
#icon
>
<s-radio readonly :value="option.value" />
</s-list-item>
</s-list>
</s-radio-group>
</template>
<script setup lang="js">
const value = defineModel()
defineProps()
</script>API
FormProps
| 属性 | 描述 | 类型 | 默认值 |
|---|---|---|---|
| model | 表单数据对象 | Record<string, any> | - |
| rules | 表单验证规则 | FormRules | - |
| validate-on-rule-change | 是否在 rules 属性改变后立即触发一次验证 | boolean | true |
| direction | 表单排列方向 | 'horizontal' | 'vertical' | 'horizontal' |
| label-width | 标签宽度 | string | - |
| label-align | 标签水平对齐方式 | 'start' | 'center' | 'end' | 'start' |
| label-valign | 标签垂直对齐方式 | 'start' | 'center' | 'end' | 'center' |
| star-position | 必填星号在标签的左边或右边 | 'start' | 'end' | 'start' |
| hide-star | 是否隐藏必填时的星号 | boolean | false |
| content-position | 内容位置 | 'left' | 'right' | 'left' |
| validate-trigger | 设置字段校验的时机 | TriggerType | 'change' |
| show-error | 是否显示校验错误信息 | boolean | true |
| scroll-to-first-error | 当校验失败时,滚动到第一个错误表单项 | boolean | false |
| scroll-into-view-options | 自定义滚动配置选项 | ScrollIntoViewOptions | - |
| disabled | 是否禁用该表单内的所有组件。如果设置为 true, 它将覆盖内部组件的 disabled 属性 | boolean | false |
| readonly | 是否只读该表单内的所有组件。如果设置为 true, 它将覆盖内部组件的 readonly 属性 | boolean | false |
| card | 是否显示为卡片样式 | boolean | false |
FormSlots
| 插槽 | 描述 | 属性 |
|---|---|---|
| default | 自定义默认内容 | - |
FormExpose
| 属性 | 描述 | 类型 |
|---|---|---|
| validate | 对整个表单的内容进行验证。 | (nameList?: FieldName[]) => Promise\<void> |
| reset | 重置表单项,将其值重置为初始值,并移除校验结果。 | (nameList?: FieldName[]) => Promise\<void> |
| clearValidate | 清理指定字段的表单验证信息。 | (nameList?: FieldName[]) => Promise\<void> |
| scrollToField | 滚动到指定的字段 | (name: FieldName) => void |
FormItemProps
| 属性 | 描述 | 类型 | 默认值 |
|---|---|---|---|
| name | 表单域 model 字段,在使用 validate、reset 方法的情况下,该属性是必填的。 | FieldName | - |
| label | 标签文本 | string | - |
| required | 是否为必填项,如不设置,则会根据校验规则确认 | boolean | - |
| rules | 表单验证规则,会合并表单组件上的验证规则 | Rule | Rule[] | - |
| error | 表单域验证错误时的提示信息。设置该值会导致表单验证状态变为 error,并显示该错误信息。 | string | - |
| inlaid | 去掉边框和内边距,用于嵌入到其他组件中 | boolean | false |
| direction | 表单排列方向,可覆盖表单的全局设置 | 'horizontal' | 'vertical' | - |
| label-width | 标签宽度,可覆盖表单的全局设置 | string | - |
| label-align | 标签水平对齐方式,可覆盖表单的全局设置 | 'start' | 'center' | 'end' | - |
| label-valign | 标签垂直对齐方式,可覆盖表单的全局设置 | 'start' | 'center' | 'end' | - |
| star-position | 必填星号在标签的左边或右边,可覆盖表单的全局设置 | 'left' | 'right' | - |
| hide-star | 是否隐藏必填时的星号,可覆盖表单的全局设置 | boolean | - |
| content-position | 内容位置,可覆盖表单的全局设置 | 'left' | 'right' | - |
| validate-trigger | 设置字段校验的时机,可覆盖表单的全局设置 | TriggerType | - |
| show-error | 是否显示校验错误信息,可覆盖表单的全局设置 | boolean | - |
FormItemSlots
| 插槽 | 描述 | 属性 |
|---|---|---|
| default | 自定义默认内容 | - |
| label | 自定义标签内容 | - |
| validate | 同默认插槽,额外接受当前验证状态属性 | { state: ValidateState } |
| error | 自定义错误信息内容 | { message: string; showError: boolean } |
FormItemExpose
| 属性 | 描述 | 类型 |
|---|---|---|
| validate | 对整个表单的内容进行验证。 | (trigger?: string | string[]) => Promise\<void> |
| reset | 重置该表单项,将其值重置为初始值,并移除校验结果。 | () => Promise\<void> |
| clearValidate | 清理指定字段的表单验证信息。 | () => void |
| scrollToField | 滚动到当前字段 | () => void |
| validateMessage | 当前验证信息 | Ref<string> |
| validateState | 当前验证状态 | Ref<ValidateState> |
FormRules
interface FormRules {
[key: PropertyKey]: Rule | Rule[] | FormRules
}TriggerType
type TriggerType = 'change' | 'blur' | ('change' | 'blur')[]FieldName
type FieldName = string | number | (string | number)[]ValidateState
type ValidateState = '' | 'success' | 'error' | 'validating'Rule
| 属性 | 描述 | 类型 | 默认值 |
|---|---|---|---|
| validator | 使用函数自定义验证,具体说明如下 | (context: ValidateContext) => Promise\<void> | boolean | string | undefined | - |
| pattern | 通过正则来进行校验 | RegExp | - |
| message | 校验失败的反馈文案 | string | (() => string) | - |
| trigger | 触发校验的时机 | string | string[] | - |
| transform | 将值转换后再进行校验 | (value: any) => any | - |
| type | 使用内置的校验规则 | ValidatorType | 'string' |
| enum | 是否匹配枚举中的值(需要将 type 设置为 enum) | (string | number)[] | - |
| len | 当 type 为字符串(字符串长度)、数值(等于数值)、数组(数组长度)时有效 | number | - |
| min | 当 type 为字符串(字符串最小长度)、数值(最小值)、数组(数组最小长度)时有效 | number | - |
| max | 当 type 为字符串(字符串最大长度)、数值(最大值)、数组(数组最大长度)时有效 | number | - |
| required | 是否为必选字段,当值为空值时 '', [], false, undefined, null,校验不通过 | boolean | false |
| whitespace | type 为 'string' 时,如果字段仅包含空格则校验不通过 | boolean | false |
Rule['validator'] 说明
validator 接收一个 ValidateContext 对象作为唯一参数,其中包含 value、rule 与 signal。
当函数返回值为 fulfilled 状态的 Promise 或者 true 则验证通过,否则验证不通过; Promise.reject 的参数或者返回的字符串会作为错误验证信息,如果返回的不是字符串,则取 Rule['message'] 的配置作为错误信息。
当本次校验被新一轮校验顶替时,signal 会触发 abort;表单内部会自动以 CancelError 结束本次校验(不改变校验状态、不计入错误)。validator 只需在 signal 的 abort 回调中清理资源 (如清除定时器、中止底层请求),无需手动 reject。
ValidateContext
interface ValidateContext {
value: any
rule: Rule
signal?: AbortSignal
}CancelError
校验被新一轮校验顶替时用于结束当前校验的错误类型,可由 sard 直接导入:
import { CancelError } from 'sard'ValidatorType
| 类型 | 描述 |
|---|---|
| string | 必须为字符串 |
| number | 必须为数值 |
| boolean | 必须为布尔值 |
| function | 必须为函数 |
| regexp | 必须为 RegExp 类型,或者作为 RegExp() 参数被实例化不会报错的字符串 |
| integer | 必须为数值,且为整数 |
| float | 必须为数值,且为小数 |
| array | 必须为数组 |
| object | 必须为对象,且不为数组和 null |
| enum | 必须存在于 Rule['enum'] 中 |
| date | 必须为 Date 类型 |
| url | 必须为 url |
| hex | 必须为 hex |
| 必须为邮件 |
FormPlainProps
继承 FormProps。
FormPlainSlots
继承 FormSlots。
FormPlainExpose
继承 FormExpose。
FormItemPlainProps
继承 FormItemProps。
FormItemPlainSlots
| 插槽 | 描述 | 属性 |
|---|---|---|
| default | 自定义默认内容 | FormItemPlainSlotsProps |
FormItemPlainSlotsProps
| 属性 | 描述 | 类型 |
|---|---|---|
| validateState | 表单验证状态 | ValidateState |
| shouldShowStar | 是否显示星号 | boolean |
| validateMessage | 当前验证信息 | string |
| shouldShowError | 是否显示错误信息 | boolean |
| direction | 表单排列方向 | FormProps['direction'] |
| labelAlign | 标签水平对齐方式 | FormProps['labelAlign'] |
| labelValign | 标签垂直对齐方式 | FormProps['labelValign'] |
| starPosition | 星号位置 | FormProps['starPosition'] |
| labelWidth | 标签宽度 | FormProps['labelWidth'] |
FormItemPlainExpose
继承 FormItemExpose。
主题定制
样式变量
| CSS 变量 | 值 |
|---|---|
--s-form-bg | var(--s-bg-color-container) |
--s-form-border-radius-card | var(--s-border-radius-lg) |
--s-form-item-min-height | var(--s-content-height) |
--s-form-item-padding-x | var(--s-content-padding-x) |
--s-form-item-padding-y | var(--s-content-padding-y) |
--s-form-item-border-color | var(--s-border-color) |
--s-form-item-label-width | 7em |
--s-form-item-label-margin-end | var(--s-size-sm) |
--s-form-item-label-margin-bottom | var(--s-size-2xs) |
--s-form-item-label-font-size | var(--s-font-size) |
--s-form-item-label-line-height | var(--s-line-height-normal) |
--s-form-item-star-font-size | var(--s-font-size) |
--s-form-item-star-line-height | var(--s-line-height-normal) |
--s-form-item-star-color | var(--s-color-danger) |
--s-form-item-star-gap | var(--s-size-2xs) |
--s-form-item-error-margin-top | var(--s-size-2xs) |
--s-form-item-error-font-size | var(--s-font-size-sm) |
--s-form-item-error-line-height | var(--s-line-height-tight) |
--s-form-item-error-color | var(--s-color-danger) |