介绍
用于数据采集,由各种类型的表单域组成,可对数据进行校验、清除、重置、提交等操作。
引入
import Form from 'sard-uniapp/components/form/form.vue'
import FormItem from 'sard-uniapp/components/form-item/form-item.vue'
import FormPlain from 'sard-uniapp/components/form-plain/form-plain.vue'
import FormItemPlain from 'sard-uniapp/components/form-item-plain/form-item-plain.vue'代码演示
典型表单
最基础的表单包括各种输入表单项,比如 input、picker-input、radio、checkbox 等。
在每一个 form 组件中,你需要一个 form-item 字段作为输入项的容器,用于获取值与验证值。
<template>
<page-meta :page-style="isLocked ? 'overflow: hidden' : ''"></page-meta>
<doc-page title="典型表单">
<sar-form :model="form" label-width="240rpx">
<sar-form-item label="Activity name">
<sar-input
inlaid
v-model="form.name"
placeholder="Please input Activity name"
/>
</sar-form-item>
<sar-form-item label="Activity zone">
<sar-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',
},
]"
/>
</sar-form-item>
<sar-form-item label="Activity time">
<sar-datetime-picker-input
v-model="form.date1"
type="yMd"
placeholder="Pick a date"
/>
</sar-form-item>
<sar-form-item label="">
<sar-datetime-picker-input
v-model="form.date2"
type="hms"
placeholder="Pick a time"
/>
</sar-form-item>
<sar-form-item label="Active date range">
<sar-datetime-range-picker-input
v-model="form.date3"
type="hms"
:tabs="['start', 'end']"
placeholder="Pick a time"
/>
</sar-form-item>
<sar-form-item label="Instant delivery">
<sar-switch v-model="form.delivery" />
</sar-form-item>
<sar-form-item label="Activity type">
<sar-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' },
]"
/>
</sar-form-item>
<sar-form-item label="Resources">
<sar-radio-input
v-model="form.resource"
placeholder="Pick Resources"
:options="[
{ label: 'Sponsor', value: 'Sponsor' },
{ label: 'Venue', value: 'Venue' },
]"
/>
</sar-form-item>
<sar-form-item label="Activity form" label-valign="start">
<sar-input
inlaid
v-model="form.desc"
type="textarea"
placeholder="Please input Activity form"
show-count
/>
</sar-form-item>
<sar-form-item>
<sar-button @click="onSubmit">Create</sar-button>
<sar-button root-style="margin-top: 20rpx" type="outline">
Cancel
</sar-button>
</sar-form-item>
</sar-form>
</doc-page>
</template>
<script setup lang="ts">
import { useCurrentPageLock, usePageTopPopup } from 'sard-uniapp'
import { onBackPress } from '@dcloudio/uni-app'
import { reactive } from 'vue'
import { toast } from 'sard-uniapp'
// 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!')
}
const { isLocked } = useCurrentPageLock()
const { shouldStopBack, hidePopup } = usePageTopPopup()
onBackPress(() => {
if (shouldStopBack.value) {
hidePopup()
return true
}
})
</script><template>
<page-meta :page-style="isLocked ? 'overflow: hidden' : ''"></page-meta>
<doc-page title="典型表单">
<sar-form :model="form" label-width="240rpx">
<sar-form-item label="Activity name">
<sar-input
inlaid
v-model="form.name"
placeholder="Please input Activity name"
/>
</sar-form-item>
<sar-form-item label="Activity zone">
<sar-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',
},
]"
/>
</sar-form-item>
<sar-form-item label="Activity time">
<sar-datetime-picker-input
v-model="form.date1"
type="yMd"
placeholder="Pick a date"
/>
</sar-form-item>
<sar-form-item label="">
<sar-datetime-picker-input
v-model="form.date2"
type="hms"
placeholder="Pick a time"
/>
</sar-form-item>
<sar-form-item label="Active date range">
<sar-datetime-range-picker-input
v-model="form.date3"
type="hms"
:tabs="['start', 'end']"
placeholder="Pick a time"
/>
</sar-form-item>
<sar-form-item label="Instant delivery">
<sar-switch v-model="form.delivery" />
</sar-form-item>
<sar-form-item label="Activity type">
<sar-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' },
]"
/>
</sar-form-item>
<sar-form-item label="Resources">
<sar-radio-input
v-model="form.resource"
placeholder="Pick Resources"
:options="[
{ label: 'Sponsor', value: 'Sponsor' },
{ label: 'Venue', value: 'Venue' },
]"
/>
</sar-form-item>
<sar-form-item label="Activity form" label-valign="start">
<sar-input
inlaid
v-model="form.desc"
type="textarea"
placeholder="Please input Activity form"
show-count
/>
</sar-form-item>
<sar-form-item>
<sar-button @click="onSubmit">Create</sar-button>
<sar-button root-style="margin-top: 20rpx" type="outline">
Cancel
</sar-button>
</sar-form-item>
</sar-form>
</doc-page>
</template>
<script setup lang="js">
import { useCurrentPageLock, usePageTopPopup } from "sard-uniapp";
import { onBackPress } from "@dcloudio/uni-app";
import { reactive } from "vue";
import { toast } from "sard-uniapp";
const form = reactive({
name: "",
region: "",
date1: void 0,
date2: void 0,
date3: void 0,
delivery: false,
type: [],
resource: "",
desc: ""
});
const onSubmit = () => {
toast("submit!");
};
const { isLocked } = useCurrentPageLock();
const { shouldStopBack, hidePopup } = usePageTopPopup();
onBackPress(() => {
if (shouldStopBack.value) {
hidePopup();
return true;
}
});
</script>方向与对齐
使用 direction 设置表单域标签水平或垂直排列; 使用 label-align 或 label-valign 设置标签表单域标签水平或垂直方向的对齐方式; 使用 star-position 属性设置星号居左或居右。
<template>
<page-meta :page-style="isLocked ? 'overflow: hidden' : ''"></page-meta>
<doc-page title="方向与对齐">
<doc-demo>
<doc-title>排列方向</doc-title>
<sar-radio-group v-model="direction" direction="horizontal">
<sar-radio value="vertical">vertical</sar-radio>
<sar-radio value="horizontal">horizontal</sar-radio>
</sar-radio-group>
<doc-title>水平对齐</doc-title>
<sar-radio-group v-model="labelAlign" direction="horizontal">
<sar-radio value="start">start</sar-radio>
<sar-radio value="center">center</sar-radio>
<sar-radio value="end">end</sar-radio>
</sar-radio-group>
<doc-title>垂直对齐</doc-title>
<sar-radio-group v-model="labelValign" direction="horizontal">
<sar-radio value="start">start</sar-radio>
<sar-radio value="center">center</sar-radio>
<sar-radio value="end">end</sar-radio>
</sar-radio-group>
<doc-title>星号位置</doc-title>
<sar-radio-group v-model="starPosition" direction="horizontal">
<sar-radio value="left">left</sar-radio>
<sar-radio value="right">right</sar-radio>
</sar-radio-group>
</doc-demo>
<doc-demo>
<sar-form
label-width="200rpx"
:direction="direction"
:label-align="labelAlign"
:label-valign="labelValign"
:star-position="starPosition"
:model="formLabelAlign"
card
>
<sar-form-item label="Name" required>
<sar-input v-model="formLabelAlign.name" />
</sar-form-item>
<sar-form-item label="Activity zone" required>
<sar-input v-model="formLabelAlign.region" />
</sar-form-item>
<sar-form-item label="Activity form" required>
<sar-input v-model="formLabelAlign.type" type="textarea" />
</sar-form-item>
</sar-form>
</doc-demo>
</doc-page>
</template>
<script setup lang="ts">
import { useCurrentPageLock, usePageTopPopup } from 'sard-uniapp'
import { onBackPress } from '@dcloudio/uni-app'
import { reactive, ref } from 'vue'
const labelAlign = ref<any>('start')
const labelValign = ref<any>('center')
const direction = ref<any>('horizontal')
const starPosition = ref<any>('left')
const formLabelAlign = reactive({
name: '',
region: '',
type: '',
})
const { isLocked } = useCurrentPageLock()
const { shouldStopBack, hidePopup } = usePageTopPopup()
onBackPress(() => {
if (shouldStopBack.value) {
hidePopup()
return true
}
})
</script><template>
<page-meta :page-style="isLocked ? 'overflow: hidden' : ''"></page-meta>
<doc-page title="方向与对齐">
<doc-demo>
<doc-title>排列方向</doc-title>
<sar-radio-group v-model="direction" direction="horizontal">
<sar-radio value="vertical">vertical</sar-radio>
<sar-radio value="horizontal">horizontal</sar-radio>
</sar-radio-group>
<doc-title>水平对齐</doc-title>
<sar-radio-group v-model="labelAlign" direction="horizontal">
<sar-radio value="start">start</sar-radio>
<sar-radio value="center">center</sar-radio>
<sar-radio value="end">end</sar-radio>
</sar-radio-group>
<doc-title>垂直对齐</doc-title>
<sar-radio-group v-model="labelValign" direction="horizontal">
<sar-radio value="start">start</sar-radio>
<sar-radio value="center">center</sar-radio>
<sar-radio value="end">end</sar-radio>
</sar-radio-group>
<doc-title>星号位置</doc-title>
<sar-radio-group v-model="starPosition" direction="horizontal">
<sar-radio value="left">left</sar-radio>
<sar-radio value="right">right</sar-radio>
</sar-radio-group>
</doc-demo>
<doc-demo>
<sar-form
label-width="200rpx"
:direction="direction"
:label-align="labelAlign"
:label-valign="labelValign"
:star-position="starPosition"
:model="formLabelAlign"
card
>
<sar-form-item label="Name" required>
<sar-input v-model="formLabelAlign.name" />
</sar-form-item>
<sar-form-item label="Activity zone" required>
<sar-input v-model="formLabelAlign.region" />
</sar-form-item>
<sar-form-item label="Activity form" required>
<sar-input v-model="formLabelAlign.type" type="textarea" />
</sar-form-item>
</sar-form>
</doc-demo>
</doc-page>
</template>
<script setup lang="js">
import { useCurrentPageLock, usePageTopPopup } from "sard-uniapp";
import { onBackPress } from "@dcloudio/uni-app";
import { reactive, ref } from "vue";
const labelAlign = ref("start");
const labelValign = ref("center");
const direction = ref("horizontal");
const starPosition = ref("left");
const formLabelAlign = reactive({
name: "",
region: "",
type: ""
});
const { isLocked } = useCurrentPageLock();
const { shouldStopBack, hidePopup } = usePageTopPopup();
onBackPress(() => {
if (shouldStopBack.value) {
hidePopup();
return true;
}
});
</script>表单校验
Form 组件允许你验证用户的输入是否符合规范,来帮助你找到和纠正错误。
Form 组件提供了表单验证的功能,只需为 rules 属性传入约定的验证规则,并将 FormItem 的 name 属性设置为需要验证的特殊键值即可。
<template>
<page-meta :page-style="isLocked ? 'overflow: hidden' : ''"></page-meta>
<doc-page title="表单校验">
<sar-form ref="ruleFormRef" :model="ruleForm" :rules="rules">
<sar-form-item label="Activity name" name="name">
<sar-input
v-model="ruleForm.name"
clearable
inlaid
placeholder="Activity name"
/>
</sar-form-item>
<sar-form-item label="Activity zone" name="region">
<sar-picker-input
v-model="ruleForm.region"
clearable
placeholder="Activity zone"
:columns="[
{ label: 'Zone one', value: 'shanghai' },
{ label: 'Zone two', value: 'beijing' },
]"
/>
</sar-form-item>
<sar-form-item label="Activity count" name="count">
<sar-picker-input
v-model="ruleForm.count"
clearable
placeholder="Activity count"
:columns="options"
/>
</sar-form-item>
<sar-form-item label="Activity time" required name="date1">
<sar-datetime-picker-input
v-model="ruleForm.date1"
clearable
type="yMd"
placeholder="Pick a date"
/>
</sar-form-item>
<sar-form-item label="" hide-star name="date2">
<sar-datetime-picker-input
v-model="ruleForm.date2"
clearable
type="hms"
placeholder="Pick a time"
/>
</sar-form-item>
<sar-form-item label="Instant delivery" name="delivery">
<sar-switch v-model="ruleForm.delivery" />
</sar-form-item>
<sar-form-item label="Activity type" name="type">
<sar-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' },
]"
/>
</sar-form-item>
<sar-form-item label="Resources" name="resource">
<sar-radio-input
v-model="ruleForm.resource"
clearable
placeholder="Pick Resources"
:options="[
{ label: 'Sponsor', value: 'Sponsor' },
{ label: 'Venue', value: 'Venue' },
]"
/>
</sar-form-item>
<sar-form-item label="Activity form" name="desc">
<sar-input
v-model="ruleForm.desc"
clearable
type="textarea"
inlaid
placeholder="Activity form"
/>
</sar-form-item>
<sar-form-item>
<sar-button @click="submitForm(ruleFormRef)">Create</sar-button>
<sar-button
type="outline"
@click="resetForm(ruleFormRef)"
root-style="margin-top: 20rpx"
>
Reset
</sar-button>
</sar-form-item>
</sar-form>
</doc-page>
</template>
<script setup lang="ts">
import { useCurrentPageLock, usePageTopPopup } from 'sard-uniapp'
import { onBackPress } from '@dcloudio/uni-app'
import { reactive, ref } from 'vue'
import {
toast,
type FormRules,
type FormExpose,
type FieldValidateError,
} from 'sard-uniapp'
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}`,
}))
const { isLocked } = useCurrentPageLock()
const { shouldStopBack, hidePopup } = usePageTopPopup()
onBackPress(() => {
if (shouldStopBack.value) {
hidePopup()
return true
}
})
</script><template>
<page-meta :page-style="isLocked ? 'overflow: hidden' : ''"></page-meta>
<doc-page title="表单校验">
<sar-form ref="ruleFormRef" :model="ruleForm" :rules="rules">
<sar-form-item label="Activity name" name="name">
<sar-input
v-model="ruleForm.name"
clearable
inlaid
placeholder="Activity name"
/>
</sar-form-item>
<sar-form-item label="Activity zone" name="region">
<sar-picker-input
v-model="ruleForm.region"
clearable
placeholder="Activity zone"
:columns="[
{ label: 'Zone one', value: 'shanghai' },
{ label: 'Zone two', value: 'beijing' },
]"
/>
</sar-form-item>
<sar-form-item label="Activity count" name="count">
<sar-picker-input
v-model="ruleForm.count"
clearable
placeholder="Activity count"
:columns="options"
/>
</sar-form-item>
<sar-form-item label="Activity time" required name="date1">
<sar-datetime-picker-input
v-model="ruleForm.date1"
clearable
type="yMd"
placeholder="Pick a date"
/>
</sar-form-item>
<sar-form-item label="" hide-star name="date2">
<sar-datetime-picker-input
v-model="ruleForm.date2"
clearable
type="hms"
placeholder="Pick a time"
/>
</sar-form-item>
<sar-form-item label="Instant delivery" name="delivery">
<sar-switch v-model="ruleForm.delivery" />
</sar-form-item>
<sar-form-item label="Activity type" name="type">
<sar-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' },
]"
/>
</sar-form-item>
<sar-form-item label="Resources" name="resource">
<sar-radio-input
v-model="ruleForm.resource"
clearable
placeholder="Pick Resources"
:options="[
{ label: 'Sponsor', value: 'Sponsor' },
{ label: 'Venue', value: 'Venue' },
]"
/>
</sar-form-item>
<sar-form-item label="Activity form" name="desc">
<sar-input
v-model="ruleForm.desc"
clearable
type="textarea"
inlaid
placeholder="Activity form"
/>
</sar-form-item>
<sar-form-item>
<sar-button @click="submitForm(ruleFormRef)">Create</sar-button>
<sar-button
type="outline"
@click="resetForm(ruleFormRef)"
root-style="margin-top: 20rpx"
>
Reset
</sar-button>
</sar-form-item>
</sar-form>
</doc-page>
</template>
<script setup lang="js">
import { useCurrentPageLock, usePageTopPopup } from "sard-uniapp";
import { onBackPress } from "@dcloudio/uni-app";
import { reactive, ref } from "vue";
import {
toast
} from "sard-uniapp";
const ruleFormRef = ref();
const ruleForm = reactive({
name: "Hello",
region: "",
count: "",
date1: void 0,
date2: void 0,
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}`
}));
const { isLocked } = useCurrentPageLock();
const { shouldStopBack, hidePopup } = usePageTopPopup();
onBackPress(() => {
if (shouldStopBack.value) {
hidePopup();
return true;
}
});
</script>自定义校验规则
这个例子中展示了如何使用自定义验证规则来完成密码的二次验证。
本例还使用 validate 插槽为输入框添加了表示校验中状态的反馈图标。
<template>
<page-meta :page-style="isLocked ? 'overflow: hidden' : ''"></page-meta>
<doc-page title="自定义校验规则">
<sar-form ref="ruleFormRef" :model="ruleForm" :rules="rules">
<sar-form-item label="Password" name="pass">
<sar-input v-model="ruleForm.pass" type="password" />
</sar-form-item>
<sar-form-item label="Confirm" name="checkPass">
<sar-input v-model="ruleForm.checkPass" type="password" />
</sar-form-item>
<sar-form-item label="Age" name="age">
<template #validate="{ state }">
<sar-input v-model="ruleForm.age">
<template #append>
<sar-loading
v-if="state === 'validating'"
color="var(--sar-tertiary-color)"
/>
</template>
</sar-input>
</template>
</sar-form-item>
<sar-form-item>
<sar-button @click="submitForm(ruleFormRef)">Submit</sar-button>
<sar-button
type="outline"
root-style="margin-top: 20rpx"
@click="resetForm(ruleFormRef)"
>
Reset
</sar-button>
</sar-form-item>
</sar-form>
</doc-page>
</template>
<script setup lang="ts">
import { useCurrentPageLock, usePageTopPopup } from 'sard-uniapp'
import { onBackPress } from '@dcloudio/uni-app'
import { reactive, ref } from 'vue'
import { toast, type FormRules, type FormExpose } from 'sard-uniapp'
const ruleFormRef = ref<FormExpose>()
const checkAge = (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: 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: 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()
}
const { isLocked } = useCurrentPageLock()
const { shouldStopBack, hidePopup } = usePageTopPopup()
onBackPress(() => {
if (shouldStopBack.value) {
hidePopup()
return true
}
})
</script><template>
<page-meta :page-style="isLocked ? 'overflow: hidden' : ''"></page-meta>
<doc-page title="自定义校验规则">
<sar-form ref="ruleFormRef" :model="ruleForm" :rules="rules">
<sar-form-item label="Password" name="pass">
<sar-input v-model="ruleForm.pass" type="password" />
</sar-form-item>
<sar-form-item label="Confirm" name="checkPass">
<sar-input v-model="ruleForm.checkPass" type="password" />
</sar-form-item>
<sar-form-item label="Age" name="age">
<template #validate="{ state }">
<sar-input v-model="ruleForm.age">
<template #append>
<sar-loading
v-if="state === 'validating'"
color="var(--sar-tertiary-color)"
/>
</template>
</sar-input>
</template>
</sar-form-item>
<sar-form-item>
<sar-button @click="submitForm(ruleFormRef)">Submit</sar-button>
<sar-button
type="outline"
root-style="margin-top: 20rpx"
@click="resetForm(ruleFormRef)"
>
Reset
</sar-button>
</sar-form-item>
</sar-form>
</doc-page>
</template>
<script setup lang="js">
import { useCurrentPageLock, usePageTopPopup } from "sard-uniapp";
import { onBackPress } from "@dcloudio/uni-app";
import { reactive, ref } from "vue";
import { toast } from "sard-uniapp";
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();
};
const { isLocked } = useCurrentPageLock();
const { shouldStopBack, hidePopup } = usePageTopPopup();
onBackPress(() => {
if (shouldStopBack.value) {
hidePopup();
return true;
}
});
</script>添加/删除表单项
除了一次通过表单组件上的所有验证规则外. 您也可以动态地通过验证规则或删除单个表单字段的规则。
<template>
<page-meta :page-style="isLocked ? 'overflow: hidden' : ''"></page-meta>
<doc-page title="添加/删除表单项">
<sar-form ref="formRef" :model="dynamicValidateForm">
<sar-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'],
},
]"
>
<sar-input v-model="dynamicValidateForm.email" />
</sar-form-item>
<sar-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',
}"
>
<view style="display: flex; flex-direction: row; gap: 10rpx">
<sar-input v-model="domain.value" />
<sar-button
type="text"
theme="danger"
icon="trash"
@click.prevent="removeDomain(domain)"
/>
</view>
</sar-form-item>
<sar-form-item>
<sar-button @click="submitForm(formRef)">Submit</sar-button>
<sar-button
type="outline"
root-style="margin-top: 20rpx"
@click="addDomain"
>
New domain
</sar-button>
<sar-button
type="outline"
root-style="margin-top: 20rpx"
@click="resetForm(formRef)"
>
Reset
</sar-button>
</sar-form-item>
</sar-form>
</doc-page>
</template>
<script setup lang="ts">
import { useCurrentPageLock, usePageTopPopup } from 'sard-uniapp'
import { onBackPress } from '@dcloudio/uni-app'
import { reactive, ref } from 'vue'
import { toast, type FormExpose } from 'sard-uniapp'
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()
}
const { isLocked } = useCurrentPageLock()
const { shouldStopBack, hidePopup } = usePageTopPopup()
onBackPress(() => {
if (shouldStopBack.value) {
hidePopup()
return true
}
})
</script><template>
<page-meta :page-style="isLocked ? 'overflow: hidden' : ''"></page-meta>
<doc-page title="添加/删除表单项">
<sar-form ref="formRef" :model="dynamicValidateForm">
<sar-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'],
},
]"
>
<sar-input v-model="dynamicValidateForm.email" />
</sar-form-item>
<sar-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',
}"
>
<view style="display: flex; flex-direction: row; gap: 10rpx">
<sar-input v-model="domain.value" />
<sar-button
type="text"
theme="danger"
icon="trash"
@click.prevent="removeDomain(domain)"
/>
</view>
</sar-form-item>
<sar-form-item>
<sar-button @click="submitForm(formRef)">Submit</sar-button>
<sar-button
type="outline"
root-style="margin-top: 20rpx"
@click="addDomain"
>
New domain
</sar-button>
<sar-button
type="outline"
root-style="margin-top: 20rpx"
@click="resetForm(formRef)"
>
Reset
</sar-button>
</sar-form-item>
</sar-form>
</doc-page>
</template>
<script setup lang="js">
import { useCurrentPageLock, usePageTopPopup } from "sard-uniapp";
import { onBackPress } from "@dcloudio/uni-app";
import { reactive, ref } from "vue";
import { toast } from "sard-uniapp";
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();
};
const { isLocked } = useCurrentPageLock();
const { shouldStopBack, hidePopup } = usePageTopPopup();
onBackPress(() => {
if (shouldStopBack.value) {
hidePopup();
return true;
}
});
</script>简单登录框
基本的表单数据域控制展示,包含布局、初始化、验证、提交。
<template>
<page-meta :page-style="isLocked ? 'overflow: hidden' : ''"></page-meta>
<doc-page title="简单登录框">
<sar-form :model="formState" ref="formRef">
<sar-form-item
label="Username"
name="username"
:rules="[{ required: true, message: 'Please input your username!' }]"
>
<sar-input v-model="formState.username" />
</sar-form-item>
<sar-form-item
label="Password"
name="password"
:rules="[{ required: true, message: 'Please input your password!' }]"
>
<sar-input type="password" v-model="formState.password" />
</sar-form-item>
<sar-form-item label="" name="remember">
<sar-checkbox v-model:checked="formState.remember">
Remember me
</sar-checkbox>
</sar-form-item>
<sar-form-item>
<sar-button @click="submitForm">Submit</sar-button>
</sar-form-item>
</sar-form>
</doc-page>
</template>
<script setup lang="ts">
import { useCurrentPageLock, usePageTopPopup } from 'sard-uniapp'
import { onBackPress } from '@dcloudio/uni-app'
import { ref, reactive, toRaw } from 'vue'
import { toast, type FormExpose, type FieldValidateError } from 'sard-uniapp'
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)
})
}
const { isLocked } = useCurrentPageLock()
const { shouldStopBack, hidePopup } = usePageTopPopup()
onBackPress(() => {
if (shouldStopBack.value) {
hidePopup()
return true
}
})
</script><template>
<page-meta :page-style="isLocked ? 'overflow: hidden' : ''"></page-meta>
<doc-page title="简单登录框">
<sar-form :model="formState" ref="formRef">
<sar-form-item
label="Username"
name="username"
:rules="[{ required: true, message: 'Please input your username!' }]"
>
<sar-input v-model="formState.username" />
</sar-form-item>
<sar-form-item
label="Password"
name="password"
:rules="[{ required: true, message: 'Please input your password!' }]"
>
<sar-input type="password" v-model="formState.password" />
</sar-form-item>
<sar-form-item label="" name="remember">
<sar-checkbox v-model:checked="formState.remember">
Remember me
</sar-checkbox>
</sar-form-item>
<sar-form-item>
<sar-button @click="submitForm">Submit</sar-button>
</sar-form-item>
</sar-form>
</doc-page>
</template>
<script setup lang="js">
import { useCurrentPageLock, usePageTopPopup } from "sard-uniapp";
import { onBackPress } from "@dcloudio/uni-app";
import { ref, reactive, toRaw } from "vue";
import { toast } from "sard-uniapp";
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);
});
};
const { isLocked } = useCurrentPageLock();
const { shouldStopBack, hidePopup } = usePageTopPopup();
onBackPress(() => {
if (shouldStopBack.value) {
hidePopup();
return true;
}
});
</script>Label 宽度
通过 label-width 属性设置标签宽度。
<template>
<page-meta :page-style="isLocked ? 'overflow: hidden' : ''"></page-meta>
<doc-page title="Label 宽度">
<sar-form :model="formState" ref="formRef" label-width="200rpx">
<sar-form-item label="Activity name">
<sar-input v-model="formState.name" />
</sar-form-item>
<sar-form-item label="Instant delivery">
<sar-switch v-model="formState.delivery" />
</sar-form-item>
<sar-form-item label="Activity type">
<sar-checkbox-group v-model="formState.type">
<sar-checkbox value="1">Online</sar-checkbox>
<sar-checkbox value="2">Promotion</sar-checkbox>
<sar-checkbox value="3">Offline</sar-checkbox>
</sar-checkbox-group>
</sar-form-item>
<sar-form-item label="Resources">
<sar-radio-group v-model="formState.resource">
<sar-radio value="1">Sponsor</sar-radio>
<sar-radio value="2">Venue</sar-radio>
</sar-radio-group>
</sar-form-item>
<sar-form-item label="Activity form">
<sar-input type="textarea" v-model="formState.desc" />
</sar-form-item>
<sar-form-item>
<sar-button @click="onSubmit">Create</sar-button>
<sar-button type="outline" root-style="margin-top: 20rpx">
Cancel
</sar-button>
</sar-form-item>
</sar-form>
</doc-page>
</template>
<script setup lang="ts">
import { useCurrentPageLock, usePageTopPopup } from 'sard-uniapp'
import { onBackPress } from '@dcloudio/uni-app'
import { reactive, toRaw, ref, type UnwrapRef } from 'vue'
import { type FormExpose } from 'sard-uniapp'
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))
})
}
const { isLocked } = useCurrentPageLock()
const { shouldStopBack, hidePopup } = usePageTopPopup()
onBackPress(() => {
if (shouldStopBack.value) {
hidePopup()
return true
}
})
</script><template>
<page-meta :page-style="isLocked ? 'overflow: hidden' : ''"></page-meta>
<doc-page title="Label 宽度">
<sar-form :model="formState" ref="formRef" label-width="200rpx">
<sar-form-item label="Activity name">
<sar-input v-model="formState.name" />
</sar-form-item>
<sar-form-item label="Instant delivery">
<sar-switch v-model="formState.delivery" />
</sar-form-item>
<sar-form-item label="Activity type">
<sar-checkbox-group v-model="formState.type">
<sar-checkbox value="1">Online</sar-checkbox>
<sar-checkbox value="2">Promotion</sar-checkbox>
<sar-checkbox value="3">Offline</sar-checkbox>
</sar-checkbox-group>
</sar-form-item>
<sar-form-item label="Resources">
<sar-radio-group v-model="formState.resource">
<sar-radio value="1">Sponsor</sar-radio>
<sar-radio value="2">Venue</sar-radio>
</sar-radio-group>
</sar-form-item>
<sar-form-item label="Activity form">
<sar-input type="textarea" v-model="formState.desc" />
</sar-form-item>
<sar-form-item>
<sar-button @click="onSubmit">Create</sar-button>
<sar-button type="outline" root-style="margin-top: 20rpx">
Cancel
</sar-button>
</sar-form-item>
</sar-form>
</doc-page>
</template>
<script setup lang="js">
import { useCurrentPageLock, usePageTopPopup } from "sard-uniapp";
import { onBackPress } from "@dcloudio/uni-app";
import { reactive, toRaw, ref } from "vue";
const formRef = ref();
const formState = reactive({
name: "",
delivery: false,
type: [],
resource: "",
desc: ""
});
const onSubmit = () => {
formRef.value?.validate().then(() => {
console.log("submit!", toRaw(formState));
});
};
const { isLocked } = useCurrentPageLock();
const { shouldStopBack, hidePopup } = usePageTopPopup();
onBackPress(() => {
if (shouldStopBack.value) {
hidePopup();
return true;
}
});
</script>表单只读和禁用
设置表单组件禁用或只读,仅对 sard 组件有效。
<template>
<page-meta :page-style="isLocked ? 'overflow: hidden' : ''"></page-meta>
<doc-page title="表单只读和禁用">
<sar-list>
<sar-list-item title="只读">
<template #value>
<sar-switch v-model="formReadonly" />
</template>
</sar-list-item>
<sar-list-item title="禁用">
<template #value>
<sar-switch v-model="formDisabled" />
</template>
</sar-list-item>
</sar-list>
<sar-form
:disabled="formDisabled"
:readonly="formReadonly"
label-width="200rpx"
>
<sar-form-item label="Checkbox">
<sar-checkbox-group v-model="checkboxValue">
<sar-checkbox value="apple">Apple</sar-checkbox>
<sar-checkbox value="pear">Pear</sar-checkbox>
</sar-checkbox-group>
</sar-form-item>
<sar-form-item label="Radio">
<sar-radio-group v-model="radioValue">
<sar-radio value="apple">Apple</sar-radio>
<sar-radio value="pear">Pear</sar-radio>
</sar-radio-group>
</sar-form-item>
<sar-form-item label="Input">
<sar-input />
</sar-form-item>
<sar-form-item label="Picker">
<sar-picker-input
placeholder="Picker"
:columns="[
{
label: 'Demo',
value: 'demo',
},
]"
/>
</sar-form-item>
<sar-form-item label="Cascader">
<sar-cascader-input placeholder="Cascader" :options="options" />
</sar-form-item>
<sar-form-item label="DatetimePicker">
<sar-datetime-picker-input placeholder="DatetimePicker" />
</sar-form-item>
<sar-form-item label="Calendar">
<sar-calendar-input placeholder="Calendar" type="range" />
</sar-form-item>
<sar-form-item label="Stepper">
<sar-stepper />
</sar-form-item>
<sar-form-item label="Input[Textarea]">
<sar-input type="textarea" />
</sar-form-item>
<sar-form-item label="PasswordInput">
<sar-password-input
gap="0"
model-value="123"
root-style="height: 80rpx"
/>
</sar-form-item>
<sar-form-item label="Switch">
<sar-switch :model-value="true" />
</sar-form-item>
<sar-form-item label="Rate">
<sar-rate :model-value="3" />
</sar-form-item>
<sar-form-item label="Slider">
<sar-slider :model-value="50" />
</sar-form-item>
<sar-form-item label="Upload">
<sar-upload />
</sar-form-item>
<sar-form-item label="Button">
<sar-button>Button</sar-button>
</sar-form-item>
</sar-form>
</doc-page>
</template>
<script setup lang="ts">
import { useCurrentPageLock, usePageTopPopup } from 'sard-uniapp'
import { onBackPress } from '@dcloudio/uni-app'
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',
},
],
},
])
const { isLocked } = useCurrentPageLock()
const { shouldStopBack, hidePopup } = usePageTopPopup()
onBackPress(() => {
if (shouldStopBack.value) {
hidePopup()
return true
}
})
</script><template>
<page-meta :page-style="isLocked ? 'overflow: hidden' : ''"></page-meta>
<doc-page title="表单只读和禁用">
<sar-list>
<sar-list-item title="只读">
<template #value>
<sar-switch v-model="formReadonly" />
</template>
</sar-list-item>
<sar-list-item title="禁用">
<template #value>
<sar-switch v-model="formDisabled" />
</template>
</sar-list-item>
</sar-list>
<sar-form
:disabled="formDisabled"
:readonly="formReadonly"
label-width="200rpx"
>
<sar-form-item label="Checkbox">
<sar-checkbox-group v-model="checkboxValue">
<sar-checkbox value="apple">Apple</sar-checkbox>
<sar-checkbox value="pear">Pear</sar-checkbox>
</sar-checkbox-group>
</sar-form-item>
<sar-form-item label="Radio">
<sar-radio-group v-model="radioValue">
<sar-radio value="apple">Apple</sar-radio>
<sar-radio value="pear">Pear</sar-radio>
</sar-radio-group>
</sar-form-item>
<sar-form-item label="Input">
<sar-input />
</sar-form-item>
<sar-form-item label="Picker">
<sar-picker-input
placeholder="Picker"
:columns="[
{
label: 'Demo',
value: 'demo',
},
]"
/>
</sar-form-item>
<sar-form-item label="Cascader">
<sar-cascader-input placeholder="Cascader" :options="options" />
</sar-form-item>
<sar-form-item label="DatetimePicker">
<sar-datetime-picker-input placeholder="DatetimePicker" />
</sar-form-item>
<sar-form-item label="Calendar">
<sar-calendar-input placeholder="Calendar" type="range" />
</sar-form-item>
<sar-form-item label="Stepper">
<sar-stepper />
</sar-form-item>
<sar-form-item label="Input[Textarea]">
<sar-input type="textarea" />
</sar-form-item>
<sar-form-item label="PasswordInput">
<sar-password-input
gap="0"
model-value="123"
root-style="height: 80rpx"
/>
</sar-form-item>
<sar-form-item label="Switch">
<sar-switch :model-value="true" />
</sar-form-item>
<sar-form-item label="Rate">
<sar-rate :model-value="3" />
</sar-form-item>
<sar-form-item label="Slider">
<sar-slider :model-value="50" />
</sar-form-item>
<sar-form-item label="Upload">
<sar-upload />
</sar-form-item>
<sar-form-item label="Button">
<sar-button>Button</sar-button>
</sar-form-item>
</sar-form>
</doc-page>
</template>
<script setup lang="js">
import { useCurrentPageLock, usePageTopPopup } from "sard-uniapp";
import { onBackPress } from "@dcloudio/uni-app";
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"
}
]
}
]);
const { isLocked } = useCurrentPageLock();
const { shouldStopBack, hidePopup } = usePageTopPopup();
onBackPress(() => {
if (shouldStopBack.value) {
hidePopup();
return true;
}
});
</script>登录框
下面实现了一个简单的登录框组件。
<template>
<page-meta :page-style="isLocked ? 'overflow: hidden' : ''"></page-meta>
<doc-page title="登录框">
<sar-form :model="formState" ref="formRef">
<sar-form-item
label="Username"
name="username"
:rules="[{ required: true, message: 'Please input your username!' }]"
>
<sar-input v-model="formState.username">
<template #prepend>
<sar-icon family="demo-icons" name="person" />
</template>
</sar-input>
</sar-form-item>
<sar-form-item
label="Password"
name="password"
:rules="[{ required: true, message: 'Please input your password!' }]"
>
<sar-input type="password" v-model="formState.password">
<template #prepend>
<sar-icon family="demo-icons" name="key" />
</template>
</sar-input>
</sar-form-item>
<sar-form-item>
<sar-space justify="between">
<sar-form-item name="remember" inlaid>
<sar-checkbox v-model:checked="formState.remember" class="my-auto">
Remember me
</sar-checkbox>
</sar-form-item>
<sar-button inline type="pale-text">Forgot password</sar-button>
</sar-space>
</sar-form-item>
<sar-form-item>
<sar-button @click="submitForm" :disabled="disabled">Log in</sar-button>
<sar-space align="center" justify="end">
<text>Or</text>
<sar-button inline type="pale-text">register now!</sar-button>
</sar-space>
</sar-form-item>
</sar-form>
</doc-page>
</template>
<script setup lang="ts">
import { useCurrentPageLock, usePageTopPopup } from 'sard-uniapp'
import { onBackPress } from '@dcloudio/uni-app'
import { reactive, computed, ref } from 'vue'
import { toast, type FormExpose, type FieldValidateError } from 'sard-uniapp'
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)
})
const { isLocked } = useCurrentPageLock()
const { shouldStopBack, hidePopup } = usePageTopPopup()
onBackPress(() => {
if (shouldStopBack.value) {
hidePopup()
return true
}
})
</script><template>
<page-meta :page-style="isLocked ? 'overflow: hidden' : ''"></page-meta>
<doc-page title="登录框">
<sar-form :model="formState" ref="formRef">
<sar-form-item
label="Username"
name="username"
:rules="[{ required: true, message: 'Please input your username!' }]"
>
<sar-input v-model="formState.username">
<template #prepend>
<sar-icon family="demo-icons" name="person" />
</template>
</sar-input>
</sar-form-item>
<sar-form-item
label="Password"
name="password"
:rules="[{ required: true, message: 'Please input your password!' }]"
>
<sar-input type="password" v-model="formState.password">
<template #prepend>
<sar-icon family="demo-icons" name="key" />
</template>
</sar-input>
</sar-form-item>
<sar-form-item>
<sar-space justify="between">
<sar-form-item name="remember" inlaid>
<sar-checkbox v-model:checked="formState.remember" class="my-auto">
Remember me
</sar-checkbox>
</sar-form-item>
<sar-button inline type="pale-text">Forgot password</sar-button>
</sar-space>
</sar-form-item>
<sar-form-item>
<sar-button @click="submitForm" :disabled="disabled">Log in</sar-button>
<sar-space align="center" justify="end">
<text>Or</text>
<sar-button inline type="pale-text">register now!</sar-button>
</sar-space>
</sar-form-item>
</sar-form>
</doc-page>
</template>
<script setup lang="js">
import { useCurrentPageLock, usePageTopPopup } from "sard-uniapp";
import { onBackPress } from "@dcloudio/uni-app";
import { reactive, computed, ref } from "vue";
import { toast } from "sard-uniapp";
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);
});
const { isLocked } = useCurrentPageLock();
const { shouldStopBack, hidePopup } = usePageTopPopup();
onBackPress(() => {
if (shouldStopBack.value) {
hidePopup();
return true;
}
});
</script>嵌套结构与校验信息
name 属性支持嵌套数据结构。
<template>
<page-meta :page-style="isLocked ? 'overflow: hidden' : ''"></page-meta>
<doc-page title="嵌套结构与校验信息">
<sar-form :model="formState" ref="formRef">
<sar-form-item
:name="['user', 'name']"
label="Name"
:rules="[{ required: true }]"
>
<sar-input v-model="formState.user.name" />
</sar-form-item>
<sar-form-item
:name="['user', 'email']"
label="Email"
:rules="[{ type: 'email' }]"
>
<sar-input v-model="formState.user.email" />
</sar-form-item>
<sar-form-item
:name="['user', 'age']"
label="Age"
:rules="[{ type: 'number', min: 0, max: 99 }]"
>
<sar-stepper v-model="formState.user.age" />
</sar-form-item>
<sar-form-item :name="['user', 'website']" label="Website">
<sar-input v-model="formState.user.website" />
</sar-form-item>
<sar-form-item :name="['user', 'introduction']" label="Introduction">
<sar-input type="textarea" v-model="formState.user.introduction" />
</sar-form-item>
<sar-form-item>
<sar-button @click="onSubmit">Submit</sar-button>
</sar-form-item>
</sar-form>
</doc-page>
</template>
<script setup lang="ts">
import { useCurrentPageLock, usePageTopPopup } from 'sard-uniapp'
import { onBackPress } from '@dcloudio/uni-app'
import { reactive, ref, toRaw } from 'vue'
import { toast, type FormExpose } from 'sard-uniapp'
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!')
})
}
const { isLocked } = useCurrentPageLock()
const { shouldStopBack, hidePopup } = usePageTopPopup()
onBackPress(() => {
if (shouldStopBack.value) {
hidePopup()
return true
}
})
</script><template>
<page-meta :page-style="isLocked ? 'overflow: hidden' : ''"></page-meta>
<doc-page title="嵌套结构与校验信息">
<sar-form :model="formState" ref="formRef">
<sar-form-item
:name="['user', 'name']"
label="Name"
:rules="[{ required: true }]"
>
<sar-input v-model="formState.user.name" />
</sar-form-item>
<sar-form-item
:name="['user', 'email']"
label="Email"
:rules="[{ type: 'email' }]"
>
<sar-input v-model="formState.user.email" />
</sar-form-item>
<sar-form-item
:name="['user', 'age']"
label="Age"
:rules="[{ type: 'number', min: 0, max: 99 }]"
>
<sar-stepper v-model="formState.user.age" />
</sar-form-item>
<sar-form-item :name="['user', 'website']" label="Website">
<sar-input v-model="formState.user.website" />
</sar-form-item>
<sar-form-item :name="['user', 'introduction']" label="Introduction">
<sar-input type="textarea" v-model="formState.user.introduction" />
</sar-form-item>
<sar-form-item>
<sar-button @click="onSubmit">Submit</sar-button>
</sar-form-item>
</sar-form>
</doc-page>
</template>
<script setup lang="js">
import { useCurrentPageLock, usePageTopPopup } from "sard-uniapp";
import { onBackPress } from "@dcloudio/uni-app";
import { reactive, ref, toRaw } from "vue";
import { toast } from "sard-uniapp";
const formRef = ref();
const formState = reactive({
user: {
name: "",
age: void 0,
email: "",
website: "",
introduction: ""
}
});
const onSubmit = () => {
formRef.value?.validate().then(() => {
toast("Success");
console.log("Success:", toRaw(formState));
}).catch(() => {
toast("fail!");
});
};
const { isLocked } = useCurrentPageLock();
const { shouldStopBack, hidePopup } = usePageTopPopup();
onBackPress(() => {
if (shouldStopBack.value) {
hidePopup();
return true;
}
});
</script>自定义表单控件
自定义或第三方的表单控件,也可以与 Form 组件一起使用。只要该组件注入 useFormItemContext 并调用相应方法。
<template>
<page-meta :page-style="isLocked ? 'overflow: hidden' : ''"></page-meta>
<doc-page title="自定义表单控件">
<sar-form :model="formState" ref="formRef">
<sar-form-item
name="price"
label="Price"
:rules="[{ validator: checkPrice }]"
>
<price-input v-model="formState.price" />
</sar-form-item>
<sar-form-item>
<sar-button @click="onSubmit">Submit</sar-button>
</sar-form-item>
</sar-form>
</doc-page>
</template>
<script setup lang="ts">
import { useCurrentPageLock, usePageTopPopup } from 'sard-uniapp'
import { onBackPress } from '@dcloudio/uni-app'
import { reactive, ref, toRaw } from 'vue'
import PriceInput, { type Currency } from './PriceInput.vue'
import { toast, type FormExpose } from 'sard-uniapp'
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: { number: number }) => {
if (value.number > 0) {
return true
}
return 'Price must be greater than zero!'
}
const { isLocked } = useCurrentPageLock()
const { shouldStopBack, hidePopup } = usePageTopPopup()
onBackPress(() => {
if (shouldStopBack.value) {
hidePopup()
return true
}
})
</script><template>
<page-meta :page-style="isLocked ? 'overflow: hidden' : ''"></page-meta>
<doc-page title="自定义表单控件">
<sar-form :model="formState" ref="formRef">
<sar-form-item
name="price"
label="Price"
:rules="[{ validator: checkPrice }]"
>
<price-input v-model="formState.price" />
</sar-form-item>
<sar-form-item>
<sar-button @click="onSubmit">Submit</sar-button>
</sar-form-item>
</sar-form>
</doc-page>
</template>
<script setup lang="js">
import { useCurrentPageLock, usePageTopPopup } from "sard-uniapp";
import { onBackPress } from "@dcloudio/uni-app";
import { reactive, ref, toRaw } from "vue";
import { toast } from "sard-uniapp";
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!";
};
const { isLocked } = useCurrentPageLock();
const { shouldStopBack, hidePopup } = usePageTopPopup();
onBackPress(() => {
if (shouldStopBack.value) {
hidePopup();
return true;
}
});
</script>PriceInput 组件:
<template>
<page-meta :page-style="isLocked ? 'overflow: hidden' : ''"></page-meta>
<sar-space align="center">
<sar-input
type="text"
:model-value="modelValue.number"
@update:model-value="onNumberChange"
:validate-event="false"
/>
<sar-picker-input
:model-value="modelValue.currency"
placeholder="currency"
:columns="[
{ value: 'rmb', label: 'RMB' },
{ value: 'dollar', label: 'Dollar' },
]"
@update:model-value="onCurrencyChange"
:validate-event="false"
/>
</sar-space>
</template>
<script setup lang="ts">
import { useCurrentPageLock, usePageTopPopup } from 'sard-uniapp'
import { onBackPress } from '@dcloudio/uni-app'
import { useFormItemContext, type FormItemContext } from 'sard-uniapp'
export type Currency = 'rmb' | 'dollar'
interface PriceValue {
number: number
currency: Currency
}
const props = defineProps<{
modelValue: PriceValue
}>()
const emit = defineEmits<{
(e: 'update:model-value', value: PriceValue): void
}>()
const formItemContext = useFormItemContext() as FormItemContext
const triggerChange = (changedValue: {
number?: number
currency?: Currency
}) => {
emit('update:model-value', { ...props.modelValue, ...changedValue })
formItemContext.onChange()
}
const onNumberChange = (value: string) => {
const newNumber = parseInt(value || '0', 10)
triggerChange({ number: newNumber })
}
const onCurrencyChange = (newCurrency: Currency) => {
triggerChange({ currency: newCurrency })
}
const { isLocked } = useCurrentPageLock()
const { shouldStopBack, hidePopup } = usePageTopPopup()
onBackPress(() => {
if (shouldStopBack.value) {
hidePopup()
return true
}
})
</script><template>
<page-meta :page-style="isLocked ? 'overflow: hidden' : ''"></page-meta>
<sar-space align="center">
<sar-input
type="text"
:model-value="modelValue.number"
@update:model-value="onNumberChange"
:validate-event="false"
/>
<sar-picker-input
:model-value="modelValue.currency"
placeholder="currency"
:columns="[
{ value: 'rmb', label: 'RMB' },
{ value: 'dollar', label: 'Dollar' },
]"
@update:model-value="onCurrencyChange"
:validate-event="false"
/>
</sar-space>
</template>
<script setup lang="js">
import { useCurrentPageLock, usePageTopPopup } from "sard-uniapp";
import { onBackPress } from "@dcloudio/uni-app";
import { useFormItemContext } from "sard-uniapp";
const props = defineProps();
const emit = defineEmits();
const formItemContext = useFormItemContext();
const triggerChange = (changedValue) => {
emit("update:model-value", { ...props.modelValue, ...changedValue });
formItemContext.onChange();
};
const onNumberChange = (value) => {
const newNumber = parseInt(value || "0", 10);
triggerChange({ number: newNumber });
};
const onCurrencyChange = (newCurrency) => {
triggerChange({ currency: newCurrency });
};
const { isLocked } = useCurrentPageLock();
const { shouldStopBack, hidePopup } = usePageTopPopup();
onBackPress(() => {
if (shouldStopBack.value) {
hidePopup();
return true;
}
});
</script>复杂的动态增减表单项
这个例子演示了一个表单中包含多个表单控件的情况。
<template>
<page-meta :page-style="isLocked ? 'overflow: hidden' : ''"></page-meta>
<doc-page title="复杂的动态增减表单项">
<sar-form ref="formRef" :model="dynamicValidateForm">
<sar-form-item
name="area"
label="Area"
:rules="[{ required: true, message: 'Missing area' }]"
>
<sar-picker-input
v-model="dynamicValidateForm.area"
placeholder="Area"
:columns="areas"
/>
</sar-form-item>
<sar-form-item
v-for="(sight, index) in dynamicValidateForm.sights"
:key="sight.id"
>
<sar-space align="center">
<sar-form-item
:name="['sights', index, 'value']"
label="Sight"
label-width="100rpx"
:rules="{
required: true,
message: 'Missing sight',
}"
inlaid
>
<sar-picker-input
v-model="sight.value"
placeholder="Sight"
:disabled="!dynamicValidateForm.area"
:columns="
(sights[dynamicValidateForm.area] || []).map((a) => ({
value: a,
label: a,
}))
"
/>
</sar-form-item>
<sar-form-item
:name="['sights', index, 'price']"
label="Price"
label-width="100rpx"
:rules="{
required: true,
message: 'Missing price',
}"
inlaid
>
<sar-input inlaid placeholder="Price" v-model="sight.price" />
</sar-form-item>
<sar-button
type="text"
icon="trash"
size="small"
theme="danger"
@click="removeSight(sight)"
/>
</sar-space>
</sar-form-item>
<sar-form-item>
<sar-button type="outline" icon="plus" @click="addSight()">
Add sights
</sar-button>
</sar-form-item>
<sar-form-item>
<sar-button @click="onSubmit">Submit</sar-button>
</sar-form-item>
</sar-form>
</doc-page>
</template>
<script setup lang="ts">
import { useCurrentPageLock, usePageTopPopup } from 'sard-uniapp'
import { onBackPress } from '@dcloudio/uni-app'
import { toRaw } from 'vue'
import { reactive, ref, watch } from 'vue'
import { toast, type FormExpose } from 'sard-uniapp'
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')
})
}
const { isLocked } = useCurrentPageLock()
const { shouldStopBack, hidePopup } = usePageTopPopup()
onBackPress(() => {
if (shouldStopBack.value) {
hidePopup()
return true
}
})
</script><template>
<page-meta :page-style="isLocked ? 'overflow: hidden' : ''"></page-meta>
<doc-page title="复杂的动态增减表单项">
<sar-form ref="formRef" :model="dynamicValidateForm">
<sar-form-item
name="area"
label="Area"
:rules="[{ required: true, message: 'Missing area' }]"
>
<sar-picker-input
v-model="dynamicValidateForm.area"
placeholder="Area"
:columns="areas"
/>
</sar-form-item>
<sar-form-item
v-for="(sight, index) in dynamicValidateForm.sights"
:key="sight.id"
>
<sar-space align="center">
<sar-form-item
:name="['sights', index, 'value']"
label="Sight"
label-width="100rpx"
:rules="{
required: true,
message: 'Missing sight',
}"
inlaid
>
<sar-picker-input
v-model="sight.value"
placeholder="Sight"
:disabled="!dynamicValidateForm.area"
:columns="
(sights[dynamicValidateForm.area] || []).map((a) => ({
value: a,
label: a,
}))
"
/>
</sar-form-item>
<sar-form-item
:name="['sights', index, 'price']"
label="Price"
label-width="100rpx"
:rules="{
required: true,
message: 'Missing price',
}"
inlaid
>
<sar-input inlaid placeholder="Price" v-model="sight.price" />
</sar-form-item>
<sar-button
type="text"
icon="trash"
size="small"
theme="danger"
@click="removeSight(sight)"
/>
</sar-space>
</sar-form-item>
<sar-form-item>
<sar-button type="outline" icon="plus" @click="addSight()">
Add sights
</sar-button>
</sar-form-item>
<sar-form-item>
<sar-button @click="onSubmit">Submit</sar-button>
</sar-form-item>
</sar-form>
</doc-page>
</template>
<script setup lang="js">
import { useCurrentPageLock, usePageTopPopup } from "sard-uniapp";
import { onBackPress } from "@dcloudio/uni-app";
import { toRaw } from "vue";
import { reactive, ref, watch } from "vue";
import { toast } from "sard-uniapp";
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");
});
};
const { isLocked } = useCurrentPageLock();
const { shouldStopBack, hidePopup } = usePageTopPopup();
onBackPress(() => {
if (shouldStopBack.value) {
hidePopup();
return true;
}
});
</script>动态增减嵌套字段
通过数组 name 绑定嵌套字段。
<template>
<page-meta :page-style="isLocked ? 'overflow: hidden' : ''"></page-meta>
<doc-page title="动态增减嵌套字段">
<sar-form ref="formRef" :model="dynamicValidateForm">
<sar-form-item
v-for="(user, index) in dynamicValidateForm.users"
:key="user.id"
>
<sar-space align="center">
<sar-form-item
:name="['users', index, 'first']"
:rules="{
required: true,
message: 'Missing first name',
}"
inlaid
>
<sar-input inlaid v-model="user.first" placeholder="First Name" />
</sar-form-item>
<sar-form-item
:name="['users', index, 'last']"
:rules="{
required: true,
message: 'Missing last name',
}"
inlaid
>
<sar-input inlaid v-model="user.last" placeholder="Last Name" />
</sar-form-item>
<sar-button
type="text"
icon="trash"
size="small"
theme="danger"
@click="removeUser(user)"
/>
</sar-space>
</sar-form-item>
<sar-form-item>
<sar-button type="outline" icon="plus" @click="addUser()">
Add user
</sar-button>
</sar-form-item>
<sar-form-item>
<sar-button @click="onSubmit">Submit</sar-button>
</sar-form-item>
</sar-form>
</doc-page>
</template>
<script setup lang="ts">
import { useCurrentPageLock, usePageTopPopup } from 'sard-uniapp'
import { onBackPress } from '@dcloudio/uni-app'
import { toRaw, reactive, ref } from 'vue'
import { toast, type FormExpose } from 'sard-uniapp'
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')
})
}
const { isLocked } = useCurrentPageLock()
const { shouldStopBack, hidePopup } = usePageTopPopup()
onBackPress(() => {
if (shouldStopBack.value) {
hidePopup()
return true
}
})
</script><template>
<page-meta :page-style="isLocked ? 'overflow: hidden' : ''"></page-meta>
<doc-page title="动态增减嵌套字段">
<sar-form ref="formRef" :model="dynamicValidateForm">
<sar-form-item
v-for="(user, index) in dynamicValidateForm.users"
:key="user.id"
>
<sar-space align="center">
<sar-form-item
:name="['users', index, 'first']"
:rules="{
required: true,
message: 'Missing first name',
}"
inlaid
>
<sar-input inlaid v-model="user.first" placeholder="First Name" />
</sar-form-item>
<sar-form-item
:name="['users', index, 'last']"
:rules="{
required: true,
message: 'Missing last name',
}"
inlaid
>
<sar-input inlaid v-model="user.last" placeholder="Last Name" />
</sar-form-item>
<sar-button
type="text"
icon="trash"
size="small"
theme="danger"
@click="removeUser(user)"
/>
</sar-space>
</sar-form-item>
<sar-form-item>
<sar-button type="outline" icon="plus" @click="addUser()">
Add user
</sar-button>
</sar-form-item>
<sar-form-item>
<sar-button @click="onSubmit">Submit</sar-button>
</sar-form-item>
</sar-form>
</doc-page>
</template>
<script setup lang="js">
import { useCurrentPageLock, usePageTopPopup } from "sard-uniapp";
import { onBackPress } from "@dcloudio/uni-app";
import { toRaw, reactive, ref } from "vue";
import { toast } from "sard-uniapp";
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");
});
};
const { isLocked } = useCurrentPageLock();
const { shouldStopBack, hidePopup } = usePageTopPopup();
onBackPress(() => {
if (shouldStopBack.value) {
hidePopup();
return true;
}
});
</script>动态校验规则
根据不同情况执行不同的校验规则。
<template>
<page-meta :page-style="isLocked ? 'overflow: hidden' : ''"></page-meta>
<doc-page title="动态校验规则">
<sar-form ref="formRef" :model="formState">
<sar-form-item
label="Username"
name="username"
:rules="[{ required: true, message: 'Please input your username!' }]"
>
<sar-input v-model="formState.username" />
</sar-form-item>
<sar-form-item
label="Nickname"
name="nickname"
:rules="[
{
required: formState.checkNick,
message: 'Please input your nickname!',
},
]"
>
<sar-input v-model="formState.nickname" />
</sar-form-item>
<sar-form-item name="checkNick">
<sar-checkbox v-model:checked="formState.checkNick">
Nickname is required
</sar-checkbox>
</sar-form-item>
<sar-form-item>
<sar-button @click="onCheck">Check</sar-button>
</sar-form-item>
</sar-form>
</doc-page>
</template>
<script setup lang="ts">
import { useCurrentPageLock, usePageTopPopup } from 'sard-uniapp'
import { onBackPress } from '@dcloudio/uni-app'
import { reactive, ref, watch, toRaw } from 'vue'
import { toast, type FormExpose, type FieldValidateError } from 'sard-uniapp'
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)
})
}
const { isLocked } = useCurrentPageLock()
const { shouldStopBack, hidePopup } = usePageTopPopup()
onBackPress(() => {
if (shouldStopBack.value) {
hidePopup()
return true
}
})
</script><template>
<page-meta :page-style="isLocked ? 'overflow: hidden' : ''"></page-meta>
<doc-page title="动态校验规则">
<sar-form ref="formRef" :model="formState">
<sar-form-item
label="Username"
name="username"
:rules="[{ required: true, message: 'Please input your username!' }]"
>
<sar-input v-model="formState.username" />
</sar-form-item>
<sar-form-item
label="Nickname"
name="nickname"
:rules="[
{
required: formState.checkNick,
message: 'Please input your nickname!',
},
]"
>
<sar-input v-model="formState.nickname" />
</sar-form-item>
<sar-form-item name="checkNick">
<sar-checkbox v-model:checked="formState.checkNick">
Nickname is required
</sar-checkbox>
</sar-form-item>
<sar-form-item>
<sar-button @click="onCheck">Check</sar-button>
</sar-form-item>
</sar-form>
</doc-page>
</template>
<script setup lang="js">
import { useCurrentPageLock, usePageTopPopup } from "sard-uniapp";
import { onBackPress } from "@dcloudio/uni-app";
import { reactive, ref, watch, toRaw } from "vue";
import { toast } from "sard-uniapp";
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);
});
};
const { isLocked } = useCurrentPageLock();
const { shouldStopBack, hidePopup } = usePageTopPopup();
onBackPress(() => {
if (shouldStopBack.value) {
hidePopup();
return true;
}
});
</script>多表单联动
把一个表单的数据添加到另一个表单。
<template>
<page-meta :page-style="isLocked ? 'overflow: hidden' : ''"></page-meta>
<doc-page title="多表单联动">
<sar-form ref="formRef" :model="formState" label-width="200rpx">
<sar-form-item
name="group"
label="Group Name"
:rules="[{ required: true, message: 'Please input group name!' }]"
>
<sar-input v-model="formState.group" />
</sar-form-item>
<sar-form-item label="User List">
<template v-if="formState.users.length">
<sar-list inlaid>
<template v-for="user in formState.users" :key="user.key">
<sar-list-item :title="`${user.name} - ${user.age}`">
<template #icon>
<sar-avatar size="64rpx" icon-size="40rpx" />
</template>
</sar-list-item>
</template>
</sar-list>
</template>
<template v-else>
<text>( No user yet. )</text>
</template>
</sar-form-item>
<sar-form-item>
<sar-button @click="onSubmit">Submit</sar-button>
<sar-button
type="outline"
root-style="margin-top: 20rpx"
@click="visible = true"
>
Add User
</sar-button>
</sar-form-item>
</sar-form>
<sar-dialog
v-model:visible="visible"
title="Basic Drawer"
:before-close="beforeClose"
>
<sar-form ref="modalFormRef" :model="modalFormState">
<sar-form-item
name="name"
label="User Name"
:rules="[{ required: true }]"
>
<sar-input
v-model="modalFormState.name"
inlaid
placeholder="Input User Name"
/>
</sar-form-item>
<sar-form-item
name="age"
label="User Age"
:rules="[{ required: true }]"
>
<sar-stepper v-model="modalFormState.age" />
</sar-form-item>
</sar-form>
</sar-dialog>
</doc-page>
</template>
<script setup lang="ts">
import { useCurrentPageLock, usePageTopPopup } from 'sard-uniapp'
import { onBackPress } from '@dcloudio/uni-app'
import { reactive, ref, toRaw } from 'vue'
import { toast, type FormExpose, type FieldValidateError } from 'sard-uniapp'
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)
})
}
const { isLocked } = useCurrentPageLock()
const { shouldStopBack, hidePopup } = usePageTopPopup()
onBackPress(() => {
if (shouldStopBack.value) {
hidePopup()
return true
}
})
</script><template>
<page-meta :page-style="isLocked ? 'overflow: hidden' : ''"></page-meta>
<doc-page title="多表单联动">
<sar-form ref="formRef" :model="formState" label-width="200rpx">
<sar-form-item
name="group"
label="Group Name"
:rules="[{ required: true, message: 'Please input group name!' }]"
>
<sar-input v-model="formState.group" />
</sar-form-item>
<sar-form-item label="User List">
<template v-if="formState.users.length">
<sar-list inlaid>
<template v-for="user in formState.users" :key="user.key">
<sar-list-item :title="`${user.name} - ${user.age}`">
<template #icon>
<sar-avatar size="64rpx" icon-size="40rpx" />
</template>
</sar-list-item>
</template>
</sar-list>
</template>
<template v-else>
<text>( No user yet. )</text>
</template>
</sar-form-item>
<sar-form-item>
<sar-button @click="onSubmit">Submit</sar-button>
<sar-button
type="outline"
root-style="margin-top: 20rpx"
@click="visible = true"
>
Add User
</sar-button>
</sar-form-item>
</sar-form>
<sar-dialog
v-model:visible="visible"
title="Basic Drawer"
:before-close="beforeClose"
>
<sar-form ref="modalFormRef" :model="modalFormState">
<sar-form-item
name="name"
label="User Name"
:rules="[{ required: true }]"
>
<sar-input
v-model="modalFormState.name"
inlaid
placeholder="Input User Name"
/>
</sar-form-item>
<sar-form-item
name="age"
label="User Age"
:rules="[{ required: true }]"
>
<sar-stepper v-model="modalFormState.age" />
</sar-form-item>
</sar-form>
</sar-dialog>
</doc-page>
</template>
<script setup lang="js">
import { useCurrentPageLock, usePageTopPopup } from "sard-uniapp";
import { onBackPress } from "@dcloudio/uni-app";
import { reactive, ref, toRaw } from "vue";
import { toast } from "sard-uniapp";
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);
});
};
const { isLocked } = useCurrentPageLock();
const { shouldStopBack, hidePopup } = usePageTopPopup();
onBackPress(() => {
if (shouldStopBack.value) {
hidePopup();
return true;
}
});
</script>弹出层中的新建表单
当用户访问一个展示了某个列表的页面,想新建一项但又不想跳转页面时,可以用 Dialog 弹出一个表单,用户填写必要信息后创建新的项。
<template>
<page-meta :page-style="isLocked ? 'overflow: hidden' : ''"></page-meta>
<doc-page title="弹出层中的新建表单">
<doc-demo>
<sar-button @click="visible = true">New Collection</sar-button>
</doc-demo>
<sar-dialog
v-model:visible="visible"
title="Create a new collection"
confirm-text="Create"
cancel-text="Cancel"
:before-close="beforeClose"
>
<sar-form ref="formRef" :model="formState">
<sar-form-item
name="title"
label="Title"
:rules="[
{
required: true,
message: 'Please input the title of collection!',
},
]"
>
<sar-input v-model="formState.title" />
</sar-form-item>
<sar-form-item name="description" label="Description">
<sar-input type="textarea" v-model="formState.description" />
</sar-form-item>
<sar-form-item name="modifier">
<sar-radio-group
v-model="formState.modifier"
direction="horizontal"
class="ml-auto"
>
<sar-radio value="public">Public</sar-radio>
<sar-radio value="private">Private</sar-radio>
</sar-radio-group>
</sar-form-item>
</sar-form>
<view style="margin-bottom: 20rpx"></view>
</sar-dialog>
</doc-page>
</template>
<script setup lang="ts">
import { useCurrentPageLock, usePageTopPopup } from 'sard-uniapp'
import { onBackPress } from '@dcloudio/uni-app'
import { reactive, ref, toRaw } from 'vue'
import { toast, type FormExpose, type FieldValidateError } from 'sard-uniapp'
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
})
}
}
const { isLocked } = useCurrentPageLock()
const { shouldStopBack, hidePopup } = usePageTopPopup()
onBackPress(() => {
if (shouldStopBack.value) {
hidePopup()
return true
}
})
</script><template>
<page-meta :page-style="isLocked ? 'overflow: hidden' : ''"></page-meta>
<doc-page title="弹出层中的新建表单">
<doc-demo>
<sar-button @click="visible = true">New Collection</sar-button>
</doc-demo>
<sar-dialog
v-model:visible="visible"
title="Create a new collection"
confirm-text="Create"
cancel-text="Cancel"
:before-close="beforeClose"
>
<sar-form ref="formRef" :model="formState">
<sar-form-item
name="title"
label="Title"
:rules="[
{
required: true,
message: 'Please input the title of collection!',
},
]"
>
<sar-input v-model="formState.title" />
</sar-form-item>
<sar-form-item name="description" label="Description">
<sar-input type="textarea" v-model="formState.description" />
</sar-form-item>
<sar-form-item name="modifier">
<sar-radio-group
v-model="formState.modifier"
direction="horizontal"
class="ml-auto"
>
<sar-radio value="public">Public</sar-radio>
<sar-radio value="private">Private</sar-radio>
</sar-radio-group>
</sar-form-item>
</sar-form>
<view style="margin-bottom: 20rpx"></view>
</sar-dialog>
</doc-page>
</template>
<script setup lang="js">
import { useCurrentPageLock, usePageTopPopup } from "sard-uniapp";
import { onBackPress } from "@dcloudio/uni-app";
import { reactive, ref, toRaw } from "vue";
import { toast } from "sard-uniapp";
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;
});
}
};
const { isLocked } = useCurrentPageLock();
const { shouldStopBack, hidePopup } = usePageTopPopup();
onBackPress(() => {
if (shouldStopBack.value) {
hidePopup();
return true;
}
});
</script>滚动到第一个错误字段
当表单超过一屏,在验证失败后想要将验证错误的表单域定位到屏幕中,可以设置 scroll-to-first-error 属性。
<template>
<page-meta :page-style="isLocked ? 'overflow: hidden' : ''"></page-meta>
<doc-page title="滚动到第一个错误字段" padding="0">
<sar-form
:model="formState"
ref="formRef"
scroll-to-first-error
:scroll-into-view-options="{
startOffset: 50,
endOffset: footerHeight,
position,
}"
>
<sar-form-item
v-for="(item, i) in formState"
:key="item.key"
:name="[i, 'value']"
:label="item.label"
required
>
<sar-input type="textarea" min-height="300rpx" v-model="item.value" />
</sar-form-item>
</sar-form>
<view :style="{ height: footerHeight + 'px' }"></view>
<view class="footer" :id="footerId">
<sar-row gap="20rpx">
<sar-col>
<sar-button @click="onSubmit">Submit</sar-button>
</sar-col>
<sar-col>
<sar-button type="outline" @click="onReset">reset</sar-button>
</sar-col>
</sar-row>
<view style="padding: 20rpx 0">
<sar-radio-group v-model="position" direction="horizontal">
<sar-radio value="start">start</sar-radio>
<sar-radio value="center">center</sar-radio>
<sar-radio value="end">end</sar-radio>
<sar-radio value="nearest">nearest</sar-radio>
</sar-radio-group>
</view>
</view>
</doc-page>
</template>
<script setup lang="ts">
import { useCurrentPageLock, usePageTopPopup } from 'sard-uniapp'
import { onBackPress } from '@dcloudio/uni-app'
import { ref, toRaw, onMounted } from 'vue'
import {
toast,
uniqid,
type FormExpose,
type ScrollIntoViewPosition,
type FieldValidateError,
} from 'sard-uniapp'
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 footerId = uniqid()
onMounted(() => {
uni
.createSelectorQuery()
.select(`#${footerId}`)
.boundingClientRect((result: any) => {
footerHeight.value = result.height
})
.exec()
})
const { isLocked } = useCurrentPageLock()
const { shouldStopBack, hidePopup } = usePageTopPopup()
onBackPress(() => {
if (shouldStopBack.value) {
hidePopup()
return true
}
})
</script>
<style lang="scss" scoped>
.footer {
position: fixed;
bottom: 0;
right: 0;
left: 0;
z-index: 1;
display: flex;
flex-direction: column;
gap: 30rpx;
padding: 20rpx 32rpx;
padding-bottom: calc(20rpx + env(safe-area-inset-bottom));
border-top: 1px var(--sar-border-color) solid;
background: var(--sar-emphasis-bg);
}
</style><template>
<page-meta :page-style="isLocked ? 'overflow: hidden' : ''"></page-meta>
<doc-page title="滚动到第一个错误字段" padding="0">
<sar-form
:model="formState"
ref="formRef"
scroll-to-first-error
:scroll-into-view-options="{
startOffset: 50,
endOffset: footerHeight,
position,
}"
>
<sar-form-item
v-for="(item, i) in formState"
:key="item.key"
:name="[i, 'value']"
:label="item.label"
required
>
<sar-input type="textarea" min-height="300rpx" v-model="item.value" />
</sar-form-item>
</sar-form>
<view :style="{ height: footerHeight + 'px' }"></view>
<view class="footer" :id="footerId">
<sar-row gap="20rpx">
<sar-col>
<sar-button @click="onSubmit">Submit</sar-button>
</sar-col>
<sar-col>
<sar-button type="outline" @click="onReset">reset</sar-button>
</sar-col>
</sar-row>
<view style="padding: 20rpx 0">
<sar-radio-group v-model="position" direction="horizontal">
<sar-radio value="start">start</sar-radio>
<sar-radio value="center">center</sar-radio>
<sar-radio value="end">end</sar-radio>
<sar-radio value="nearest">nearest</sar-radio>
</sar-radio-group>
</view>
</view>
</doc-page>
</template>
<script setup lang="js">
import { useCurrentPageLock, usePageTopPopup } from "sard-uniapp";
import { onBackPress } from "@dcloudio/uni-app";
import { ref, toRaw, onMounted } from "vue";
import {
toast,
uniqid
} from "sard-uniapp";
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 footerId = uniqid();
onMounted(() => {
uni.createSelectorQuery().select(`#${footerId}`).boundingClientRect((result) => {
footerHeight.value = result.height;
}).exec();
});
const { isLocked } = useCurrentPageLock();
const { shouldStopBack, hidePopup } = usePageTopPopup();
onBackPress(() => {
if (shouldStopBack.value) {
hidePopup();
return true;
}
});
</script>
<style lang="scss" scoped>
.footer {
position: fixed;
bottom: 0;
right: 0;
left: 0;
z-index: 1;
display: flex;
flex-direction: column;
gap: 30rpx;
padding: 20rpx 32rpx;
padding-bottom: calc(20rpx + env(safe-area-inset-bottom));
border-top: 1px var(--sar-border-color) solid;
background: var(--sar-emphasis-bg);
}
</style>toast 显示验证错误信息
通过设置 showError 属性隐藏默认验证错误信息,再通过 toast 显示 validate() 方法 catch 回调中的信息。
<template>
<page-meta :page-style="isLocked ? 'overflow: hidden' : ''"></page-meta>
<doc-page title="toast显示验证错误信息">
<sar-form ref="formRef" :model="formState" class="form" :showError="false">
<sar-form-item
name="username"
class="form-item"
:rules="[{ required: true, message: '请输入手机号!' }]"
>
<sar-input
v-model="formState.username"
placeholder="请输入手机号"
inlaid
/>
</sar-form-item>
<sar-form-item
name="password"
class="form-item"
:rules="[{ required: true, message: '请输入短信验证码!' }]"
>
<sar-space>
<sar-input
v-model="formState.password"
type="password"
placeholder="请输入短信验证码"
inlaid
/>
<sar-button type="pale-text" root-style="height: 100%">
发送验证码
</sar-button>
</sar-space>
</sar-form-item>
<sar-form-item class="form-item">
<sar-button @click="submitForm" round>登录</sar-button>
</sar-form-item>
</sar-form>
</doc-page>
</template>
<script setup lang="ts">
import { useCurrentPageLock, usePageTopPopup } from 'sard-uniapp'
import { onBackPress } from '@dcloudio/uni-app'
import { ref, reactive, toRaw } from 'vue'
import { toast, type FormExpose, type FieldValidateError } from 'sard-uniapp'
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)
})
}
const { isLocked } = useCurrentPageLock()
const { shouldStopBack, hidePopup } = usePageTopPopup()
onBackPress(() => {
if (shouldStopBack.value) {
hidePopup()
return true
}
})
</script><template>
<page-meta :page-style="isLocked ? 'overflow: hidden' : ''"></page-meta>
<doc-page title="toast显示验证错误信息">
<sar-form ref="formRef" :model="formState" class="form" :showError="false">
<sar-form-item
name="username"
class="form-item"
:rules="[{ required: true, message: '请输入手机号!' }]"
>
<sar-input
v-model="formState.username"
placeholder="请输入手机号"
inlaid
/>
</sar-form-item>
<sar-form-item
name="password"
class="form-item"
:rules="[{ required: true, message: '请输入短信验证码!' }]"
>
<sar-space>
<sar-input
v-model="formState.password"
type="password"
placeholder="请输入短信验证码"
inlaid
/>
<sar-button type="pale-text" root-style="height: 100%">
发送验证码
</sar-button>
</sar-space>
</sar-form-item>
<sar-form-item class="form-item">
<sar-button @click="submitForm" round>登录</sar-button>
</sar-form-item>
</sar-form>
</doc-page>
</template>
<script setup lang="js">
import { useCurrentPageLock, usePageTopPopup } from "sard-uniapp";
import { onBackPress } from "@dcloudio/uni-app";
import { ref, reactive, toRaw } from "vue";
import { toast } from "sard-uniapp";
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);
});
};
const { isLocked } = useCurrentPageLock();
const { shouldStopBack, hidePopup } = usePageTopPopup();
onBackPress(() => {
if (shouldStopBack.value) {
hidePopup();
return true;
}
});
</script>手机号登录
演示了常用的手机号登录的表单实现。
<template>
<page-meta :page-style="isLocked ? 'overflow: hidden' : ''"></page-meta>
<doc-page emphasis title="手机号登录">
<sar-form
ref="formRef"
:model="formState"
:show-error="false"
root-class="auth-form"
>
<sar-form-item
name="phone"
:rules="[{ required: true, message: '请输入手机号码' }]"
inlaid
root-class="auth-form-item"
>
<sar-input
v-model="formState.phone"
placeholder="手机号码"
inlaid
root-class="auth-form-input"
>
<template #prepend>
<sar-icon
family="demo-icons"
name="phone"
size="32rpx"
color="var(--sar-tertiary-color)"
/>
</template>
</sar-input>
</sar-form-item>
<sar-form-item
name="password"
:rules="[{ required: true, message: '请输入密码' }]"
inlaid
root-class="auth-form-item"
>
<sar-input
v-model="formState.password"
type="password"
placeholder="密码"
inlaid
root-class="auth-form-input"
>
<template #prepend>
<sar-icon
family="demo-icons"
name="key"
size="32rpx"
color="var(--sar-tertiary-color)"
/>
</template>
</sar-input>
</sar-form-item>
<sar-form-item inlaid>
<sar-button round :loading="submitting" @click="submitForm">
登录
</sar-button>
</sar-form-item>
</sar-form>
</doc-page>
</template>
<script setup lang="ts">
import { useCurrentPageLock, usePageTopPopup } from 'sard-uniapp'
import { onBackPress } from '@dcloudio/uni-app'
import { ref, reactive, toRaw } from 'vue'
import { toast, type FormExpose, type FieldValidateError } from 'sard-uniapp'
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)
})
}
const { isLocked } = useCurrentPageLock()
const { shouldStopBack, hidePopup } = usePageTopPopup()
onBackPress(() => {
if (shouldStopBack.value) {
hidePopup()
return true
}
})
</script>
<style lang="scss">
.auth-form {
margin: 0 32rpx;
.auth-form-item {
margin-bottom: 48rpx;
}
.auth-form-input {
height: 80rpx;
border-radius: 9999px;
padding: 0 48rpx;
background: rgba(var(--sar-primary-rgb), 0.05);
}
}
</style><template>
<page-meta :page-style="isLocked ? 'overflow: hidden' : ''"></page-meta>
<doc-page emphasis title="手机号登录">
<sar-form
ref="formRef"
:model="formState"
:show-error="false"
root-class="auth-form"
>
<sar-form-item
name="phone"
:rules="[{ required: true, message: '请输入手机号码' }]"
inlaid
root-class="auth-form-item"
>
<sar-input
v-model="formState.phone"
placeholder="手机号码"
inlaid
root-class="auth-form-input"
>
<template #prepend>
<sar-icon
family="demo-icons"
name="phone"
size="32rpx"
color="var(--sar-tertiary-color)"
/>
</template>
</sar-input>
</sar-form-item>
<sar-form-item
name="password"
:rules="[{ required: true, message: '请输入密码' }]"
inlaid
root-class="auth-form-item"
>
<sar-input
v-model="formState.password"
type="password"
placeholder="密码"
inlaid
root-class="auth-form-input"
>
<template #prepend>
<sar-icon
family="demo-icons"
name="key"
size="32rpx"
color="var(--sar-tertiary-color)"
/>
</template>
</sar-input>
</sar-form-item>
<sar-form-item inlaid>
<sar-button round :loading="submitting" @click="submitForm">
登录
</sar-button>
</sar-form-item>
</sar-form>
</doc-page>
</template>
<script setup lang="js">
import { useCurrentPageLock, usePageTopPopup } from "sard-uniapp";
import { onBackPress } from "@dcloudio/uni-app";
import { ref, reactive, toRaw } from "vue";
import { toast } from "sard-uniapp";
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("\u767B\u5F55\u6210\u529F");
console.log("Success:", toRaw(formState));
}).catch((error) => {
toast(error[0].message);
console.log("Failed:", error);
});
};
const { isLocked } = useCurrentPageLock();
const { shouldStopBack, hidePopup } = usePageTopPopup();
onBackPress(() => {
if (shouldStopBack.value) {
hidePopup();
return true;
}
});
</script>
<style lang="scss">
.auth-form {
margin: 0 32rpx;
.auth-form-item {
margin-bottom: 48rpx;
}
.auth-form-input {
height: 80rpx;
border-radius: 9999px;
padding: 0 48rpx;
background: rgba(var(--sar-primary-rgb), 0.05);
}
}
</style>修改密码
演示了常用的修改密码的表单实现。
<template>
<page-meta :page-style="isLocked ? 'overflow: hidden' : ''"></page-meta>
<doc-page emphasis title="修改密码">
<sar-form
ref="formRef"
:model="formState"
root-class="auth-form"
:show-error="false"
>
<sar-form-item
name="oldPassword"
:rules="[{ required: true, message: '请输入旧密码' }]"
inlaid
root-class="auth-form-item"
>
<sar-input
v-model="formState.oldPassword"
type="password"
placeholder="旧密码"
inlaid
root-class="auth-form-input"
>
<template #prepend>
<sar-icon
family="demo-icons"
name="key"
size="32rpx"
color="var(--sar-tertiary-color)"
/>
</template>
</sar-input>
</sar-form-item>
<sar-form-item
name="newPassword"
:rules="[{ required: true, message: '请输入新密码' }]"
inlaid
root-class="auth-form-item"
>
<sar-input
v-model="formState.newPassword"
type="password"
placeholder="新密码"
inlaid
root-class="auth-form-input"
>
<template #prepend>
<sar-icon
family="demo-icons"
name="key"
size="32rpx"
color="var(--sar-tertiary-color)"
/>
</template>
</sar-input>
</sar-form-item>
<sar-form-item inlaid>
<sar-button round :loading="submitting" @click="submitForm">
提交
</sar-button>
</sar-form-item>
</sar-form>
</doc-page>
</template>
<script setup lang="ts">
import { useCurrentPageLock, usePageTopPopup } from 'sard-uniapp'
import { onBackPress } from '@dcloudio/uni-app'
import { ref, reactive, toRaw } from 'vue'
import { toast, type FormExpose, type FieldValidateError } from 'sard-uniapp'
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)
})
}
const { isLocked } = useCurrentPageLock()
const { shouldStopBack, hidePopup } = usePageTopPopup()
onBackPress(() => {
if (shouldStopBack.value) {
hidePopup()
return true
}
})
</script>
<style lang="scss">
.auth-form {
margin: 0 32rpx;
.auth-form-item {
margin-bottom: 48rpx;
}
.auth-form-input {
height: 80rpx;
border-radius: 9999px;
padding: 0 48rpx;
background: rgba(var(--sar-primary-rgb), 0.05);
}
}
</style><template>
<page-meta :page-style="isLocked ? 'overflow: hidden' : ''"></page-meta>
<doc-page emphasis title="修改密码">
<sar-form
ref="formRef"
:model="formState"
root-class="auth-form"
:show-error="false"
>
<sar-form-item
name="oldPassword"
:rules="[{ required: true, message: '请输入旧密码' }]"
inlaid
root-class="auth-form-item"
>
<sar-input
v-model="formState.oldPassword"
type="password"
placeholder="旧密码"
inlaid
root-class="auth-form-input"
>
<template #prepend>
<sar-icon
family="demo-icons"
name="key"
size="32rpx"
color="var(--sar-tertiary-color)"
/>
</template>
</sar-input>
</sar-form-item>
<sar-form-item
name="newPassword"
:rules="[{ required: true, message: '请输入新密码' }]"
inlaid
root-class="auth-form-item"
>
<sar-input
v-model="formState.newPassword"
type="password"
placeholder="新密码"
inlaid
root-class="auth-form-input"
>
<template #prepend>
<sar-icon
family="demo-icons"
name="key"
size="32rpx"
color="var(--sar-tertiary-color)"
/>
</template>
</sar-input>
</sar-form-item>
<sar-form-item inlaid>
<sar-button round :loading="submitting" @click="submitForm">
提交
</sar-button>
</sar-form-item>
</sar-form>
</doc-page>
</template>
<script setup lang="js">
import { useCurrentPageLock, usePageTopPopup } from "sard-uniapp";
import { onBackPress } from "@dcloudio/uni-app";
import { ref, reactive, toRaw } from "vue";
import { toast } from "sard-uniapp";
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("\u4FEE\u6539\u6210\u529F");
console.log("Success:", toRaw(formState));
}).catch((error) => {
toast(error[0].message);
console.log("Failed:", error);
});
};
const { isLocked } = useCurrentPageLock();
const { shouldStopBack, hidePopup } = usePageTopPopup();
onBackPress(() => {
if (shouldStopBack.value) {
hidePopup();
return true;
}
});
</script>
<style lang="scss">
.auth-form {
margin: 0 32rpx;
.auth-form-item {
margin-bottom: 48rpx;
}
.auth-form-input {
height: 80rpx;
border-radius: 9999px;
padding: 0 48rpx;
background: rgba(var(--sar-primary-rgb), 0.05);
}
}
</style>忘记密码
演示了常用的忘记密码的表单实现。
<template>
<page-meta :page-style="isLocked ? 'overflow: hidden' : ''"></page-meta>
<doc-page emphasis title="忘记密码">
<sar-form
ref="formRef"
:model="formState"
:show-error="false"
root-class="auth-form"
>
<sar-form-item
name="phone"
:rules="[{ required: true, message: '请输入手机号码' }]"
inlaid
root-class="auth-form-item"
>
<sar-input
v-model="formState.phone"
placeholder="手机号码"
inlaid
root-class="auth-form-input"
>
<template #prepend>
<sar-icon
family="demo-icons"
name="phone"
size="32rpx"
color="var(--sar-tertiary-color)"
/>
</template>
</sar-input>
</sar-form-item>
<sar-form-item
name="captcha"
:rules="[{ required: true, message: '请输入验证码' }]"
inlaid
root-class="auth-form-item"
>
<sar-input
v-model="formState.captcha"
placeholder="验证码"
inlaid
root-class="auth-form-input"
root-style="padding-right: 0;"
>
<template #prepend>
<sar-icon
family="demo-icons"
name="captcha"
size="32rpx"
color="var(--sar-tertiary-color)"
/>
</template>
<template #append>
<sar-button
type="pale-text"
:loading="captchaLoading"
:disabled="captchaDisabled"
@click="onCaptchaClick"
>
<sar-count-down
v-if="captchaDisabled"
:time="1000 * 8"
format="重发验证码(s)"
@finish="captchaDisabled = false"
/>
<text v-else-if="captchaLoading">正在发送</text>
<text v-else>发送验证码</text>
</sar-button>
</template>
</sar-input>
</sar-form-item>
<sar-form-item
name="password"
:rules="[{ required: true, message: '请输入新密码' }]"
inlaid
root-class="auth-form-item"
>
<sar-input
v-model="formState.password"
type="password"
placeholder="新密码"
inlaid
root-class="auth-form-input"
>
<template #prepend>
<sar-icon
family="demo-icons"
name="key"
size="32rpx"
color="var(--sar-tertiary-color)"
/>
</template>
</sar-input>
</sar-form-item>
<sar-form-item inlaid>
<sar-button round :loading="submitting" @click="submitForm">
提交
</sar-button>
</sar-form-item>
</sar-form>
</doc-page>
</template>
<script setup lang="ts">
import { useCurrentPageLock, usePageTopPopup } from 'sard-uniapp'
import { onBackPress } from '@dcloudio/uni-app'
import { ref, reactive, toRaw } from 'vue'
import { toast, type FormExpose, type FieldValidateError } from 'sard-uniapp'
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
})
}
const { isLocked } = useCurrentPageLock()
const { shouldStopBack, hidePopup } = usePageTopPopup()
onBackPress(() => {
if (shouldStopBack.value) {
hidePopup()
return true
}
})
</script>
<style lang="scss">
.auth-form {
margin: 0 32rpx;
.auth-form-item {
margin-bottom: 48rpx;
}
.auth-form-input {
height: 80rpx;
border-radius: 9999px;
padding: 0 48rpx;
background: rgba(var(--sar-primary-rgb), 0.05);
}
}
</style><template>
<page-meta :page-style="isLocked ? 'overflow: hidden' : ''"></page-meta>
<doc-page emphasis title="忘记密码">
<sar-form
ref="formRef"
:model="formState"
:show-error="false"
root-class="auth-form"
>
<sar-form-item
name="phone"
:rules="[{ required: true, message: '请输入手机号码' }]"
inlaid
root-class="auth-form-item"
>
<sar-input
v-model="formState.phone"
placeholder="手机号码"
inlaid
root-class="auth-form-input"
>
<template #prepend>
<sar-icon
family="demo-icons"
name="phone"
size="32rpx"
color="var(--sar-tertiary-color)"
/>
</template>
</sar-input>
</sar-form-item>
<sar-form-item
name="captcha"
:rules="[{ required: true, message: '请输入验证码' }]"
inlaid
root-class="auth-form-item"
>
<sar-input
v-model="formState.captcha"
placeholder="验证码"
inlaid
root-class="auth-form-input"
root-style="padding-right: 0;"
>
<template #prepend>
<sar-icon
family="demo-icons"
name="captcha"
size="32rpx"
color="var(--sar-tertiary-color)"
/>
</template>
<template #append>
<sar-button
type="pale-text"
:loading="captchaLoading"
:disabled="captchaDisabled"
@click="onCaptchaClick"
>
<sar-count-down
v-if="captchaDisabled"
:time="1000 * 8"
format="重发验证码(s)"
@finish="captchaDisabled = false"
/>
<text v-else-if="captchaLoading">正在发送</text>
<text v-else>发送验证码</text>
</sar-button>
</template>
</sar-input>
</sar-form-item>
<sar-form-item
name="password"
:rules="[{ required: true, message: '请输入新密码' }]"
inlaid
root-class="auth-form-item"
>
<sar-input
v-model="formState.password"
type="password"
placeholder="新密码"
inlaid
root-class="auth-form-input"
>
<template #prepend>
<sar-icon
family="demo-icons"
name="key"
size="32rpx"
color="var(--sar-tertiary-color)"
/>
</template>
</sar-input>
</sar-form-item>
<sar-form-item inlaid>
<sar-button round :loading="submitting" @click="submitForm">
提交
</sar-button>
</sar-form-item>
</sar-form>
</doc-page>
</template>
<script setup lang="js">
import { useCurrentPageLock, usePageTopPopup } from "sard-uniapp";
import { onBackPress } from "@dcloudio/uni-app";
import { ref, reactive, toRaw } from "vue";
import { toast } from "sard-uniapp";
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("\u4FEE\u6539\u6210\u529F");
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("\u5DF2\u53D1\u9001\u9A8C\u8BC1\u7801");
captchaDisabled.value = true;
}).finally(() => {
captchaLoading.value = false;
});
};
const { isLocked } = useCurrentPageLock();
const { shouldStopBack, hidePopup } = usePageTopPopup();
onBackPress(() => {
if (shouldStopBack.value) {
hidePopup();
return true;
}
});
</script>
<style lang="scss">
.auth-form {
margin: 0 32rpx;
.auth-form-item {
margin-bottom: 48rpx;
}
.auth-form-input {
height: 80rpx;
border-radius: 9999px;
padding: 0 48rpx;
background: rgba(var(--sar-primary-rgb), 0.05);
}
}
</style>自定义表单样式和结构 1.23+
如果对表单样式和结构有很强的定义性,可使用 FormPlain 和 FormItemPlain 组件来代替 Form 和 FormItem。
前者不包含任意样式,能让你自由发挥。
这两组组件几乎拥有一样的接口,除了 FormItemPlain 的插槽提供了一些属性用于获取当前表单项的状态和样式。
<template>
<page-meta :page-style="isLocked ? 'overflow: hidden' : ''"></page-meta>
<doc-page title="服务人员满意度评分表" emphasis padding-bottom="0">
<sar-form-plain
ref="formRef"
:model="formModel"
:rules="formRules"
scroll-to-first-error
:scroll-into-view-options="{
startOffset,
position: 'start',
}"
>
<view class="form-description">
尊敬的客户:感谢您对我们服务的支持!请根据您的真实体验为服务人员评分。
</view>
<view class="section">
<view class="section-title">
<text class="section-index">1</text>
<text>基本信息</text>
</view>
<view class="section-content">
<sar-form-item-plain name="staff_name" class="form-item">
<template #custom="{ shouldShowStar }">
<view class="form-label">
<text v-if="shouldShowStar" class="form-star">*</text>
服务人员姓名/工号:
</view>
<view class="form-input">
<sar-input
v-model="formModel.staff_name"
placeholder="请输入服务人员姓名/工号"
clearable
inlaid
/>
</view>
</template>
</sar-form-item-plain>
<sar-form-item-plain name="service_date">
<template #custom="{ shouldShowStar }">
<view class="form-label">
<text v-if="shouldShowStar" class="form-star">*</text>
服务日期:
</view>
<view class="form-input">
<sar-datetime-picker-input
v-model="formModel.service_date"
placeholder="请选择服务日期"
clearable
/>
</view>
</template>
</sar-form-item-plain>
<sar-form-item-plain name="contact">
<template #custom="{ shouldShowStar }">
<view class="form-label">
<text v-if="shouldShowStar" class="form-star">*</text>
您的联系方式(可选)
</view>
<view class="form-input">
<sar-input
type="textarea"
v-model="formModel.contact"
placeholder="请输入联系方式"
clearable
inlaid
auto-height
min-height="48rpx"
/>
</view>
</template>
</sar-form-item-plain>
</view>
</view>
<view class="section">
<view class="section-title">
<text class="section-index">2</text>
<text>服务人员评分</text>
</view>
<view class="section-content">
<sar-form-item-plain name="professionalism">
<template #custom="{ shouldShowStar }">
<view class="form-label">
<text v-if="shouldShowStar" class="form-star">*</text>
专业能力:是否具备足够的知识和技能?
</view>
<RadioList
v-model="formModel.professionalism"
:options="ratingOptions"
/>
</template>
</sar-form-item-plain>
<sar-form-item-plain name="attitude">
<template #custom="{ shouldShowStar }">
<view class="form-label">
<text v-if="shouldShowStar" class="form-star">*</text>
服务态度:是否礼貌、耐心、热情?
</view>
<RadioList
v-model="formModel.attitude"
:options="ratingOptions"
/>
</template>
</sar-form-item-plain>
<sar-form-item-plain name="response_speed">
<template #custom="{ shouldShowStar }">
<view class="form-label">
<text v-if="shouldShowStar" class="form-star">*</text>
响应速度:问题处理是否及时?
</view>
<RadioList
v-model="formModel.response_speed"
:options="ratingOptions"
/>
</template>
</sar-form-item-plain>
<sar-form-item-plain name="communication">
<template #custom="{ shouldShowStar }">
<view class="form-label">
<text v-if="shouldShowStar" class="form-star">*</text>
沟通能力:表达是否清晰、易于理解?
</view>
<RadioList
v-model="formModel.communication"
:options="ratingOptions"
/>
</template>
</sar-form-item-plain>
<sar-form-item-plain name="problem_solving">
<template #custom="{ shouldShowStar }">
<view class="form-label">
<text v-if="shouldShowStar" class="form-star">*</text>
问题解决:是否有效解决您的需求?
</view>
<RadioList
v-model="formModel.problem_solving"
:options="ratingOptions"
/>
</template>
</sar-form-item-plain>
</view>
</view>
<view class="section">
<view class="section-title">
<text class="section-index">3</text>
<text>整体评价</text>
</view>
<view class="section-content">
<sar-form-item-plain name="overall_satisfaction">
<template #custom="{ shouldShowStar }">
<view class="form-label">
<text v-if="shouldShowStar" class="form-star">*</text>
本次服务的整体满意度
</view>
<RadioList
v-model="formModel.overall_satisfaction"
:options="ratingOptions"
/>
</template>
</sar-form-item-plain>
<sar-form-item-plain name="would_return">
<template #custom="{ shouldShowStar }">
<view class="form-label">
<text v-if="shouldShowStar" class="form-star">*</text>
您是否会再次选择该服务人员?
</view>
<RadioList
v-model="formModel.would_return"
:options="returnOptions"
/>
</template>
</sar-form-item-plain>
</view>
</view>
<view class="section">
<view class="section-title">
<text class="section-index">4</text>
<text>开放式反馈</text>
</view>
<view class="section-content">
<sar-form-item-plain name="strengths">
<view class="form-label">服务人员的优点(可选)</view>
<view class="form-input">
<sar-input
v-model="formModel.strengths"
type="textarea"
placeholder="请输入服务人员的优点"
clearable
inlaid
auto-height
min-height="48rpx"
/>
</view>
</sar-form-item-plain>
<sar-form-item-plain name="improvements">
<view class="form-label">需要改进的方面(可选)</view>
<view class="form-input">
<sar-input
v-model="formModel.improvements"
type="textarea"
placeholder="请输入需要改进的方面"
clearable
inlaid
auto-height
min-height="48rpx"
/>
</view>
</sar-form-item-plain>
<sar-form-item-plain name="suggestions">
<view class="form-label">其他建议(可选)</view>
<view class="form-input">
<sar-input
v-model="formModel.suggestions"
type="textarea"
placeholder="请输入其他建议"
clearable
inlaid
auto-height
min-height="48rpx"
/>
</view>
</sar-form-item-plain>
</view>
</view>
<view class="footer">
<sar-button @click="onSubmit">提交评价</sar-button>
</view>
</sar-form-plain>
</doc-page>
</template>
<script setup lang="ts">
import { useCurrentPageLock, usePageTopPopup } from 'sard-uniapp'
import { onBackPress } from '@dcloudio/uni-app'
import { reactive, ref } from 'vue'
import {
toast,
type FormExpose,
type FieldValidateError,
type FormRules,
getWindowInfo,
} from 'sard-uniapp'
import RadioList from './RadioList.vue'
const startOffset = ref(getWindowInfo().statusBarHeight + 60)
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: '不确定' },
]
const { isLocked } = useCurrentPageLock()
const { shouldStopBack, hidePopup } = usePageTopPopup()
onBackPress(() => {
if (shouldStopBack.value) {
hidePopup()
return true
}
})
</script>
<style lang="scss" scoped>
.form-description {
padding: 20rpx 32px;
font-size: var(--sar-text-sm);
color: var(--sar-secondary-color);
text-align: center;
}
.section {
margin-top: 32rpx;
}
.section-title {
display: flex;
align-items: center;
font-size: var(--sar-text-lg);
}
.section-index {
display: inline-flex;
justify-content: center;
align-items: center;
width: 32rpx;
height: 32rpx;
margin-right: 8rpx;
font-size: var(--sar-text-xs);
color: #fff;
background-color: var(--sar-primary);
}
.section-content {
margin: 0 32rpx;
}
.form-label {
margin: 40rpx 0 10rpx;
}
.form-star {
color: var(--sar-danger);
}
.form-input {
margin-top: 20rpx;
&::after {
display: block;
content: '';
margin-top: 20rpx;
border-top: 1px solid var(--sar-border-color);
transform: scaleY(0.5);
}
}
.footer {
position: sticky;
bottom: 0;
margin-top: 40rpx;
padding: 20rpx 32rpx calc(20rpx + env(safe-area-inset-bottom));
border-top: 1px solid var(--sar-border-color);
background-color: var(--sar-emphasis-bg);
}
</style><template>
<page-meta :page-style="isLocked ? 'overflow: hidden' : ''"></page-meta>
<doc-page title="服务人员满意度评分表" emphasis padding-bottom="0">
<sar-form-plain
ref="formRef"
:model="formModel"
:rules="formRules"
scroll-to-first-error
:scroll-into-view-options="{
startOffset,
position: 'start',
}"
>
<view class="form-description">
尊敬的客户:感谢您对我们服务的支持!请根据您的真实体验为服务人员评分。
</view>
<view class="section">
<view class="section-title">
<text class="section-index">1</text>
<text>基本信息</text>
</view>
<view class="section-content">
<sar-form-item-plain name="staff_name" class="form-item">
<template #custom="{ shouldShowStar }">
<view class="form-label">
<text v-if="shouldShowStar" class="form-star">*</text>
服务人员姓名/工号:
</view>
<view class="form-input">
<sar-input
v-model="formModel.staff_name"
placeholder="请输入服务人员姓名/工号"
clearable
inlaid
/>
</view>
</template>
</sar-form-item-plain>
<sar-form-item-plain name="service_date">
<template #custom="{ shouldShowStar }">
<view class="form-label">
<text v-if="shouldShowStar" class="form-star">*</text>
服务日期:
</view>
<view class="form-input">
<sar-datetime-picker-input
v-model="formModel.service_date"
placeholder="请选择服务日期"
clearable
/>
</view>
</template>
</sar-form-item-plain>
<sar-form-item-plain name="contact">
<template #custom="{ shouldShowStar }">
<view class="form-label">
<text v-if="shouldShowStar" class="form-star">*</text>
您的联系方式(可选)
</view>
<view class="form-input">
<sar-input
type="textarea"
v-model="formModel.contact"
placeholder="请输入联系方式"
clearable
inlaid
auto-height
min-height="48rpx"
/>
</view>
</template>
</sar-form-item-plain>
</view>
</view>
<view class="section">
<view class="section-title">
<text class="section-index">2</text>
<text>服务人员评分</text>
</view>
<view class="section-content">
<sar-form-item-plain name="professionalism">
<template #custom="{ shouldShowStar }">
<view class="form-label">
<text v-if="shouldShowStar" class="form-star">*</text>
专业能力:是否具备足够的知识和技能?
</view>
<RadioList
v-model="formModel.professionalism"
:options="ratingOptions"
/>
</template>
</sar-form-item-plain>
<sar-form-item-plain name="attitude">
<template #custom="{ shouldShowStar }">
<view class="form-label">
<text v-if="shouldShowStar" class="form-star">*</text>
服务态度:是否礼貌、耐心、热情?
</view>
<RadioList
v-model="formModel.attitude"
:options="ratingOptions"
/>
</template>
</sar-form-item-plain>
<sar-form-item-plain name="response_speed">
<template #custom="{ shouldShowStar }">
<view class="form-label">
<text v-if="shouldShowStar" class="form-star">*</text>
响应速度:问题处理是否及时?
</view>
<RadioList
v-model="formModel.response_speed"
:options="ratingOptions"
/>
</template>
</sar-form-item-plain>
<sar-form-item-plain name="communication">
<template #custom="{ shouldShowStar }">
<view class="form-label">
<text v-if="shouldShowStar" class="form-star">*</text>
沟通能力:表达是否清晰、易于理解?
</view>
<RadioList
v-model="formModel.communication"
:options="ratingOptions"
/>
</template>
</sar-form-item-plain>
<sar-form-item-plain name="problem_solving">
<template #custom="{ shouldShowStar }">
<view class="form-label">
<text v-if="shouldShowStar" class="form-star">*</text>
问题解决:是否有效解决您的需求?
</view>
<RadioList
v-model="formModel.problem_solving"
:options="ratingOptions"
/>
</template>
</sar-form-item-plain>
</view>
</view>
<view class="section">
<view class="section-title">
<text class="section-index">3</text>
<text>整体评价</text>
</view>
<view class="section-content">
<sar-form-item-plain name="overall_satisfaction">
<template #custom="{ shouldShowStar }">
<view class="form-label">
<text v-if="shouldShowStar" class="form-star">*</text>
本次服务的整体满意度
</view>
<RadioList
v-model="formModel.overall_satisfaction"
:options="ratingOptions"
/>
</template>
</sar-form-item-plain>
<sar-form-item-plain name="would_return">
<template #custom="{ shouldShowStar }">
<view class="form-label">
<text v-if="shouldShowStar" class="form-star">*</text>
您是否会再次选择该服务人员?
</view>
<RadioList
v-model="formModel.would_return"
:options="returnOptions"
/>
</template>
</sar-form-item-plain>
</view>
</view>
<view class="section">
<view class="section-title">
<text class="section-index">4</text>
<text>开放式反馈</text>
</view>
<view class="section-content">
<sar-form-item-plain name="strengths">
<view class="form-label">服务人员的优点(可选)</view>
<view class="form-input">
<sar-input
v-model="formModel.strengths"
type="textarea"
placeholder="请输入服务人员的优点"
clearable
inlaid
auto-height
min-height="48rpx"
/>
</view>
</sar-form-item-plain>
<sar-form-item-plain name="improvements">
<view class="form-label">需要改进的方面(可选)</view>
<view class="form-input">
<sar-input
v-model="formModel.improvements"
type="textarea"
placeholder="请输入需要改进的方面"
clearable
inlaid
auto-height
min-height="48rpx"
/>
</view>
</sar-form-item-plain>
<sar-form-item-plain name="suggestions">
<view class="form-label">其他建议(可选)</view>
<view class="form-input">
<sar-input
v-model="formModel.suggestions"
type="textarea"
placeholder="请输入其他建议"
clearable
inlaid
auto-height
min-height="48rpx"
/>
</view>
</sar-form-item-plain>
</view>
</view>
<view class="footer">
<sar-button @click="onSubmit">提交评价</sar-button>
</view>
</sar-form-plain>
</doc-page>
</template>
<script setup lang="js">
import { useCurrentPageLock, usePageTopPopup } from "sard-uniapp";
import { onBackPress } from "@dcloudio/uni-app";
import { reactive, ref } from "vue";
import {
toast,
getWindowInfo
} from "sard-uniapp";
const startOffset = ref(getWindowInfo().statusBarHeight + 60);
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: "\u8BF7\u8F93\u5165\u670D\u52A1\u4EBA\u5458\u59D3\u540D/\u5DE5\u53F7"
},
service_date: {
required: true,
message: "\u8BF7\u9009\u62E9\u670D\u52A1\u65E5\u671F"
},
professionalism: {
required: true,
message: "\u8BF7\u6253\u5206"
},
attitude: {
required: true,
message: "\u8BF7\u6253\u5206"
},
response_speed: {
required: true,
message: "\u8BF7\u6253\u5206"
},
communication: {
required: true,
message: "\u8BF7\u6253\u5206"
},
problem_solving: {
required: true,
message: "\u8BF7\u6253\u5206"
},
overall_satisfaction: {
required: true,
message: "\u8BF7\u9009\u62E9\u6574\u4F53\u6EE1\u610F\u5EA6"
},
would_return: {
required: true,
message: "\u8BF7\u9009\u62E9"
}
};
const formRef = ref();
const onSubmit = () => {
formRef.value?.validate().then(() => {
toast("\u63D0\u4EA4\u6210\u529F!");
}).catch((error) => {
toast(error[0].message);
console.log("error submit!", error);
});
};
const ratingOptions = [
{ value: "1", label: "\u4E0D\u6EE1\u610F" },
{ value: "2", label: "\u4E00\u822C" },
{ value: "3", label: "\u57FA\u672C\u6EE1\u610F" },
{ value: "4", label: "\u975E\u5E38\u6EE1\u610F" }
];
const returnOptions = [
{ value: "yes", label: "\u662F" },
{ value: "no", label: "\u5426" },
{ value: "unsure", label: "\u4E0D\u786E\u5B9A" }
];
const { isLocked } = useCurrentPageLock();
const { shouldStopBack, hidePopup } = usePageTopPopup();
onBackPress(() => {
if (shouldStopBack.value) {
hidePopup();
return true;
}
});
</script>
<style lang="scss" scoped>
.form-description {
padding: 20rpx 32px;
font-size: var(--sar-text-sm);
color: var(--sar-secondary-color);
text-align: center;
}
.section {
margin-top: 32rpx;
}
.section-title {
display: flex;
align-items: center;
font-size: var(--sar-text-lg);
}
.section-index {
display: inline-flex;
justify-content: center;
align-items: center;
width: 32rpx;
height: 32rpx;
margin-right: 8rpx;
font-size: var(--sar-text-xs);
color: #fff;
background-color: var(--sar-primary);
}
.section-content {
margin: 0 32rpx;
}
.form-label {
margin: 40rpx 0 10rpx;
}
.form-star {
color: var(--sar-danger);
}
.form-input {
margin-top: 20rpx;
&::after {
display: block;
content: '';
margin-top: 20rpx;
border-top: 1px solid var(--sar-border-color);
transform: scaleY(0.5);
}
}
.footer {
position: sticky;
bottom: 0;
margin-top: 40rpx;
padding: 20rpx 32rpx calc(20rpx + env(safe-area-inset-bottom));
border-top: 1px solid var(--sar-border-color);
background-color: var(--sar-emphasis-bg);
}
</style>RadioList.vue
<template>
<sar-radio-group v-model="value">
<template #custom="{ toggle }">
<sar-list hide-border>
<sar-list-item
v-for="option in options"
:key="option.value"
:title="option.label"
@click="toggle(option.value)"
>
<template #icon>
<sar-radio readonly :value="option.value" />
</template>
</sar-list-item>
</sar-list>
</template>
</sar-radio-group>
</template>
<script setup lang="ts">
const value = defineModel<string>()
defineProps<{
options: {
label: string
value: string
}[]
}>()
</script><template>
<sar-radio-group v-model="value">
<template #custom="{ toggle }">
<sar-list hide-border>
<sar-list-item
v-for="option in options"
:key="option.value"
:title="option.label"
@click="toggle(option.value)"
>
<template #icon>
<sar-radio readonly :value="option.value" />
</template>
</sar-list-item>
</sar-list>
</template>
</sar-radio-group>
</template>
<script setup lang="js">
const value = defineModel();
defineProps();
</script>API
FormProps
| 属性 | 描述 | 类型 | 默认值 |
|---|---|---|---|
| root-class | 组件根元素类名 | string | - |
| root-style | 组件根元素样式 | StyleValue | - |
| model | 表单数据对象 | Record<string, any> | - |
| rules | 表单验证规则 | FormRules | - |
| validate-trigger | 设置字段校验的时机 | TriggerType | change |
| 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' | 'start' |
| star-position | 必填星号在标签的左边或右边 | 'left' | 'right' | 'left' |
| hide-star | 是否隐藏必填时的星号 | boolean | false |
| content-position 1.24.1+ | 内容位置 | 'left' | 'right' | 'left' |
| show-error | 是否显示校验错误信息 | boolean | true |
| scroll-to-first-error | 当校验失败时,滚动到第一个错误表单项 | boolean | false |
| scroll-into-view-options | 自定义滚动配置选项 | ScrollIntoViewOptions | {position: 'nearest', startOffset: 0, endOffset: 0} |
| disabled | 是否禁用该表单内的所有组件。 如果设置为 true, 它将覆盖内部组件的 disabled 属性 | boolean | false |
| readonly | 是否只读该表单内的所有组件。 如果设置为 true, 它将覆盖内部组件的 readonly 属性 | 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
| 属性 | 描述 | 类型 | 默认值 |
|---|---|---|---|
| root-class | 组件根元素类名 | string | - |
| root-style | 组件根元素样式 | StyleValue | - |
| direction | 表单排列方向 | 'horizontal' | 'vertical' | 'horizontal' |
| label-width | 标签宽度 | string | - |
| label-align | 标签水平对齐方式 | 'start' | 'center' | 'end' | 'start' |
| label-valign | 标签垂直对齐方式 | 'start' | 'center' | 'end' | 'start' |
| star-position | 必填星号在标签的左边或右边 | 'left' | 'right' | 'left' |
| content-position 1.24.1+ | 内容位置 | 'left' | 'right' | 'left' |
| label | 标签文本 | string | - |
| required | 是否为必填项,如不设置,则会根据校验规则确认 | boolean | - |
| name | 表单域 model 字段,在使用 validate、reset 方法的情况下,该属性是必填的。 | FieldName | - |
| rules | 表单验证规则 | Rule | Rule[] | - |
| validate-trigger | 设置字段校验的时机 | TriggerType | change |
| error | 表单域验证错误时的提示信息。设置该值会导致表单验证状态变为 error,并显示该错误信息。 | string | - |
| show-error | 是否显示校验错误信息 | boolean | true |
| inlaid | 去掉边框和内边距,用于嵌入到其他组件中 | boolean | false |
FormItemSlots
| 插槽 | 描述 | 属性 |
|---|---|---|
| default | 自定义默认内容 | - |
| label | 自定义标签内容 | - |
| validate | 同默认插槽,额外接受当前验证状态属性 | { state: ValidateState } |
| error 1.22+ | 自定义错误信息内容 | { 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 | 使用函数自定义验证,具体说明如下 | (value: any, rule: Rule) => 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'] 说明
当函数返回值为 fulfilled 状态的 Promise 或者 true 则验证通过,否则验证不通过; Promise.reject 的参数或者返回的字符串会作为错误验证信息,如果返回的不是字符串,则取 Rule['message'] 的配置作为错误信息。
ValidatorType
| 类型 | 描述 |
|---|---|
| string | 必须为字符串 |
| number | 必须为数值 |
| boolean | 必须为布尔值 |
| function | 必须为函数 |
| regexp | 必须为 RegExp 类型,或者作为 RegExp() 参数被实例化不会报错的字符串 |
| integer | 必须为数值,且为整数 |
| float | 必须为数值,且为小数 |
| array | 必须为数组 |
| object | 必须为对象,且不为数组和 null |
| enum | 必须存在于 Rule['enum'] 中 |
| date | 必须为 Date 类型 |
| url | 必须为 url |
| hex | 必须为 hex |
| 必须为邮件 |
FormPlainProps 1.23+
继承 FormProps。
FormPlainSlots 1.23+
继承 FormSlots。
FormPlainExpose 1.23+
继承 FormExpose。
FormItemPlainProps 1.23+
继承 FormItemProps。
FormItemPlainSlots 1.23+
| 插槽 | 描述 | 属性 |
|---|---|---|
| default | 自定义默认内容 | - |
| custom | 自定义默认内容 | FormItemPlainSlotsProps |
FormItemPlainSlotsProps 1.23+
| 属性 | 描述 | 类型 |
|---|---|---|
| validateState | 表单验证状态 | ValidateState |
| shouldShowStar | 是否显示星号 | boolean |
| validateMessage | 当前验证信息 | string |
| shouldShowError | 是否显示错误信息 | boolean |
| direction | 表单排列方向 | FormProps['direction'] |
| labelAlign | 标签水平对齐方式 | FormProps['labelAlign'] |
| labelValign | 标签垂直对齐方式 | FormProps['labelValign'] |
| starPosition | 星号位置 | FormProps['starPosition'] |
| labelWidth | 标签宽度 | FormProps['labelWidth'] |
FormItemPlainExpose 1.23+
继承 FormItemExpose。
主题定制
CSS 变量
page,
.sar-portal {
--sar-form-bg: var(--sar-emphasis-bg);
--sar-form-card-border-radius: var(--sar-rounded-lg);
--sar-form-item-padding-x: 32rpx;
--sar-form-item-padding-y: 20rpx;
--sar-form-item-border-color: var(--sar-border-color);
--sar-form-item-label-width: 176rpx;
--sar-form-item-label-margin-right: 24rpx;
--sar-form-item-label-margin-bottom: 8rpx;
--sar-form-item-label-font-size: var(--sar-text-base);
--sar-form-item-label-line-height: var(--sar-leading-normal);
--sar-form-item-star-font-size: var(--sar-text-base);
--sar-form-item-star-line-height: var(--sar-leading-normal);
--sar-form-item-star-color: var(--sar-danger);
--sar-form-item-star-gap: 8rpx;
--sar-form-item-error-margin-top: 4px;
--sar-form-item-error-font-size: var(--sar-text-sm);
--sar-form-item-error-line-height: var(--sar-leading-tight);
--sar-form-item-error-color: var(--sar-danger);
}