添加 cl-form 组件
This commit is contained in:
@@ -595,6 +595,22 @@ export function base64ToBlob(data: string, type: string = "image/jpeg"): Blob {
|
|||||||
// #endif
|
// #endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查两个值是否相等
|
||||||
|
* @param a 值1
|
||||||
|
* @param b 值2
|
||||||
|
* @returns 是否相等
|
||||||
|
*/
|
||||||
|
export function isEqual(a: any, b: any): boolean {
|
||||||
|
if (isObject(a) && isObject(b)) {
|
||||||
|
return isEqual(JSON.stringify(a), JSON.stringify(b));
|
||||||
|
} else if (isArray(a) && isArray(b)) {
|
||||||
|
return isEqual(JSON.stringify(a), JSON.stringify(b));
|
||||||
|
}
|
||||||
|
|
||||||
|
return a == b;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 检查是否为小程序环境
|
* 检查是否为小程序环境
|
||||||
* @returns 是否为小程序环境
|
* @returns 是否为小程序环境
|
||||||
|
|||||||
@@ -111,11 +111,11 @@ export const parseClass = (data: any): string => {
|
|||||||
* @returns 转换后的UTSJSONObject对象
|
* @returns 转换后的UTSJSONObject对象
|
||||||
*/
|
*/
|
||||||
export function parseToObject<T>(data: T): UTSJSONObject {
|
export function parseToObject<T>(data: T): UTSJSONObject {
|
||||||
// #ifdef APP
|
// #ifdef APP-ANDROID
|
||||||
return JSON.parseObject(JSON.stringify(data)!)!;
|
return JSON.parseObject(JSON.stringify(data)!)!;
|
||||||
// #endif
|
// #endif
|
||||||
|
|
||||||
// #ifndef APP
|
// #ifndef APP-ANDROID
|
||||||
return JSON.parse(JSON.stringify(data)) as UTSJSONObject;
|
return JSON.parse(JSON.stringify(data)) as UTSJSONObject;
|
||||||
// #endif
|
// #endif
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,45 +12,93 @@
|
|||||||
:disabled="saving"
|
:disabled="saving"
|
||||||
label-position="top"
|
label-position="top"
|
||||||
>
|
>
|
||||||
<cl-form-item label="头像" prop="avatarUrl" required>
|
<cl-form-item prop="avatarUrl">
|
||||||
<cl-upload v-model="formData.avatarUrl"></cl-upload>
|
<cl-upload v-model="formData.avatarUrl" test></cl-upload>
|
||||||
</cl-form-item>
|
</cl-form-item>
|
||||||
|
|
||||||
<cl-form-item label="用户名" prop="nickName" required>
|
<cl-form-item :label="t('用户名')" prop="nickName" required>
|
||||||
<cl-input v-model="formData.nickName" placeholder="请输入用户名"></cl-input>
|
<cl-input
|
||||||
|
v-model="formData.nickName"
|
||||||
|
:placeholder="t('请输入用户名')"
|
||||||
|
clearable
|
||||||
|
></cl-input>
|
||||||
</cl-form-item>
|
</cl-form-item>
|
||||||
|
|
||||||
<cl-form-item label="邮箱" prop="email">
|
<cl-form-item :label="t('邮箱')" prop="email" required>
|
||||||
<cl-input v-model="formData.email" placeholder="请输入邮箱地址"></cl-input>
|
<cl-input
|
||||||
|
v-model="formData.email"
|
||||||
|
:placeholder="t('请输入邮箱地址')"
|
||||||
|
></cl-input>
|
||||||
</cl-form-item>
|
</cl-form-item>
|
||||||
|
|
||||||
<cl-form-item label="年龄" prop="age">
|
<cl-form-item :label="t('身高(cm)')" prop="height" required>
|
||||||
<cl-input-number
|
<cl-slider v-model="formData.height" :max="220" show-value>
|
||||||
v-model="formData.age"
|
<template #value="{ value }">
|
||||||
:min="18"
|
<cl-text
|
||||||
:max="50"
|
:pt="{
|
||||||
></cl-input-number>
|
className: 'text-center w-[120rpx]'
|
||||||
|
}"
|
||||||
|
>{{ value }} cm</cl-text
|
||||||
|
>
|
||||||
|
</template>
|
||||||
|
</cl-slider>
|
||||||
</cl-form-item>
|
</cl-form-item>
|
||||||
|
|
||||||
<cl-form-item label="性别" prop="gender">
|
<cl-form-item :label="t('体重(kg)')" prop="weight" required>
|
||||||
<cl-select
|
<cl-slider v-model="formData.weight" :max="150" show-value>
|
||||||
v-model="formData.gender"
|
<template #value="{ value }">
|
||||||
:options="options['gender']"
|
<cl-text
|
||||||
></cl-select>
|
:pt="{
|
||||||
|
className: 'text-center w-[120rpx]'
|
||||||
|
}"
|
||||||
|
>{{ value }} kg</cl-text
|
||||||
|
>
|
||||||
|
</template>
|
||||||
|
</cl-slider>
|
||||||
</cl-form-item>
|
</cl-form-item>
|
||||||
|
|
||||||
<cl-form-item label="个人简介" prop="description">
|
<cl-form-item :label="t('标签')" prop="tags" required>
|
||||||
|
<view class="flex flex-row flex-wrap">
|
||||||
|
<cl-checkbox
|
||||||
|
v-model="formData.tags"
|
||||||
|
v-for="(item, index) in tagsOptions"
|
||||||
|
:value="index"
|
||||||
|
:pt="{
|
||||||
|
className: 'mr-5 mt-2'
|
||||||
|
}"
|
||||||
|
>{{ item.label }}</cl-checkbox
|
||||||
|
>
|
||||||
|
</view>
|
||||||
|
</cl-form-item>
|
||||||
|
|
||||||
|
<cl-form-item :label="t('性别')" prop="gender" required>
|
||||||
|
<cl-select v-model="formData.gender" :options="genderOptions"></cl-select>
|
||||||
|
</cl-form-item>
|
||||||
|
|
||||||
|
<cl-form-item :label="t('所在地区')" prop="pca" required>
|
||||||
|
<cl-cascader v-model="formData.pca" :options="pcaOptions"></cl-cascader>
|
||||||
|
</cl-form-item>
|
||||||
|
|
||||||
|
<cl-form-item :label="t('出生年月')" prop="birthday" required>
|
||||||
|
<cl-select-date v-model="formData.birthday" type="date"></cl-select-date>
|
||||||
|
</cl-form-item>
|
||||||
|
|
||||||
|
<cl-form-item :label="t('个人简介')" prop="description">
|
||||||
<cl-textarea
|
<cl-textarea
|
||||||
v-model="formData.description"
|
v-model="formData.description"
|
||||||
placeholder="请输入个人简介"
|
:placeholder="t('请输入个人简介')"
|
||||||
:maxlength="200"
|
:maxlength="200"
|
||||||
></cl-textarea>
|
></cl-textarea>
|
||||||
</cl-form-item>
|
</cl-form-item>
|
||||||
|
|
||||||
|
<cl-form-item :label="t('公开状态')">
|
||||||
|
<cl-switch v-model="formData.isPublic"></cl-switch>
|
||||||
|
</cl-form-item>
|
||||||
</cl-form>
|
</cl-form>
|
||||||
</demo-item>
|
</demo-item>
|
||||||
|
|
||||||
<demo-item>
|
<demo-item>
|
||||||
<cl-text :pt="{ className: '!text-sm p-2' }">{{
|
<cl-text pre-wrap :pt="{ className: '!text-sm p-2' }">{{
|
||||||
JSON.stringify(formData, null, 4)
|
JSON.stringify(formData, null, 4)
|
||||||
}}</cl-text>
|
}}</cl-text>
|
||||||
</demo-item>
|
</demo-item>
|
||||||
@@ -58,13 +106,15 @@
|
|||||||
|
|
||||||
<cl-footer>
|
<cl-footer>
|
||||||
<view class="flex flex-row">
|
<view class="flex flex-row">
|
||||||
<cl-button type="info" :pt="{ className: 'flex-1' }" @click="reset">重置</cl-button>
|
<cl-button type="info" :pt="{ className: 'flex-1' }" @click="reset">{{
|
||||||
|
t("重置")
|
||||||
|
}}</cl-button>
|
||||||
<cl-button
|
<cl-button
|
||||||
type="primary"
|
type="primary"
|
||||||
:loading="saving"
|
:loading="saving"
|
||||||
:pt="{ className: 'flex-1' }"
|
:pt="{ className: 'flex-1' }"
|
||||||
@click="submit"
|
@click="submit"
|
||||||
>提交</cl-button
|
>{{ t("提交") }}</cl-button
|
||||||
>
|
>
|
||||||
</view>
|
</view>
|
||||||
</cl-footer>
|
</cl-footer>
|
||||||
@@ -72,38 +122,78 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { reactive, ref, type Ref } from "vue";
|
import { ref, type Ref } from "vue";
|
||||||
import DemoItem from "../components/item.uvue";
|
import DemoItem from "../components/item.uvue";
|
||||||
import { useForm, useUi, type ClFormRule, type ClSelectOption } from "@/uni_modules/cool-ui";
|
import {
|
||||||
|
useCascader,
|
||||||
|
useForm,
|
||||||
|
useUi,
|
||||||
|
type ClFormRule,
|
||||||
|
type ClSelectOption
|
||||||
|
} from "@/uni_modules/cool-ui";
|
||||||
|
import pca from "@/data/pca.json";
|
||||||
|
import { t } from "@/locale";
|
||||||
|
import { dayUts } from "@/cool";
|
||||||
|
|
||||||
const ui = useUi();
|
const ui = useUi();
|
||||||
const { formRef, validate, clearValidate } = useForm();
|
const { formRef, validate, clearValidate } = useForm();
|
||||||
|
|
||||||
const options = reactive({
|
// 性别选项
|
||||||
gender: [
|
const genderOptions = [
|
||||||
{
|
{
|
||||||
label: "未知",
|
label: t("未知"),
|
||||||
value: 0
|
value: 0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "男",
|
label: t("男"),
|
||||||
value: 1
|
value: 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "女",
|
label: t("女"),
|
||||||
value: 2
|
value: 2
|
||||||
}
|
}
|
||||||
] as ClSelectOption[]
|
] as ClSelectOption[];
|
||||||
});
|
|
||||||
|
|
||||||
|
// 标签选项
|
||||||
|
const tagsOptions = [
|
||||||
|
{
|
||||||
|
label: t("篮球"),
|
||||||
|
value: 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t("足球"),
|
||||||
|
value: 2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t("羽毛球"),
|
||||||
|
value: 3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t("乒乓球"),
|
||||||
|
value: 4
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t("游泳"),
|
||||||
|
value: 5
|
||||||
|
}
|
||||||
|
] as ClSelectOption[];
|
||||||
|
|
||||||
|
// 地区选项
|
||||||
|
const pcaOptions = useCascader(pca);
|
||||||
|
|
||||||
|
// 自定义表单数据类型
|
||||||
type FormData = {
|
type FormData = {
|
||||||
avatarUrl: string;
|
avatarUrl: string;
|
||||||
nickName: string;
|
nickName: string;
|
||||||
email: string;
|
email: string;
|
||||||
age: number;
|
height: number;
|
||||||
|
weight: number;
|
||||||
gender: number;
|
gender: number;
|
||||||
description: string;
|
description: string;
|
||||||
pics: string[];
|
pca: string[];
|
||||||
|
tags: number[];
|
||||||
|
birthday: string;
|
||||||
|
isPublic: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
// 表单数据
|
// 表单数据
|
||||||
@@ -111,27 +201,68 @@ const formData = ref<FormData>({
|
|||||||
avatarUrl: "",
|
avatarUrl: "",
|
||||||
nickName: "神仙都没用",
|
nickName: "神仙都没用",
|
||||||
email: "",
|
email: "",
|
||||||
age: 18,
|
height: 180,
|
||||||
|
weight: 70,
|
||||||
gender: 0,
|
gender: 0,
|
||||||
description: "",
|
description: "",
|
||||||
pics: []
|
pca: [],
|
||||||
|
tags: [1, 2],
|
||||||
|
birthday: "",
|
||||||
|
isPublic: false
|
||||||
}) as Ref<FormData>;
|
}) as Ref<FormData>;
|
||||||
|
|
||||||
// 表单验证规则
|
// 表单验证规则
|
||||||
const rules = new Map<string, ClFormRule[]>([
|
const rules = new Map<string, ClFormRule[]>([
|
||||||
["avatarUrl", [{ required: true, message: "头像不能为空" }]],
|
|
||||||
[
|
[
|
||||||
"nickName",
|
"nickName",
|
||||||
[
|
[
|
||||||
{ required: true, message: "用户名不能为空" },
|
{ required: true, message: t("用户名不能为空") },
|
||||||
{ min: 3, max: 20, message: "用户名长度在3-20个字符之间" }
|
{ min: 3, max: 20, message: t("用户名长度在3-20个字符之间") }
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
"email",
|
"email",
|
||||||
[
|
[
|
||||||
{ required: true, message: "邮箱不能为空" },
|
{ required: true, message: t("邮箱不能为空") },
|
||||||
{ pattern: /^[^\s@]+@[^\s@]+\.[^\s@]+$/, message: "邮箱格式不正确" }
|
{ pattern: /^[^\s@]+@[^\s@]+\.[^\s@]+$/, message: t("邮箱格式不正确") }
|
||||||
|
]
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"height",
|
||||||
|
[
|
||||||
|
{ required: true, message: t("身高不能为空") },
|
||||||
|
{ min: 160, max: 190, message: t("身高在160-190cm之间") }
|
||||||
|
]
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"weight",
|
||||||
|
[
|
||||||
|
{ required: true, message: t("体重不能为空") },
|
||||||
|
{ min: 40, max: 100, message: t("体重在40-100kg之间") }
|
||||||
|
]
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"tags",
|
||||||
|
[
|
||||||
|
{ required: true, message: t("标签不能为空") },
|
||||||
|
{ min: 1, max: 2, message: t("标签最多选择2个") }
|
||||||
|
]
|
||||||
|
],
|
||||||
|
["gender", [{ required: true, message: t("性别不能为空") }]],
|
||||||
|
["pca", [{ required: true, message: t("所在地区不能为空") }]],
|
||||||
|
[
|
||||||
|
"birthday",
|
||||||
|
[
|
||||||
|
{ required: true, message: t("出生年月不能为空") },
|
||||||
|
{
|
||||||
|
validator(value) {
|
||||||
|
if (dayUts(value).isAfter(dayUts("2010-01-01"))) {
|
||||||
|
return t("出生年月不大于2010-01-01");
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
]);
|
]);
|
||||||
@@ -139,18 +270,24 @@ const rules = new Map<string, ClFormRule[]>([
|
|||||||
// 是否保存中
|
// 是否保存中
|
||||||
const saving = ref(false);
|
const saving = ref(false);
|
||||||
|
|
||||||
|
// 重置表单数据
|
||||||
function reset() {
|
function reset() {
|
||||||
formData.value.avatarUrl = "";
|
formData.value.avatarUrl = "";
|
||||||
formData.value.nickName = "";
|
formData.value.nickName = "";
|
||||||
formData.value.email = "";
|
formData.value.email = "";
|
||||||
formData.value.age = 18;
|
formData.value.height = 180;
|
||||||
|
formData.value.weight = 70;
|
||||||
formData.value.gender = 0;
|
formData.value.gender = 0;
|
||||||
formData.value.description = "";
|
formData.value.description = "";
|
||||||
formData.value.pics = [];
|
formData.value.pca = [];
|
||||||
|
formData.value.tags = [];
|
||||||
|
formData.value.birthday = "";
|
||||||
|
formData.value.isPublic = false;
|
||||||
|
|
||||||
clearValidate();
|
clearValidate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 提交表单
|
||||||
function submit() {
|
function submit() {
|
||||||
validate((valid, errors) => {
|
validate((valid, errors) => {
|
||||||
if (valid) {
|
if (valid) {
|
||||||
@@ -158,7 +295,7 @@ function submit() {
|
|||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
ui.showToast({
|
ui.showToast({
|
||||||
message: "提交成功",
|
message: t("提交成功"),
|
||||||
icon: "check-line"
|
icon: "check-line"
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -42,7 +42,7 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, onMounted, onUnmounted, watch, type PropType } from "vue";
|
import { computed, onMounted, onUnmounted, watch, type PropType } from "vue";
|
||||||
import { parseClass, parsePt } from "@/cool";
|
import { isEqual, parseClass, parsePt } from "@/cool";
|
||||||
import type { ClFormLabelPosition, PassThroughProps } from "../../types";
|
import type { ClFormLabelPosition, PassThroughProps } from "../../types";
|
||||||
import { useForm } from "../../hooks";
|
import { useForm } from "../../hooks";
|
||||||
|
|
||||||
@@ -163,10 +163,20 @@ watch(
|
|||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
watch(
|
watch(
|
||||||
computed(() => getValue(props.prop)!),
|
computed(() => {
|
||||||
() => {
|
const value = getValue(props.prop);
|
||||||
|
|
||||||
|
if (value == null) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}),
|
||||||
|
(val: any, val2: any) => {
|
||||||
if (props.required) {
|
if (props.required) {
|
||||||
validateField(props.prop);
|
if (!isEqual(val, val2)) {
|
||||||
|
validateField(props.prop);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -67,6 +67,7 @@ const props = defineProps({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 透传样式类型
|
||||||
type PassThrough = {
|
type PassThrough = {
|
||||||
className?: string;
|
className?: string;
|
||||||
};
|
};
|
||||||
@@ -98,8 +99,15 @@ const showMessage = computed(() => props.showMessage);
|
|||||||
// 是否禁用整个表单
|
// 是否禁用整个表单
|
||||||
const disabled = computed(() => props.disabled);
|
const disabled = computed(() => props.disabled);
|
||||||
|
|
||||||
|
// 错误信息锁定
|
||||||
|
const errorLock = ref(false);
|
||||||
|
|
||||||
// 设置字段错误信息
|
// 设置字段错误信息
|
||||||
function setError(prop: string, error: string) {
|
function setError(prop: string, error: string) {
|
||||||
|
if (errorLock.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (prop != "") {
|
if (prop != "") {
|
||||||
errors.value.set(prop, error);
|
errors.value.set(prop, error);
|
||||||
}
|
}
|
||||||
@@ -171,17 +179,29 @@ function validateRule(value: any | null, rule: ClFormRule): null | string {
|
|||||||
|
|
||||||
// 最小长度验证
|
// 最小长度验证
|
||||||
if (rule.min != null) {
|
if (rule.min != null) {
|
||||||
const len = Array.isArray(value) ? value.length : `${value}`.length;
|
if (typeof value == "number") {
|
||||||
if (len < rule.min) {
|
if ((value as number) < rule.min) {
|
||||||
return rule.message ?? $t(`最少需要{min}个字符`, { min: rule.min });
|
return rule.message ?? $t(`最小值为{min}`, { min: rule.min });
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const len = Array.isArray(value) ? value.length : `${value}`.length;
|
||||||
|
if (len < rule.min) {
|
||||||
|
return rule.message ?? $t(`最少需要{min}个字符`, { min: rule.min });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 最大长度验证
|
// 最大长度验证
|
||||||
if (rule.max != null) {
|
if (rule.max != null) {
|
||||||
const len = Array.isArray(value) ? value.length : `${value}`.length;
|
if (typeof value == "number") {
|
||||||
if (len > rule.max) {
|
if (value > rule.max) {
|
||||||
return rule.message ?? $t(`最多允许{max}个字符`, { max: rule.max });
|
return rule.message ?? $t(`最大值为{max}`, { max: rule.max });
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const len = Array.isArray(value) ? value.length : `${value}`.length;
|
||||||
|
if (len > rule.max) {
|
||||||
|
return rule.message ?? $t(`最多允许{max}个字符`, { max: rule.max });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -205,8 +225,11 @@ function validateRule(value: any | null, rule: ClFormRule): null | string {
|
|||||||
|
|
||||||
// 清除所有验证
|
// 清除所有验证
|
||||||
function clearValidate() {
|
function clearValidate() {
|
||||||
|
errorLock.value = true;
|
||||||
|
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
clearErrors();
|
clearErrors();
|
||||||
|
errorLock.value = false;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user