添加 cl-form 组件
This commit is contained in:
@@ -1,22 +1,22 @@
|
||||
import { getCurrentInstance } from "vue";
|
||||
|
||||
/**
|
||||
* 获取父组件实例
|
||||
*
|
||||
* 用于在子组件中获取父组件实例,以便访问父组件的属性和方法
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* // 在子组件中使用
|
||||
* const parent = useParent<ParentType>();
|
||||
* // 访问父组件属性
|
||||
* console.log(parent.someProperty);
|
||||
* ```
|
||||
*
|
||||
* @template T 父组件实例的类型
|
||||
* @returns {T} 返回父组件实例
|
||||
* 获取父组件
|
||||
* @param name 组件名称
|
||||
* @example useParent<ClFormComponentPublicInstance>("cl-form")
|
||||
* @returns 父组件
|
||||
*/
|
||||
export function useParent<T>(): T {
|
||||
export function useParent<T>(name: string): T | null {
|
||||
const { proxy } = getCurrentInstance()!;
|
||||
return proxy?.$parent as T;
|
||||
|
||||
let p = proxy?.$parent;
|
||||
|
||||
while (p != null) {
|
||||
if (p.$options.name == name) {
|
||||
return p as T | null;
|
||||
}
|
||||
p = p.$parent;
|
||||
}
|
||||
|
||||
return p as T | null;
|
||||
}
|
||||
|
||||
@@ -166,7 +166,7 @@ export function get(object: any, path: string, defaultValue: any | null = null):
|
||||
* 设置对象的属性值
|
||||
* @example set({a: 1}, 'b', 2) // {a: 1, b: 2}
|
||||
*/
|
||||
export function set(object: any, key: string, value: any): void {
|
||||
export function set(object: any, key: string, value: any | null): void {
|
||||
(object as UTSJSONObject)[key] = value;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { ref, type Ref } from "vue";
|
||||
import { forEach, forInObject, isArray, isObject, isString } from "./comm";
|
||||
|
||||
/**
|
||||
@@ -104,6 +105,21 @@ export const parseClass = (data: any): string => {
|
||||
return names.join(" ");
|
||||
};
|
||||
|
||||
/**
|
||||
* 将自定义类型数据转换为UTSJSONObject对象
|
||||
* @param data 要转换的数据
|
||||
* @returns 转换后的UTSJSONObject对象
|
||||
*/
|
||||
export function parseToObject<T>(data: T): UTSJSONObject {
|
||||
// #ifdef APP
|
||||
return JSON.parseObject(JSON.stringify(data)!)!;
|
||||
// #endif
|
||||
|
||||
// #ifndef APP
|
||||
return JSON.parse(JSON.stringify(data)) as UTSJSONObject;
|
||||
// #endif
|
||||
}
|
||||
|
||||
/**
|
||||
* 将数值或字符串转换为rpx单位的字符串
|
||||
* @param val 要转换的值,可以是数字或字符串
|
||||
|
||||
@@ -112,6 +112,12 @@
|
||||
"navigationBarTitleText": "Tag 标签"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "form/form",
|
||||
"style": {
|
||||
"navigationBarTitleText": "Form 表单验证"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "form/input",
|
||||
"style": {
|
||||
|
||||
175
pages/demo/form/form.uvue
Normal file
175
pages/demo/form/form.uvue
Normal file
@@ -0,0 +1,175 @@
|
||||
<template>
|
||||
<cl-page>
|
||||
<view class="p-3">
|
||||
<demo-item>
|
||||
<cl-form
|
||||
:pt="{
|
||||
className: 'p-2 pb-0'
|
||||
}"
|
||||
v-model="formData"
|
||||
ref="formRef"
|
||||
:rules="rules"
|
||||
:disabled="saving"
|
||||
label-position="top"
|
||||
>
|
||||
<cl-form-item label="头像" prop="avatarUrl" required>
|
||||
<cl-upload v-model="formData.avatarUrl"></cl-upload>
|
||||
</cl-form-item>
|
||||
|
||||
<cl-form-item label="用户名" prop="nickName" required>
|
||||
<cl-input v-model="formData.nickName" placeholder="请输入用户名"></cl-input>
|
||||
</cl-form-item>
|
||||
|
||||
<cl-form-item label="邮箱" prop="email">
|
||||
<cl-input v-model="formData.email" placeholder="请输入邮箱地址"></cl-input>
|
||||
</cl-form-item>
|
||||
|
||||
<cl-form-item label="年龄" prop="age">
|
||||
<cl-input-number
|
||||
v-model="formData.age"
|
||||
:min="18"
|
||||
:max="50"
|
||||
></cl-input-number>
|
||||
</cl-form-item>
|
||||
|
||||
<cl-form-item label="性别" prop="gender">
|
||||
<cl-select
|
||||
v-model="formData.gender"
|
||||
:options="options['gender']"
|
||||
></cl-select>
|
||||
</cl-form-item>
|
||||
|
||||
<cl-form-item label="个人简介" prop="description">
|
||||
<cl-textarea
|
||||
v-model="formData.description"
|
||||
placeholder="请输入个人简介"
|
||||
:maxlength="200"
|
||||
></cl-textarea>
|
||||
</cl-form-item>
|
||||
</cl-form>
|
||||
</demo-item>
|
||||
|
||||
<demo-item>
|
||||
<cl-text :pt="{ className: '!text-sm p-2' }">{{
|
||||
JSON.stringify(formData, null, 4)
|
||||
}}</cl-text>
|
||||
</demo-item>
|
||||
</view>
|
||||
|
||||
<cl-footer>
|
||||
<view class="flex flex-row">
|
||||
<cl-button type="info" :pt="{ className: 'flex-1' }" @click="reset">重置</cl-button>
|
||||
<cl-button
|
||||
type="primary"
|
||||
:loading="saving"
|
||||
:pt="{ className: 'flex-1' }"
|
||||
@click="submit"
|
||||
>提交</cl-button
|
||||
>
|
||||
</view>
|
||||
</cl-footer>
|
||||
</cl-page>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { reactive, ref, type Ref } from "vue";
|
||||
import DemoItem from "../components/item.uvue";
|
||||
import { useForm, useUi, type ClFormRule, type ClSelectOption } from "@/uni_modules/cool-ui";
|
||||
|
||||
const ui = useUi();
|
||||
const { formRef, validate, clearValidate } = useForm();
|
||||
|
||||
const options = reactive({
|
||||
gender: [
|
||||
{
|
||||
label: "未知",
|
||||
value: 0
|
||||
},
|
||||
{
|
||||
label: "男",
|
||||
value: 1
|
||||
},
|
||||
{
|
||||
label: "女",
|
||||
value: 2
|
||||
}
|
||||
] as ClSelectOption[]
|
||||
});
|
||||
|
||||
type FormData = {
|
||||
avatarUrl: string;
|
||||
nickName: string;
|
||||
email: string;
|
||||
age: number;
|
||||
gender: number;
|
||||
description: string;
|
||||
pics: string[];
|
||||
};
|
||||
|
||||
// 表单数据
|
||||
const formData = ref<FormData>({
|
||||
avatarUrl: "",
|
||||
nickName: "神仙都没用",
|
||||
email: "",
|
||||
age: 18,
|
||||
gender: 0,
|
||||
description: "",
|
||||
pics: []
|
||||
}) as Ref<FormData>;
|
||||
|
||||
// 表单验证规则
|
||||
const rules = new Map<string, ClFormRule[]>([
|
||||
["avatarUrl", [{ required: true, message: "头像不能为空" }]],
|
||||
[
|
||||
"nickName",
|
||||
[
|
||||
{ required: true, message: "用户名不能为空" },
|
||||
{ min: 3, max: 20, message: "用户名长度在3-20个字符之间" }
|
||||
]
|
||||
],
|
||||
[
|
||||
"email",
|
||||
[
|
||||
{ required: true, message: "邮箱不能为空" },
|
||||
{ pattern: /^[^\s@]+@[^\s@]+\.[^\s@]+$/, message: "邮箱格式不正确" }
|
||||
]
|
||||
]
|
||||
]);
|
||||
|
||||
// 是否保存中
|
||||
const saving = ref(false);
|
||||
|
||||
function reset() {
|
||||
formData.value.avatarUrl = "";
|
||||
formData.value.nickName = "";
|
||||
formData.value.email = "";
|
||||
formData.value.age = 18;
|
||||
formData.value.gender = 0;
|
||||
formData.value.description = "";
|
||||
formData.value.pics = [];
|
||||
|
||||
clearValidate();
|
||||
}
|
||||
|
||||
function submit() {
|
||||
validate((valid, errors) => {
|
||||
if (valid) {
|
||||
saving.value = true;
|
||||
|
||||
setTimeout(() => {
|
||||
ui.showToast({
|
||||
message: "提交成功",
|
||||
icon: "check-line"
|
||||
});
|
||||
|
||||
saving.value = false;
|
||||
reset();
|
||||
}, 2000);
|
||||
} else {
|
||||
ui.showToast({
|
||||
message: errors[0].message
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
@@ -118,6 +118,11 @@ const data = computed<Item[]>(() => {
|
||||
{
|
||||
label: t("表单组件"),
|
||||
children: [
|
||||
{
|
||||
label: t("表单验证"),
|
||||
icon: "draft-line",
|
||||
path: "/pages/demo/form/form"
|
||||
},
|
||||
{
|
||||
label: t("输入框"),
|
||||
icon: "input-field",
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
:disabled="disabled"
|
||||
:focus="popupRef?.isOpen"
|
||||
:text="text"
|
||||
@tap="open"
|
||||
@open="open"
|
||||
@clear="clear"
|
||||
></cl-select-trigger>
|
||||
|
||||
|
||||
@@ -55,7 +55,7 @@ const props = defineProps({
|
||||
});
|
||||
|
||||
// 获取父组件实例
|
||||
const parent = useParent<ClRowComponentPublicInstance>();
|
||||
const parent = useParent<ClRowComponentPublicInstance>("cl-row");
|
||||
|
||||
// 透传类型定义
|
||||
type PassThrough = {
|
||||
@@ -67,7 +67,7 @@ type PassThrough = {
|
||||
const pt = computed(() => parsePt<PassThrough>(props.pt));
|
||||
|
||||
// 计算列的padding,用于实现栅格间隔
|
||||
const padding = computed(() => parseRpx(parent.gutter / 2));
|
||||
const padding = computed(() => (parent == null ? "0" : parseRpx(parent.gutter / 2)));
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
223
uni_modules/cool-ui/components/cl-form-item/cl-form-item.uvue
Normal file
223
uni_modules/cool-ui/components/cl-form-item/cl-form-item.uvue
Normal file
@@ -0,0 +1,223 @@
|
||||
<template>
|
||||
<view class="cl-form-item" :class="[pt.className]">
|
||||
<view class="cl-form-item__inner" :class="[`is-${labelPosition}`, pt.inner?.className]">
|
||||
<view
|
||||
class="cl-form-item__label"
|
||||
:class="[`is-${labelPosition}`, pt.label?.className]"
|
||||
:style="{
|
||||
width: labelPosition != 'top' ? labelWidth : 'auto'
|
||||
}"
|
||||
v-if="label != ''"
|
||||
>
|
||||
<cl-text>{{ label }}</cl-text>
|
||||
|
||||
<cl-text
|
||||
color="error"
|
||||
:pt="{
|
||||
className: 'ml-1'
|
||||
}"
|
||||
v-if="showAsterisk"
|
||||
>
|
||||
*
|
||||
</cl-text>
|
||||
</view>
|
||||
|
||||
<view class="cl-form-item__content" :class="[pt.content?.className]">
|
||||
<slot></slot>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<slot name="error" :error="errorText" v-if="hasError && showMessage">
|
||||
<cl-text
|
||||
color="error"
|
||||
:pt="{
|
||||
className: parseClass(['mt-2 !text-sm', pt.error?.className])
|
||||
}"
|
||||
>
|
||||
{{ errorText }}
|
||||
</cl-text>
|
||||
</slot>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, onUnmounted, watch, type PropType } from "vue";
|
||||
import { parseClass, parsePt } from "@/cool";
|
||||
import type { ClFormLabelPosition, PassThroughProps } from "../../types";
|
||||
import { useForm } from "../../hooks";
|
||||
|
||||
defineOptions({
|
||||
name: "cl-form-item"
|
||||
});
|
||||
|
||||
// 组件属性定义
|
||||
const props = defineProps({
|
||||
// 透传样式
|
||||
pt: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
// 字段标签
|
||||
label: {
|
||||
type: String,
|
||||
default: ""
|
||||
},
|
||||
// 字段名称
|
||||
prop: {
|
||||
type: String,
|
||||
default: ""
|
||||
},
|
||||
// 标签位置
|
||||
labelPosition: {
|
||||
type: String as PropType<ClFormLabelPosition>,
|
||||
default: null
|
||||
},
|
||||
// 标签宽度
|
||||
labelWidth: {
|
||||
type: String as PropType<string | null>,
|
||||
default: null
|
||||
},
|
||||
// 是否显示必填星号
|
||||
showAsterisk: {
|
||||
type: Boolean as PropType<boolean | null>,
|
||||
default: null
|
||||
},
|
||||
// 是否显示错误信息
|
||||
showMessage: {
|
||||
type: Boolean as PropType<boolean | null>,
|
||||
default: null
|
||||
},
|
||||
// 是否必填
|
||||
required: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
});
|
||||
|
||||
// cl-form 上下文
|
||||
const { formRef, getError, getValue, validateField, addField, removeField } = useForm();
|
||||
|
||||
// 透传样式类型
|
||||
type PassThrough = {
|
||||
className?: string;
|
||||
inner?: PassThroughProps;
|
||||
label?: PassThroughProps;
|
||||
content?: PassThroughProps;
|
||||
error?: PassThroughProps;
|
||||
};
|
||||
|
||||
// 解析透传样式
|
||||
const pt = computed(() => parsePt<PassThrough>(props.pt));
|
||||
|
||||
// 当前错误信息
|
||||
const errorText = computed<string>(() => {
|
||||
return getError(props.prop);
|
||||
});
|
||||
|
||||
// 是否有错误
|
||||
const hasError = computed<boolean>(() => {
|
||||
return errorText.value != "";
|
||||
});
|
||||
|
||||
// 当前标签位置
|
||||
const labelPosition = computed<ClFormLabelPosition>(() => {
|
||||
return props.labelPosition ?? formRef.value?.labelPosition ?? "left";
|
||||
});
|
||||
|
||||
// 标签宽度
|
||||
const labelWidth = computed<string>(() => {
|
||||
return props.labelWidth ?? formRef.value?.labelWidth ?? "120rpx";
|
||||
});
|
||||
|
||||
// 是否显示必填星号
|
||||
const showAsterisk = computed<boolean>(() => {
|
||||
if (!props.required) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return props.showAsterisk ?? formRef.value?.showAsterisk ?? true;
|
||||
});
|
||||
|
||||
// 是否显示错误信息
|
||||
const showMessage = computed<boolean>(() => {
|
||||
if (!props.required) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return props.showMessage ?? formRef.value?.showMessage ?? true;
|
||||
});
|
||||
|
||||
watch(
|
||||
computed(() => props.required),
|
||||
(val: boolean) => {
|
||||
if (val) {
|
||||
addField(props.prop);
|
||||
} else {
|
||||
removeField(props.prop);
|
||||
}
|
||||
},
|
||||
{
|
||||
immediate: true
|
||||
}
|
||||
);
|
||||
|
||||
onMounted(() => {
|
||||
watch(
|
||||
computed(() => getValue(props.prop)!),
|
||||
() => {
|
||||
if (props.required) {
|
||||
validateField(props.prop);
|
||||
}
|
||||
},
|
||||
{
|
||||
deep: true
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
removeField(props.prop);
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.cl-form-item {
|
||||
@apply w-full mb-6;
|
||||
|
||||
&__inner {
|
||||
@apply w-full;
|
||||
|
||||
&.is-top {
|
||||
@apply flex flex-col;
|
||||
}
|
||||
|
||||
&.is-left {
|
||||
@apply flex flex-row;
|
||||
}
|
||||
|
||||
&.is-right {
|
||||
@apply flex flex-row;
|
||||
}
|
||||
}
|
||||
|
||||
&__label {
|
||||
@apply flex flex-row items-center;
|
||||
|
||||
&.is-top {
|
||||
@apply w-full mb-2;
|
||||
}
|
||||
|
||||
&.is-left {
|
||||
@apply mr-3;
|
||||
}
|
||||
|
||||
&.is-right {
|
||||
@apply mr-3 justify-end;
|
||||
}
|
||||
}
|
||||
|
||||
&__content {
|
||||
@apply relative flex-1 w-full;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
16
uni_modules/cool-ui/components/cl-form-item/props.ts
Normal file
16
uni_modules/cool-ui/components/cl-form-item/props.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import type { ClFormItemPassThrough } from "./props";
|
||||
import type { ClFormRule } from "../cl-form/props";
|
||||
|
||||
export type ClFormItemProps = {
|
||||
className?: string;
|
||||
pt?: ClFormItemPassThrough;
|
||||
label?: string;
|
||||
prop?: string;
|
||||
required?: boolean;
|
||||
labelPosition?: "left" | "top" | "right";
|
||||
labelWidth?: string;
|
||||
rules?: ClFormRule | ClFormRule[];
|
||||
showRequiredAsterisk?: boolean;
|
||||
error?: string;
|
||||
disabled?: boolean;
|
||||
};
|
||||
302
uni_modules/cool-ui/components/cl-form/cl-form.uvue
Normal file
302
uni_modules/cool-ui/components/cl-form/cl-form.uvue
Normal file
@@ -0,0 +1,302 @@
|
||||
<template>
|
||||
<view
|
||||
class="cl-form"
|
||||
:class="[
|
||||
`cl-form--label-${labelPosition}`,
|
||||
{
|
||||
'cl-form--disabled': disabled
|
||||
},
|
||||
pt.className
|
||||
]"
|
||||
>
|
||||
<slot></slot>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, nextTick, ref, watch, type PropType } from "vue";
|
||||
import { isEmpty, parsePt, parseToObject } from "@/cool";
|
||||
import type { ClFormLabelPosition, ClFormRule, ClFormValidateError } from "../../types";
|
||||
import { $t, t } from "@/locale";
|
||||
|
||||
defineOptions({
|
||||
name: "cl-form"
|
||||
});
|
||||
|
||||
// 组件属性定义
|
||||
const props = defineProps({
|
||||
// 透传样式
|
||||
pt: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
// 表单数据模型
|
||||
modelValue: {
|
||||
type: Object as PropType<any>,
|
||||
default: () => ({})
|
||||
},
|
||||
// 表单规则
|
||||
rules: {
|
||||
type: Object as PropType<Map<string, ClFormRule[]>>,
|
||||
default: () => new Map<string, ClFormRule[]>()
|
||||
},
|
||||
// 标签位置
|
||||
labelPosition: {
|
||||
type: String as PropType<ClFormLabelPosition>,
|
||||
default: "top"
|
||||
},
|
||||
// 标签宽度
|
||||
labelWidth: {
|
||||
type: String,
|
||||
default: "140rpx"
|
||||
},
|
||||
// 是否显示必填星号
|
||||
showAsterisk: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 是否显示错误信息
|
||||
showMessage: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 是否禁用整个表单
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
});
|
||||
|
||||
type PassThrough = {
|
||||
className?: string;
|
||||
};
|
||||
|
||||
// 解析透传样式
|
||||
const pt = computed(() => parsePt<PassThrough>(props.pt));
|
||||
|
||||
// 表单数据
|
||||
const data = ref({} as UTSJSONObject);
|
||||
|
||||
// 表单字段错误信息
|
||||
const errors = ref(new Map<string, string>());
|
||||
|
||||
// 表单字段集合
|
||||
const fields = ref(new Set<string>([]));
|
||||
|
||||
// 标签位置
|
||||
const labelPosition = computed(() => props.labelPosition);
|
||||
|
||||
// 标签宽度
|
||||
const labelWidth = computed(() => props.labelWidth);
|
||||
|
||||
// 是否显示必填星号
|
||||
const showAsterisk = computed(() => props.showAsterisk);
|
||||
|
||||
// 是否显示错误信息
|
||||
const showMessage = computed(() => props.showMessage);
|
||||
|
||||
// 是否禁用整个表单
|
||||
const disabled = computed(() => props.disabled);
|
||||
|
||||
// 设置字段错误信息
|
||||
function setError(prop: string, error: string) {
|
||||
if (prop != "") {
|
||||
errors.value.set(prop, error);
|
||||
}
|
||||
}
|
||||
|
||||
// 移除字段错误信息
|
||||
function removeError(prop: string) {
|
||||
if (prop != "") {
|
||||
errors.value.delete(prop);
|
||||
}
|
||||
}
|
||||
|
||||
// 获取字段错误信息
|
||||
function getError(prop: string): string {
|
||||
if (prop != "") {
|
||||
return errors.value.get(prop) ?? "";
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
// 清除所有错误信息
|
||||
function clearErrors() {
|
||||
errors.value.clear();
|
||||
}
|
||||
|
||||
// 获取字段值
|
||||
function getValue(prop: string): any | null {
|
||||
if (prop != "") {
|
||||
return data.value[prop];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// 注册表单字段
|
||||
function addField(prop: string) {
|
||||
if (prop != "") {
|
||||
fields.value.add(prop);
|
||||
}
|
||||
}
|
||||
|
||||
// 注销表单字段
|
||||
function removeField(prop: string) {
|
||||
if (prop != "") {
|
||||
fields.value.delete(prop);
|
||||
removeError(prop);
|
||||
}
|
||||
}
|
||||
|
||||
// 获取字段规则
|
||||
function getRule(prop: string): ClFormRule[] {
|
||||
return props.rules.get(prop) ?? ([] as ClFormRule[]);
|
||||
}
|
||||
|
||||
// 验证单个规则
|
||||
function validateRule(value: any | null, rule: ClFormRule): null | string {
|
||||
// 必填验证
|
||||
if (rule.required == true) {
|
||||
if (value == null || value == "" || (Array.isArray(value) && value.length == 0)) {
|
||||
return rule.message ?? t("此字段为必填项");
|
||||
}
|
||||
}
|
||||
|
||||
// 如果值为空且不是必填,直接通过
|
||||
if ((value == null || value == "") && rule.required != true) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 最小长度验证
|
||||
if (rule.min != null) {
|
||||
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) {
|
||||
const len = Array.isArray(value) ? value.length : `${value}`.length;
|
||||
if (len > rule.max) {
|
||||
return rule.message ?? $t(`最多允许{max}个字符`, { max: rule.max });
|
||||
}
|
||||
}
|
||||
|
||||
// 正则验证
|
||||
if (rule.pattern != null) {
|
||||
if (!rule.pattern.test(`${value}`)) {
|
||||
return rule.message ?? t("格式不正确");
|
||||
}
|
||||
}
|
||||
|
||||
// 自定义验证
|
||||
if (rule.validator != null) {
|
||||
const result = rule.validator(value);
|
||||
if (result != true) {
|
||||
return typeof result == "string" ? result : (rule.message ?? t("验证失败"));
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// 清除所有验证
|
||||
function clearValidate() {
|
||||
nextTick(() => {
|
||||
clearErrors();
|
||||
});
|
||||
}
|
||||
|
||||
// 验证单个字段
|
||||
function validateField(prop: string): string | null {
|
||||
let error = null as string | null;
|
||||
|
||||
if (prop != "") {
|
||||
const value = getValue(prop);
|
||||
const rules = getRule(prop);
|
||||
|
||||
if (!isEmpty(rules)) {
|
||||
// 逐个验证规则
|
||||
rules.find((rule) => {
|
||||
const msg = validateRule(value, rule);
|
||||
|
||||
if (msg != null) {
|
||||
error = msg;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
removeError(prop);
|
||||
}
|
||||
|
||||
if (error != null) {
|
||||
setError(prop, error!);
|
||||
}
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
// 验证整个表单
|
||||
function validate(callback: (valid: boolean, errors: ClFormValidateError[]) => void) {
|
||||
const errs = [] as ClFormValidateError[];
|
||||
|
||||
fields.value.forEach((prop) => {
|
||||
const result = validateField(prop);
|
||||
|
||||
if (result != null) {
|
||||
errs.push({
|
||||
field: prop,
|
||||
message: result
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
callback(errs.length == 0, errs);
|
||||
}
|
||||
|
||||
watch(
|
||||
computed(() => parseToObject(props.modelValue)),
|
||||
(val: UTSJSONObject) => {
|
||||
data.value = val;
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
deep: true
|
||||
}
|
||||
);
|
||||
|
||||
defineExpose({
|
||||
labelPosition,
|
||||
labelWidth,
|
||||
showAsterisk,
|
||||
showMessage,
|
||||
disabled,
|
||||
data,
|
||||
errors,
|
||||
fields,
|
||||
addField,
|
||||
removeField,
|
||||
getValue,
|
||||
setError,
|
||||
getError,
|
||||
removeError,
|
||||
clearErrors,
|
||||
getRule,
|
||||
validateRule,
|
||||
clearValidate,
|
||||
validateField,
|
||||
validate
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.cl-form {
|
||||
@apply w-full;
|
||||
}
|
||||
</style>
|
||||
12
uni_modules/cool-ui/components/cl-form/props.ts
Normal file
12
uni_modules/cool-ui/components/cl-form/props.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import type { ClFormData, ClFormRule, ClFormValidateResult, ClFormPassThrough } from "./props";
|
||||
|
||||
export type ClFormProps = {
|
||||
className?: string;
|
||||
pt?: ClFormPassThrough;
|
||||
modelValue?: ClFormData;
|
||||
rules?: Record<string, ClFormRule | ClFormRule[]>;
|
||||
labelPosition?: "left" | "top" | "right";
|
||||
labelWidth?: string;
|
||||
showRequiredAsterisk?: boolean;
|
||||
disabled?: boolean;
|
||||
};
|
||||
@@ -3,7 +3,7 @@
|
||||
class="cl-input-number"
|
||||
:class="[
|
||||
{
|
||||
'cl-input-number--disabled': disabled
|
||||
'cl-input-number--disabled': isDisabled
|
||||
},
|
||||
pt.className
|
||||
]"
|
||||
@@ -41,7 +41,7 @@
|
||||
<cl-input
|
||||
:model-value="`${value}`"
|
||||
:type="inputType"
|
||||
:disabled="disabled"
|
||||
:disabled="isDisabled"
|
||||
:clearable="false"
|
||||
:readonly="inputable == false"
|
||||
:placeholder="placeholder"
|
||||
@@ -92,6 +92,7 @@ import { computed, nextTick, ref, watch, type PropType } from "vue";
|
||||
import type { PassThroughProps } from "../../types";
|
||||
import type { ClIconProps } from "../cl-icon/props";
|
||||
import { useLongPress, parsePt, parseRpx } from "@/cool";
|
||||
import { useForm } from "../../hooks";
|
||||
|
||||
defineOptions({
|
||||
name: "cl-input-number"
|
||||
@@ -156,6 +157,14 @@ const emit = defineEmits(["update:modelValue", "change"]);
|
||||
// 长按操作
|
||||
const longPress = useLongPress();
|
||||
|
||||
// cl-form 上下文
|
||||
const { disabled } = useForm();
|
||||
|
||||
// 是否禁用
|
||||
const isDisabled = computed(() => {
|
||||
return disabled.value || props.disabled;
|
||||
});
|
||||
|
||||
// 数值样式
|
||||
type ValuePassThrough = {
|
||||
className?: string;
|
||||
@@ -184,10 +193,10 @@ const pt = computed(() => parsePt<PassThrough>(props.pt));
|
||||
const value = ref(props.modelValue);
|
||||
|
||||
// 是否可以继续增加数值
|
||||
const isPlus = computed(() => !props.disabled && value.value < props.max);
|
||||
const isPlus = computed(() => !isDisabled.value && value.value < props.max);
|
||||
|
||||
// 是否可以继续减少数值
|
||||
const isMinus = computed(() => !props.disabled && value.value > props.min);
|
||||
const isMinus = computed(() => !isDisabled.value && value.value > props.min);
|
||||
|
||||
/**
|
||||
* 更新数值并触发事件
|
||||
@@ -232,7 +241,7 @@ function update() {
|
||||
* 在非禁用状态下增加step值
|
||||
*/
|
||||
function onPlus() {
|
||||
if (props.disabled || !isPlus.value) return;
|
||||
if (isDisabled.value || !isPlus.value) return;
|
||||
|
||||
longPress.start(() => {
|
||||
if (isPlus.value) {
|
||||
@@ -248,7 +257,7 @@ function onPlus() {
|
||||
* 在非禁用状态下减少step值
|
||||
*/
|
||||
function onMinus() {
|
||||
if (props.disabled || !isMinus.value) return;
|
||||
if (isDisabled.value || !isMinus.value) return;
|
||||
|
||||
longPress.start(() => {
|
||||
if (isMinus.value) {
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
'is-dark': isDark,
|
||||
'cl-input--border': border,
|
||||
'cl-input--focus': isFocus,
|
||||
'cl-input--disabled': disabled
|
||||
'cl-input--disabled': isDisabled
|
||||
}
|
||||
]"
|
||||
@tap="onTap"
|
||||
@@ -26,13 +26,13 @@
|
||||
class="cl-input__inner"
|
||||
:class="[
|
||||
{
|
||||
'is-disabled': disabled,
|
||||
'is-disabled': isDisabled,
|
||||
'is-dark': isDark
|
||||
},
|
||||
pt.inner?.className
|
||||
]"
|
||||
:value="value"
|
||||
:disabled="readonly ?? disabled"
|
||||
:disabled="readonly ?? isDisabled"
|
||||
:type="type"
|
||||
:password="isPassword"
|
||||
:focus="isFocus"
|
||||
@@ -80,11 +80,12 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, nextTick, ref, watch, type PropType } from "vue";
|
||||
import { computed, nextTick, onMounted, ref, watch, type PropType } from "vue";
|
||||
import type { ClInputType, PassThroughProps } from "../../types";
|
||||
import { isDark, parseClass, parsePt } from "@/cool";
|
||||
import type { ClIconProps } from "../cl-icon/props";
|
||||
import { t } from "@/locale";
|
||||
import { useForm } from "../../hooks";
|
||||
|
||||
defineOptions({
|
||||
name: "cl-input"
|
||||
@@ -201,6 +202,14 @@ const emit = defineEmits([
|
||||
"keyboardheightchange"
|
||||
]);
|
||||
|
||||
// cl-form 上下文
|
||||
const { disabled } = useForm();
|
||||
|
||||
// 是否禁用
|
||||
const isDisabled = computed(() => {
|
||||
return disabled.value || props.disabled;
|
||||
});
|
||||
|
||||
// 透传样式类型定义
|
||||
type PassThrough = {
|
||||
className?: string;
|
||||
@@ -271,7 +280,7 @@ function onKeyboardheightchange(e: UniInputKeyboardHeightChangeEvent) {
|
||||
|
||||
// 点击事件
|
||||
function onTap() {
|
||||
if (props.disabled) {
|
||||
if (isDisabled.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
:focus="popupRef?.isOpen"
|
||||
:text="text"
|
||||
arrow-icon="calendar-line"
|
||||
@tap="open()"
|
||||
@open="open()"
|
||||
@clear="clear"
|
||||
></cl-select-trigger>
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
:focus="popupRef?.isOpen"
|
||||
:text="text"
|
||||
arrow-icon="time-line"
|
||||
@tap="open()"
|
||||
@open="open()"
|
||||
@clear="clear"
|
||||
></cl-select-trigger>
|
||||
|
||||
|
||||
@@ -4,11 +4,12 @@
|
||||
:class="[
|
||||
{
|
||||
'is-dark': isDark,
|
||||
'cl-select-trigger--disabled': disabled,
|
||||
'cl-select-trigger--disabled': isDisabled,
|
||||
'cl-select-trigger--focus': focus
|
||||
},
|
||||
pt.className
|
||||
]"
|
||||
@tap="open"
|
||||
>
|
||||
<view class="cl-select-trigger__content">
|
||||
<cl-text
|
||||
@@ -16,7 +17,7 @@
|
||||
:pt="{
|
||||
className: parseClass([
|
||||
{
|
||||
'!text-surface-400': disabled
|
||||
'!text-surface-400': isDisabled
|
||||
},
|
||||
pt.text?.className
|
||||
])
|
||||
@@ -35,7 +36,7 @@
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<view v-if="showText && !disabled" class="cl-select-trigger__icon" @tap.stop="clear">
|
||||
<view v-if="showText && !isDisabled" class="cl-select-trigger__icon" @tap.stop="clear">
|
||||
<cl-icon
|
||||
name="close-circle-fill"
|
||||
:size="32"
|
||||
@@ -43,7 +44,7 @@
|
||||
></cl-icon>
|
||||
</view>
|
||||
|
||||
<view v-if="!disabled && !showText" class="cl-select-trigger__icon">
|
||||
<view v-if="!isDisabled && !showText" class="cl-select-trigger__icon">
|
||||
<cl-icon
|
||||
:name="pt.icon?.name ?? arrowIcon"
|
||||
:size="pt.icon?.size ?? 32"
|
||||
@@ -61,6 +62,7 @@ import type { ClIconProps } from "../cl-icon/props";
|
||||
import { isDark, parseClass, parsePt } from "@/cool";
|
||||
import { t } from "@/locale";
|
||||
import type { PassThroughProps } from "../../types";
|
||||
import { useForm } from "../../hooks";
|
||||
|
||||
defineOptions({
|
||||
name: "cl-select-trigger"
|
||||
@@ -100,7 +102,14 @@ const props = defineProps({
|
||||
}
|
||||
});
|
||||
|
||||
const emit = defineEmits(["clear"]);
|
||||
const emit = defineEmits(["open", "clear"]);
|
||||
|
||||
const { disabled } = useForm();
|
||||
|
||||
// 是否禁用
|
||||
const isDisabled = computed(() => {
|
||||
return disabled.value || props.disabled;
|
||||
});
|
||||
|
||||
// 透传样式类型定义
|
||||
type PassThrough = {
|
||||
@@ -120,6 +129,15 @@ const showText = computed(() => props.text != "");
|
||||
function clear() {
|
||||
emit("clear");
|
||||
}
|
||||
|
||||
// 打开选择器
|
||||
function open() {
|
||||
if (isDisabled.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
emit("open");
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
:disabled="disabled"
|
||||
:focus="popupRef?.isOpen"
|
||||
:text="text"
|
||||
@tap="open()"
|
||||
@open="open()"
|
||||
@clear="clear"
|
||||
></cl-select-trigger>
|
||||
|
||||
@@ -68,6 +68,7 @@ import { isEmpty, parsePt } from "@/cool";
|
||||
import type { ClSelectTriggerPassThrough } from "../cl-select-trigger/props";
|
||||
import type { ClPopupPassThrough } from "../cl-popup/props";
|
||||
import { t } from "@/locale";
|
||||
import { useForm } from "../../hooks";
|
||||
|
||||
defineOptions({
|
||||
name: "cl-select"
|
||||
@@ -268,10 +269,6 @@ let callback: ((value: Value) => void) | null = null;
|
||||
|
||||
// 打开选择器
|
||||
function open(cb: ((value: Value) => void) | null = null) {
|
||||
if (props.disabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
visible.value = true;
|
||||
callback = cb;
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
'is-dark': isDark,
|
||||
'cl-textarea--border': border,
|
||||
'cl-textarea--focus': isFocus,
|
||||
'cl-textarea--disabled': disabled
|
||||
'cl-textarea--disabled': isDisabled
|
||||
}
|
||||
]"
|
||||
@tap="onTap"
|
||||
@@ -16,7 +16,7 @@
|
||||
class="cl-textarea__inner"
|
||||
:class="[
|
||||
{
|
||||
'is-disabled': disabled,
|
||||
'is-disabled': isDisabled,
|
||||
'is-dark': isDark
|
||||
},
|
||||
pt.inner?.className
|
||||
@@ -26,7 +26,7 @@
|
||||
}"
|
||||
:value="value"
|
||||
:name="name"
|
||||
:disabled="readonly ?? disabled"
|
||||
:disabled="readonly ?? isDisabled"
|
||||
:placeholder="placeholder"
|
||||
:placeholder-class="`text-surface-400 ${placeholderClass}`"
|
||||
:maxlength="maxlength"
|
||||
@@ -63,6 +63,7 @@ import { parsePt, parseRpx } from "@/cool";
|
||||
import type { PassThroughProps } from "../../types";
|
||||
import { isDark } from "@/cool";
|
||||
import { t } from "@/locale";
|
||||
import { useForm } from "../../hooks";
|
||||
|
||||
defineOptions({
|
||||
name: "cl-textarea"
|
||||
@@ -221,6 +222,14 @@ const emit = defineEmits([
|
||||
"keyboardheightchange"
|
||||
]);
|
||||
|
||||
// cl-form 上下文
|
||||
const { disabled } = useForm();
|
||||
|
||||
// 是否禁用
|
||||
const isDisabled = computed(() => {
|
||||
return disabled.value || props.disabled;
|
||||
});
|
||||
|
||||
// 透传样式类型定义
|
||||
type PassThrough = {
|
||||
className?: string;
|
||||
@@ -277,7 +286,7 @@ function onLineChange(e: UniTextareaLineChangeEvent) {
|
||||
|
||||
// 点击事件
|
||||
function onTap() {
|
||||
if (props.disabled) {
|
||||
if (isDisabled.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
:class="[
|
||||
{
|
||||
'is-dark': isDark,
|
||||
'is-disabled': disabled
|
||||
'is-disabled': isDisabled
|
||||
},
|
||||
pt.item?.className
|
||||
]"
|
||||
@@ -33,7 +33,7 @@
|
||||
className: 'cl-upload__close'
|
||||
}"
|
||||
@tap.stop="remove(item.uid)"
|
||||
v-if="!disabled"
|
||||
v-if="!isDisabled"
|
||||
></cl-icon>
|
||||
|
||||
<view class="cl-upload__progress" v-if="item.progress < 100">
|
||||
@@ -47,7 +47,7 @@
|
||||
:class="[
|
||||
{
|
||||
'is-dark': isDark,
|
||||
'is-disabled': disabled
|
||||
'is-disabled': isDisabled
|
||||
},
|
||||
pt.add?.className
|
||||
]"
|
||||
@@ -84,6 +84,7 @@ import { forInObject, isDark, parseClass, parsePt, parseRpx, uploadFile, uuid }
|
||||
import { t } from "@/locale";
|
||||
import { computed, reactive, ref, watch, type PropType } from "vue";
|
||||
import type { ClUploadItem, PassThroughProps } from "../../types";
|
||||
import { useForm } from "../../hooks";
|
||||
|
||||
defineOptions({
|
||||
name: "cl-upload"
|
||||
@@ -156,6 +157,14 @@ const emit = defineEmits([
|
||||
"progress" // 上传进度更新时触发
|
||||
]);
|
||||
|
||||
// cl-form 上下文
|
||||
const { disabled } = useForm();
|
||||
|
||||
// 是否禁用
|
||||
const isDisabled = computed(() => {
|
||||
return disabled.value || props.disabled;
|
||||
});
|
||||
|
||||
// 透传属性的类型定义
|
||||
type PassThrough = {
|
||||
className?: string;
|
||||
@@ -179,7 +188,7 @@ const activeIndex = ref(0);
|
||||
const isAdd = computed(() => {
|
||||
const n = list.value.length;
|
||||
|
||||
if (props.disabled) {
|
||||
if (isDisabled.value) {
|
||||
// 禁用状态下,只有没有文件时才显示添加按钮
|
||||
return n == 0;
|
||||
} else {
|
||||
@@ -294,7 +303,7 @@ function remove(uid: string) {
|
||||
* @param {number} index - 操作的索引,-1表示新增,其他表示替换
|
||||
*/
|
||||
function choose(index: number) {
|
||||
if (props.disabled) {
|
||||
if (isDisabled.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
86
uni_modules/cool-ui/hooks/form.ts
Normal file
86
uni_modules/cool-ui/hooks/form.ts
Normal file
@@ -0,0 +1,86 @@
|
||||
import { computed, ref, type ComputedRef } from "vue";
|
||||
import type { ClFormRule, ClFormValidateError } from "../types";
|
||||
import { useParent } from "@/cool";
|
||||
|
||||
class UseForm {
|
||||
public formRef = ref<ClFormComponentPublicInstance | null>(null);
|
||||
public disabled: ComputedRef<boolean>;
|
||||
|
||||
constructor() {
|
||||
// 获取 cl-form 实例
|
||||
if (this.formRef.value == null) {
|
||||
const ClForm = useParent<ClFormComponentPublicInstance>("cl-form");
|
||||
|
||||
if (ClForm != null) {
|
||||
this.formRef.value = ClForm;
|
||||
}
|
||||
}
|
||||
|
||||
// 监听表单是否禁用
|
||||
this.disabled = computed<boolean>(() => this.formRef.value?.disabled ?? false);
|
||||
}
|
||||
|
||||
// 注册表单字段
|
||||
addField = (prop: string): void => {
|
||||
this.formRef.value!.addField(prop);
|
||||
};
|
||||
|
||||
// 注销表单字段
|
||||
removeField = (prop: string): void => {
|
||||
this.formRef.value!.removeField(prop);
|
||||
};
|
||||
|
||||
// 获取字段值
|
||||
getValue = (prop: string): any | null => {
|
||||
return this.formRef.value!.getValue(prop);
|
||||
};
|
||||
|
||||
// 设置字段错误信息
|
||||
setError = (prop: string, error: string): void => {
|
||||
this.formRef.value!.setError(prop, error);
|
||||
};
|
||||
|
||||
// 获取字段错误信息
|
||||
getError = (prop: string): string => {
|
||||
return this.formRef.value!.getError(prop);
|
||||
};
|
||||
|
||||
// 移除字段错误信息
|
||||
removeError = (prop: string): void => {
|
||||
this.formRef.value!.removeError(prop);
|
||||
};
|
||||
|
||||
// 清除所有错误信息
|
||||
clearErrors = (): void => {
|
||||
this.formRef.value!.clearErrors();
|
||||
};
|
||||
|
||||
// 获取字段规则
|
||||
getRule = (prop: string): ClFormRule[] => {
|
||||
return this.formRef.value!.getRule(prop);
|
||||
};
|
||||
|
||||
// 验证单个规则
|
||||
validateRule = (value: any | null, rule: ClFormRule): string | null => {
|
||||
return this.formRef.value!.validateRule(value, rule);
|
||||
};
|
||||
|
||||
// 清除所有验证
|
||||
clearValidate = (): void => {
|
||||
this.formRef.value!.clearValidate();
|
||||
};
|
||||
|
||||
// 验证单个字段
|
||||
validateField = (prop: string): string | null => {
|
||||
return this.formRef.value!.validateField(prop);
|
||||
};
|
||||
|
||||
// 验证整个表单
|
||||
validate = (callback: (valid: boolean, errors: ClFormValidateError[]) => void): void => {
|
||||
this.formRef.value!.validate(callback);
|
||||
};
|
||||
}
|
||||
|
||||
export function useForm() {
|
||||
return new UseForm();
|
||||
}
|
||||
@@ -1,2 +1,3 @@
|
||||
export * from "./ui";
|
||||
export * from "./component";
|
||||
export * from "./form";
|
||||
|
||||
4
uni_modules/cool-ui/index.d.ts
vendored
4
uni_modules/cool-ui/index.d.ts
vendored
@@ -16,6 +16,8 @@ import type { ClCropperProps, ClCropperPassThrough } from "./components/cl-cropp
|
||||
import type { ClDraggableProps, ClDraggablePassThrough } from "./components/cl-draggable/props";
|
||||
import type { ClFloatViewProps } from "./components/cl-float-view/props";
|
||||
import type { ClFooterProps, ClFooterPassThrough } from "./components/cl-footer/props";
|
||||
import type { ClFormProps } from "./components/cl-form/props";
|
||||
import type { ClFormItemProps } from "./components/cl-form-item/props";
|
||||
import type { ClIconProps, ClIconPassThrough } from "./components/cl-icon/props";
|
||||
import type { ClImageProps, ClImagePassThrough } from "./components/cl-image/props";
|
||||
import type { ClIndexBarProps, ClIndexBarPassThrough } from "./components/cl-index-bar/props";
|
||||
@@ -84,6 +86,8 @@ declare module "vue" {
|
||||
"cl-draggable": (typeof import('./components/cl-draggable/cl-draggable.uvue')['default']) & import('vue').DefineComponent<ClDraggableProps>;
|
||||
"cl-float-view": (typeof import('./components/cl-float-view/cl-float-view.uvue')['default']) & import('vue').DefineComponent<ClFloatViewProps>;
|
||||
"cl-footer": (typeof import('./components/cl-footer/cl-footer.uvue')['default']) & import('vue').DefineComponent<ClFooterProps>;
|
||||
"cl-form": (typeof import('./components/cl-form/cl-form.uvue')['default']) & import('vue').DefineComponent<ClFormProps>;
|
||||
"cl-form-item": (typeof import('./components/cl-form-item/cl-form-item.uvue')['default']) & import('vue').DefineComponent<ClFormItemProps>;
|
||||
"cl-icon": (typeof import('./components/cl-icon/cl-icon.uvue')['default']) & import('vue').DefineComponent<ClIconProps>;
|
||||
"cl-image": (typeof import('./components/cl-image/cl-image.uvue')['default']) & import('vue').DefineComponent<ClImageProps>;
|
||||
"cl-index-bar": (typeof import('./components/cl-index-bar/cl-index-bar.uvue')['default']) & import('vue').DefineComponent<ClIndexBarProps>;
|
||||
|
||||
31
uni_modules/cool-ui/types/component.d.ts
vendored
31
uni_modules/cool-ui/types/component.d.ts
vendored
@@ -159,3 +159,34 @@ declare type ClCropperComponentPublicInstance = {
|
||||
chooseImage: () => void;
|
||||
toPng: () => Promise<string>;
|
||||
};
|
||||
|
||||
declare type ClFormComponentPublicInstance = {
|
||||
labelPosition: "left" | "top" | "right";
|
||||
labelWidth: string;
|
||||
showAsterisk: boolean;
|
||||
showMessage: boolean;
|
||||
disabled: boolean;
|
||||
data: UTSJSONObject;
|
||||
errors: Map<string, string>;
|
||||
fields: Set<string>;
|
||||
addField: (prop: string) => void;
|
||||
removeField: (prop: string) => void;
|
||||
getValue: (prop: string) => any | null;
|
||||
setData: (data: UTSJSONObject) => void;
|
||||
setError: (prop: string, error: string) => void;
|
||||
getError: (prop: string) => string;
|
||||
removeError: (prop: string) => void;
|
||||
clearErrors: () => void;
|
||||
getRule: (prop: string) => ClFormRule[];
|
||||
validateRule: (value: any | null, rule: ClFormRule) => string | null;
|
||||
clearValidate: () => void;
|
||||
validateField: (prop: string) => string | null;
|
||||
validate: (callback: (valid: boolean, errors: ClFormValidateError[]) => void) => void;
|
||||
};
|
||||
|
||||
declare type ClFormItemComponentPublicInstance = {
|
||||
validate: () => Promise<boolean>;
|
||||
clearValidate: () => void;
|
||||
hasError: boolean;
|
||||
currentError: string;
|
||||
};
|
||||
|
||||
@@ -128,3 +128,31 @@ export type ClSelectDateShortcut = {
|
||||
label: string;
|
||||
value: string[];
|
||||
};
|
||||
|
||||
// 表单规则类型
|
||||
export type ClFormRule = {
|
||||
// 是否必填
|
||||
required?: boolean;
|
||||
// 错误信息
|
||||
message?: string;
|
||||
// 最小长度
|
||||
min?: number;
|
||||
// 最大长度
|
||||
max?: number;
|
||||
// 正则验证
|
||||
pattern?: RegExp;
|
||||
// 自定义验证函数
|
||||
validator?: (value: any | null) => boolean | string;
|
||||
};
|
||||
|
||||
export type ClFormValidateError = {
|
||||
field: string;
|
||||
message: string;
|
||||
};
|
||||
|
||||
export type ClFormValidateResult = {
|
||||
valid: boolean;
|
||||
errors: ClFormValidateError[];
|
||||
};
|
||||
|
||||
export type ClFormLabelPosition = "left" | "top" | "right";
|
||||
|
||||
Reference in New Issue
Block a user