| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269 |
- <template>
- <view
- class="cl-form-item"
- :class="[
- {
- 'cl-form-item--error': isError
- },
- pt.className
- ]"
- :id="`cl-form-item-${prop}`"
- >
- <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>
- <view class="cl-form-item__error" v-if="isError && showMessage">
- <slot name="error" :error="errorText">
- <cl-text
- color="error"
- :pt="{
- className: parseClass(['mt-2 !text-sm', pt.error?.className])
- }"
- >
- {{ errorText }}
- </cl-text>
- </slot>
- </view>
- </view>
- </template>
- <script setup lang="ts">
- import { computed, onMounted, onUnmounted, watch, type PropType } from "vue";
- import { isEqual, parseClass, parsePt } from "@/cool";
- import type { ClFormLabelPosition, ClFormRule, PassThroughProps } from "../../types";
- import { useForm } from "../../hooks";
- defineOptions({
- name: "cl-form-item"
- });
- defineSlots<{
- error(props: { error: string }): any;
- }>();
- // 组件属性定义
- const props = defineProps({
- // 透传样式
- pt: {
- type: Object,
- default: () => ({})
- },
- // 字段标签
- label: {
- type: String,
- default: ""
- },
- // 字段名称
- prop: {
- type: String,
- default: ""
- },
- // 字段验证规则
- rules: {
- type: Array as PropType<ClFormRule[]>,
- 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, setRule } = 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 isError = 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, props.rules);
- } else {
- removeField(props.prop);
- }
- },
- {
- immediate: true
- }
- );
- onMounted(() => {
- // 监听字段值变化
- watch(
- computed(() => {
- const value = getValue(props.prop);
- if (value == null) {
- return "";
- }
- return value;
- }),
- (a: any, b: any) => {
- if (props.required) {
- if (!isEqual(a, b)) {
- validateField(props.prop);
- }
- }
- },
- {
- deep: true
- }
- );
- // 监听规则变化
- watch(
- computed(() => props.rules),
- (val: ClFormRule[]) => {
- setRule(props.prop, val);
- },
- {
- deep: true
- }
- );
- });
- onUnmounted(() => {
- removeField(props.prop);
- });
- defineExpose({
- prop: 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>
|