Jelajahi Sumber

Update 优化表单组件

Yue 2 hari lalu
induk
melakukan
c5109d28ad

+ 29 - 26
UI/VAP_V3.VUE/src/components/form/VbForm.vue

@@ -2,6 +2,7 @@
 import type { FormItemRule } from "element-plus/es/components/form/src/types"
 import type { VbFormItem as VbFormColItem, VbFormRowItem } from "@@@/form/models"
 import VbFormItem from "@@@/form/VbFormItem.vue"
+
 const props = withDefaults(
 	defineProps<{
 		data: object
@@ -24,10 +25,10 @@ const props = withDefaults(
 		size: "large"
 	}
 )
-// const emits = defineEmits<{ (e: "update:data", v: any): void }>()
+
 const { data } = toRefs(props)
 
-// 表单
+// 表单引用
 const VbForm = ref<HTMLFormElement>()
 
 // 重置表单
@@ -36,16 +37,17 @@ function resetFields() {
 		VbForm.value?.resetFields()
 	})
 }
+
 // 清除表单校验
-const clearValidate = () => {
+function clearValidate() {
 	nextTick(() => {
 		VbForm.value?.clearValidate()
 	})
 }
+
 // 验证表单,返回 Promise<boolean>
-async function validate() {
-	const valid = await VbForm.value?.validate()
-	return valid
+async function validate(): Promise<boolean | undefined> {
+	return await VbForm.value?.validate()
 }
 
 defineExpose({ resetFields, clearValidate, validate, VbForm })
@@ -57,32 +59,33 @@ defineExpose({ resetFields, clearValidate, validate, VbForm })
 		:model="data"
 		:class="props.class"
 		:label-width="`${Number(labelWidth) ? labelWidth + 'px' : labelWidth}`">
+		<!-- 使用 rowItems 的布局 -->
 		<template v-if="rowItems && rowItems.length">
-			<el-row v-for="(v, i) in rowItems" :key="i" :gutter="v.gutter || gutter">
-				<template v-if="v.children && v.children.length">
-					<VbFormItem
-						v-for="vv in v.children"
-						:key="vv.field"
-						v-model:data="data"
-						:item="vv"
-						:labelWidth="labelWidth"
-						:rules="rules"
-						:gutter="gutter"
-						:span="span">
-						<template v-for="(_, name) in $slots" #[name]>
-							<slot :name="name" />
-						</template>
-					</VbFormItem>
-				</template>
+			<el-row v-for="(row, rowIndex) in rowItems" :key="rowIndex" :gutter="row.gutter ?? gutter">
+				<VbFormItem
+					v-for="item in row.children"
+					:key="item.field"
+					v-model:data="data"
+					:item="item"
+					:labelWidth="labelWidth"
+					:rules="rules"
+					:gutter="gutter"
+					:span="span">
+					<template v-for="(_, name) in $slots" #[name]>
+						<slot :name="name" />
+					</template>
+				</VbFormItem>
 			</el-row>
 		</template>
-		<template v-if="items && items.length">
+
+		<!-- 使用 items 的布局 -->
+		<template v-else-if="items && items.length">
 			<el-row :gutter="gutter">
 				<VbFormItem
-					v-for="vv in items"
-					:key="vv.field"
+					v-for="item in items"
+					:key="item.field"
 					v-model:data="data"
-					:item="vv"
+					:item="item"
 					:labelWidth="labelWidth"
 					:rules="rules"
 					:gutter="gutter"

+ 213 - 249
UI/VAP_V3.VUE/src/components/form/VbFormItem.vue

@@ -10,212 +10,206 @@ const props = withDefaults(
 		item: VbFormItem
 		labelWidth?: number | string
 		rules?: Record<string, FormItemRule[]>
-		gutter?: number
 		span?: number
 		size?: "large" | "default" | "small"
 	}>(),
 	{
-		gutter: 20,
+		labelWidth: 80,
 		span: 24,
-		size: "large"
+		size: "default"
 	}
 )
 const emits = defineEmits<(e: "update:data", v: any) => void>()
 const { item } = toRefs(props)
-const itemId = computed(() => {
-	return typeof item.value.id == "function" ? item.value.id() : item.value.id
-})
-const itemData = computed(() => {
-	return typeof item.value.data == "function" ? item.value.data() : item.value.data
-})
-const isShow = computed(() => {
-	if (props.item.show == undefined) {
-		return true
-	}
-	if (typeof props.item.show == "function") {
-		return props.item.show(data.value)
-	}
-	return props.item.show
-})
-const isNum = computed(() => {
-	if (item.value.type == "number") {
-		return true
-	}
-	if (item.value.rules && item.value.rules.length > 0) {
-		return (
-			item.value.rules.includes(RULE_KEYS.NUMBER) ||
-			item.value.rules.includes(RULE_KEYS.REQUIRED_NUMBER) ||
-			item.value.rules.includes(RULE_KEYS.N_MAX) ||
-			item.value.rules.includes(RULE_KEYS.N_MIN) ||
-			item.value.rules.includes(RULE_KEYS.N_MIN_MAX)
-		)
-	}
-	return false
-})
+
+// 双向绑定的数据
 const data: WritableComputedRef<any> = computed({
 	get(): any {
-		return props.data
+		return typeof props.data === "function" ? props.data() : props.data
 	},
 	set(value: any): void {
 		emits("update:data", value)
 	}
 })
 
+// 动态解析值
+const resolveDynamicValue = <T,>(
+	value: T | ((v?: any) => T) | undefined,
+	data?: any
+): T | undefined => {
+	if (typeof value === "function") {
+		return (value as Function)(data)
+	}
+	return value
+}
+
+// 计算属性
+const itemId = computed(() => resolveDynamicValue<string | undefined>(item.value.id, undefined))
+const itemData = computed(() => resolveDynamicValue(item.value.data, undefined))
+const colSpan = computed(() => resolveDynamicValue(item.value.span, data.value) || props.span)
+const isShow = computed(() => {
+	if (item.value.show === undefined) return true
+	return resolveDynamicValue(item.value.show, data.value) || true
+})
+const isRequired = computed(() => resolveDynamicValue(item.value.required, data.value) || false)
+const isReadonly = computed(() => resolveDynamicValue(item.value.readonly, data.value) || false)
+const isDisabled = computed(() => resolveDynamicValue(item.value.disabled, data.value) || false)
+const itemType = computed(() => resolveDynamicValue(item.value.type, data.value))
+const itemSize = computed(() => resolveDynamicValue(item.value.size, data.value) || props.size)
+const elItemClass = computed(() => resolveDynamicValue(item.value.itemClass, data.value))
+const itemClass = computed(() => resolveDynamicValue(item.value.class, data.value))
+const elItemStyle = computed(() => resolveDynamicValue(item.value.itemStyle, data.value))
+const itemStyle = computed(() => resolveDynamicValue(item.value.style, data.value))
+const itemLabel = computed(() => resolveDynamicValue(item.value.label, data.value))
+const itemPlaceholder = computed(
+	() => resolveDynamicValue(item.value.placeholder, data.value) || itemLabel.value
+)
+const itemInnerText = computed(() => resolveDynamicValue(item.value.innerText, data.value))
+const tipsContent = computed(() => resolveDynamicValue(item.value.tips, data.value))
+const itemLabelWidth = computed(() => {
+	const _lw = resolveDynamicValue(item.value.labelWidth, data.value) || props.labelWidth
+	if (_lw && !isNaN(Number(_lw))) {
+		return `${_lw}px`
+	}
+	return _lw
+})
+const itemLabelStyle = computed(() => resolveDynamicValue(item.value.labelStyle, data.value))
+
+// 获取验证规则
 const getRules = (item: VbFormItem): FormItemRule[] => {
-	const _rules = [] as FormItemRule[]
+	const _rules: FormItemRule[] = []
+
 	if (item.rules) {
 		for (let i = 0; i < item.rules.length; i++) {
 			const r = item.rules[i]
 			let rule
-			if (typeof r == "string") {
-				rule = props.rules
-					? props.rules[r]
-					: getConfigRule(r, item.ruleFormat ? item.ruleFormat[i] : undefined)
-				if (rule) {
-					Array.isArray(rule) ? _rules.push(...rule) : _rules.push(rule)
-				}
-			} else if (typeof r == "object") {
-				Array.isArray(r) ? _rules.push(...r) : _rules.push(r)
+
+			if (typeof r === "string") {
+				rule = props.rules ? props.rules[r] : getConfigRule(r, item.ruleFormat?.[i])
+			} else if (typeof r === "object") {
+				rule = r
+			}
+
+			if (rule) {
+				Array.isArray(rule) ? _rules.push(...rule) : _rules.push(rule)
 			}
 		}
 	}
 
 	// 添加默认的 required 规则
-	if (
-		item.required &&
-		_rules.find((v) => {
-			return v.required == true
-		}) == null
-	) {
-		const rule = getConfigRule(RULE_KEYS.REQUIRED, [item.label?.toString() ?? ""])
+	if (isRequired.value && !_rules.find((v) => v.required === true)) {
+		const rule = getConfigRule(RULE_KEYS.REQUIRED, [itemLabel.value?.toString() || ""])
 		if (rule) {
 			Array.isArray(rule) ? _rules.push(...rule) : _rules.push(rule)
 		}
 	}
-	// console.log("RULES===", _rules)
+
 	return _rules
 }
-function checkComponent(name: string) {
-	// if (!props.item?.component && name == "I") {
-	//   return true
-	// }
-	const component = props.item?.component ?? "I"
-	return typeof component == "string"
-		? component.toLocaleUpperCase() == name.toLocaleUpperCase()
-		: (component as any).name == name
-}
-function isDisabled() {
-	if (typeof item.value.disabled == "function") {
-		return item.value.disabled(data)
-	}
-	return item.value.disabled
-}
-function isReadonly() {
-	if (typeof item.value.readonly == "function") {
-		return item.value.readonly(data)
+
+// 检查组件类型
+const checkComponent = (name: string): boolean => {
+	const component = props.item?.component || "I"
+	if (typeof component === "string") {
+		return component.toUpperCase() === name.toUpperCase()
 	}
-	return item.value.readonly
+	return (component as any)?.name === name
 }
-function isDisable(item: any) {
-	if (typeof item.disabled == "function") {
-		return item.disabled(item)
+
+// 判断选项是否禁用
+const isOptionDisable = (option: any): boolean => {
+	if (typeof option.disabled === "function") {
+		return option.disabled(option)
 	}
-	return item.disabled
+	return option.disabled || false
 }
 
-const hasTips = computed(() => {
-	return item.value.tips
-})
+// Tips 相关
+const hasTips = computed(() => !!tipsContent.value)
 const tipsIconClass = computed(() => {
-	let str = item.value.tipsIcon ? item.value.tipsIcon : "bi bi-question-circle-fill "
-	str += item.value.tipsIconClass ? item.value.tipsIconClass : " text-muted ms-1"
-	return str
+	const icon = resolveDynamicValue(item.value.tipsIcon, data.value) || "bi bi-question-circle-fill"
+	const iconClass = resolveDynamicValue(item.value.tipsIconClass, data.value) || "text-muted ms-1"
+	return `${icon} ${iconClass}`
 })
+
+// Prepend 相关
 const hasPrepend = computed(() => {
-	if (
-		(typeof item.value.prependDisabled == "function" && item.value.prependDisabled(data.value)) ||
-		item.value.prependDisabled === true
-	) {
+	if (resolveDynamicValue(item.value.prependDisabled, data.value) === true) {
 		return false
 	}
 	return !!(item.value.prepend || item.value.prependIcon || item.value.prependClickFunc)
 })
-const hasPrependSlot = computed(() => {
-	return item.value.prepend === "slot" || item.value.prepend === "Slot"
-})
+const hasPrependSlot = computed(
+	() => item.value.prepend === "slot" || item.value.prepend === "Slot"
+)
 const prependIcon = computed(() => {
 	if (item.value.prependIcon) {
-		return item.value.prependIcon
-	} else if (item.value.prepend && item.value.prepend !== "icon" && item.value.prepend !== "Icon") {
+		return resolveDynamicValue(item.value.prependIcon, data.value)
+	}
+	if (item.value.prepend && !["icon", "Icon"].includes(item.value.prepend)) {
 		return ""
-	} else if (
-		item.value.prependClickFunc ||
-		item.value.prepend === "icon" ||
-		item.value.prepend === "Icon"
-	) {
+	}
+	if (item.value.prependClickFunc || ["icon", "Icon"].includes(item.value.prepend || "")) {
 		return "bi bi-search text-primary"
 	}
 	return ""
 })
-function prependClickFunc() {
-	item.value.prependClickFunc && item.value.prependClickFunc(data.value)
-}
+const prependText = computed(() => {
+	if (item.value.prepend && !["slot", "Slot", "icon", "Icon"].includes(item.value.prepend)) {
+		return item.value.prepend
+	}
+	return ""
+})
 
+// Append 相关
 const hasAppend = computed(() => {
-	if (
-		(typeof item.value.appendDisabled == "function" && item.value.appendDisabled(data.value)) ||
-		item.value.appendDisabled === true
-	) {
+	if (resolveDynamicValue(item.value.appendDisabled, data.value) === true) {
 		return false
 	}
 	return !!(item.value.append || item.value.appendIcon || item.value.appendClickFunc)
 })
-
-const hasAppendSlot = computed(() => {
-	return item.value.append === "slot" || item.value.append === "Slot"
-})
+const hasAppendSlot = computed(() => item.value.append === "slot" || item.value.append === "Slot")
 const appendIcon = computed(() => {
 	if (item.value.appendIcon) {
-		return item.value.appendIcon
-	} else if (item.value.append && item.value.append !== "icon" && item.value.append !== "Icon") {
+		return resolveDynamicValue(item.value.appendIcon, data.value)
+	}
+	if (item.value.append && !["icon", "Icon"].includes(item.value.append)) {
 		return ""
-	} else if (
-		item.value.appendClickFunc ||
-		item.value.append === "icon" ||
-		item.value.append === "Icon"
-	) {
+	}
+	if (item.value.appendClickFunc || ["icon", "Icon"].includes(item.value.append || "")) {
 		return "bi bi-search text-primary"
 	}
 	return ""
 })
-function appendClickFunc() {
-	item.value.appendClickFunc && item.value.appendClickFunc(data.value)
+const appendText = computed(() => {
+	if (item.value.append && !["slot", "Slot", "icon", "Icon"].includes(item.value.append)) {
+		return item.value.append
+	}
+	return ""
+})
+
+// 事件处理
+const handlePrependClick = () => {
+	item.value.prependClickFunc?.(data.value)
 }
-function init() {
-	//
+const handleAppendClick = () => {
+	item.value.appendClickFunc?.(data.value)
 }
-onMounted(() => {
-	init()
-})
 </script>
 <template>
-	<el-col v-if="isShow" :span="item.span || span">
+	<el-col v-if="isShow" :span="colSpan">
 		<el-form-item
 			:prop="item.field"
-			:class="item.itemClass"
-			:style="item.itemStyle"
-			:required="item.required"
+			:class="elItemClass"
+			:style="elItemStyle"
+			:required="isRequired"
 			:rules="getRules(item)"
-			:label="item.label"
-			:label-width="
-				Number(item.labelWidth || labelWidth)
-					? (item.labelWidth || labelWidth) + 'px'
-					: item.labelWidth || labelWidth
-			"
+			:label="itemLabel"
+			:label-width="itemLabelWidth"
 			v-bind="item.itemProps">
 			<template #label v-if="hasTips">
-				<span class="fw-bold text-dark" :style="item.labelStyle ?? ''">{{ item.label }}</span>
-				<vb-tooltip :content="item.tips ?? ''" placement="top">
+				<span class="fw-bold text-dark" :style="itemLabelStyle">{{ itemLabel }}</span>
+				<vb-tooltip :content="tipsContent" placement="top">
 					<span :class="tipsIconClass" style="cursor: pointer"></span>
 				</vb-tooltip>
 			</template>
@@ -223,67 +217,37 @@ onMounted(() => {
 			<div
 				v-else-if="checkComponent('text') || checkComponent('innerText')"
 				:id="itemId"
-				:class="item.class"
-				:style="item.style">
-				{{ item.innerText || data[item.field] }}
+				:class="itemClass"
+				:style="itemStyle">
+				{{ itemInnerText || data[item.field] }}
 			</div>
 			<template v-else-if="checkComponent('I') || checkComponent('ElInput')">
 				<el-input
-					v-if="isNum"
-					v-model.number="data[item.field]"
-					:id="itemId"
-					:required="item.required"
-					:type="item.type || 'text'"
-					:class="item.class"
-					:style="item.style"
-					:placeholder="item.placeholder ?? item.label"
-					:size="item.size"
-					:disabled="isDisabled()"
-					:readonly="isReadonly()"
-					v-bind="item.props"
-					v-on="item.listeners">
-					<template #prepend v-if="hasPrepend">
-						<slot v-if="hasPrependSlot" :name="`${item.field}_prepend`"></slot>
-						<el-button v-else-if="prependIcon" @click="prependClickFunc">
-							<i :class="prependIcon"></i>
-						</el-button>
-						<span v-else @click="prependClickFunc">{{ item.prepend }}</span>
-					</template>
-					<template #append v-if="hasAppend">
-						<slot v-if="hasAppendSlot" :name="`${item.field}_append`"></slot>
-						<el-button v-else-if="appendIcon" @click="appendClickFunc">
-							<i :class="appendIcon"></i>
-						</el-button>
-						<span v-else @click="appendClickFunc">{{ item.append }}</span>
-					</template>
-				</el-input>
-				<el-input
-					v-else
 					v-model="data[item.field]"
 					:id="itemId"
-					:required="item.required"
-					:type="item.type || 'text'"
-					:class="item.class"
-					:style="item.style"
-					:placeholder="item.placeholder ?? item.label"
-					:size="item.size"
-					:disabled="isDisabled()"
-					:readonly="isReadonly()"
+					:required="isRequired"
+					:type="itemType || 'text'"
+					:class="itemClass"
+					:style="itemStyle"
+					:placeholder="itemPlaceholder || itemLabel"
+					:size="itemSize"
+					:disabled="isDisabled"
+					:readonly="isReadonly"
 					v-bind="item.props"
 					v-on="item.listeners">
 					<template #prepend v-if="hasPrepend">
 						<slot v-if="hasPrependSlot" :name="`${item.field}_prepend`"></slot>
-						<el-button v-else-if="prependIcon" @click="prependClickFunc">
+						<el-button v-else-if="prependIcon" @click="handlePrependClick">
 							<i :class="prependIcon"></i>
 						</el-button>
-						<span v-else @click="prependClickFunc">{{ item.prepend }}</span>
+						<span v-else @click="handlePrependClick">{{ prependText }}</span>
 					</template>
 					<template #append v-if="hasAppend">
 						<slot v-if="hasAppendSlot" :name="`${item.field}_append`"></slot>
-						<el-button v-else-if="appendIcon" @click="appendClickFunc">
+						<el-button v-else-if="appendIcon" @click="handleAppendClick">
 							<i :class="appendIcon"></i>
 						</el-button>
-						<span v-else @click="appendClickFunc">{{ item.append }}</span>
+						<span v-else @click="handleAppendClick">{{ appendText }}</span>
 					</template>
 				</el-input>
 			</template>
@@ -291,15 +255,15 @@ onMounted(() => {
 				<vb-select
 					v-model="data[item.field]"
 					:id="itemId"
-					:type="(item.type || 'select') as any"
-					:required="item.required"
-					:class="item.class"
-					:style="item.style"
-					:placeholder="item.placeholder ?? item.label"
-					:size="item.size"
+					:type="(itemType || 'select') as any"
+					:required="isRequired"
+					:class="itemClass"
+					:style="itemStyle"
+					:placeholder="itemPlaceholder || itemLabel"
+					:size="itemSize"
 					:data="itemData"
-					:disabled="isDisabled()"
-					:readonly="isReadonly()"
+					:disabled="isDisabled"
+					:readonly="isReadonly"
 					v-bind="item.props"
 					v-on="item.listeners"></vb-select>
 			</template>
@@ -307,14 +271,14 @@ onMounted(() => {
 				<vb-upload
 					v-model="data[item.field]"
 					:id="itemId"
-					:required="item.required"
-					:class="item.class"
-					:style="item.style"
-					:placeholder="item.placeholder ?? item.label"
-					:size="item.size"
+					:required="isRequired"
+					:class="itemClass"
+					:style="itemStyle"
+					:placeholder="itemPlaceholder || itemLabel"
+					:size="itemSize"
 					:data="itemData"
-					:disabled="isDisabled()"
-					:readonly="isReadonly()"
+					:disabled="isDisabled"
+					:readonly="isReadonly"
 					v-bind="item.props"
 					v-on="item.listeners"></vb-upload>
 			</template>
@@ -323,14 +287,14 @@ onMounted(() => {
 					v-model="data[item.field]"
 					:id="itemId"
 					:dictType="item.props?.dictType || ''"
-					:type="(item.type || 'select') as any"
-					:required="item.required"
-					:class="item.class"
-					:style="item.style"
-					:placeholder="item.placeholder ?? item.label"
-					:size="item.size"
-					:disabled="isDisabled()"
-					:readonly="isReadonly()"
+					:type="(itemType || 'select') as any"
+					:required="isRequired"
+					:class="itemClass"
+					:style="itemStyle"
+					:placeholder="itemPlaceholder || itemLabel"
+					:size="itemSize"
+					:disabled="isDisabled"
+					:readonly="isReadonly"
 					v-bind="item.props"
 					v-on="item.listeners"></dict-select>
 			</template>
@@ -338,13 +302,13 @@ onMounted(() => {
 				<el-select
 					v-model="data[item.field]"
 					:id="itemId"
-					:required="item.required"
-					:class="item.class"
-					:style="item.style"
-					:placeholder="item.placeholder ?? item.label"
-					:size="item.size"
-					:disabled="isDisabled()"
-					:readonly="isReadonly()"
+					:required="isRequired"
+					:class="itemClass"
+					:style="itemStyle"
+					:placeholder="itemPlaceholder || itemLabel"
+					:size="itemSize"
+					:disabled="isDisabled"
+					:readonly="isReadonly"
 					v-bind="item.props"
 					v-on="item.listeners">
 					<el-option
@@ -353,7 +317,7 @@ onMounted(() => {
 						:value="select.value"
 						:label="select.label"
 						:class="select.class"
-						:disabled="isDisable(select)"
+						:disabled="isOptionDisable(select)"
 						:style="select.style"></el-option>
 				</el-select>
 			</template>
@@ -361,12 +325,12 @@ onMounted(() => {
 				<el-radio-group
 					v-model="data[item.field]"
 					:id="itemId"
-					:required="item.required"
-					:class="item.class"
-					:style="item.style"
-					:size="item.size"
-					:disabled="isDisabled()"
-					:readonly="isReadonly()"
+					:required="isRequired"
+					:class="itemClass"
+					:style="itemStyle"
+					:size="itemSize"
+					:disabled="isDisabled"
+					:readonly="isReadonly"
 					v-bind="item.props"
 					v-on="item.listeners">
 					<el-radio
@@ -375,7 +339,7 @@ onMounted(() => {
 						:label="radio.value"
 						:class="radio.class"
 						:style="radio.style"
-						:disabled="isDisable(radio)">
+						:disabled="isOptionDisable(radio)">
 						{{ radio.label }}
 					</el-radio>
 				</el-radio-group>
@@ -384,12 +348,12 @@ onMounted(() => {
 				<el-checkbox-group
 					v-model="data[item.field]"
 					:id="itemId"
-					:required="item.required"
-					:class="item.class"
-					:style="item.style"
-					:size="item.size"
-					:disabled="isDisabled()"
-					:readonly="isReadonly()"
+					:required="isRequired"
+					:class="itemClass"
+					:style="itemStyle"
+					:size="itemSize"
+					:disabled="isDisabled"
+					:readonly="isReadonly"
 					v-bind="item.props"
 					v-on="item.listeners">
 					<el-checkbox
@@ -398,7 +362,7 @@ onMounted(() => {
 						:label="checkbox.value"
 						:class="checkbox.class"
 						:style="checkbox.style"
-						:disabled="isDisable(checkbox)">
+						:disabled="isOptionDisable(checkbox)">
 						{{ checkbox.label }}
 					</el-checkbox>
 				</el-checkbox-group>
@@ -407,12 +371,12 @@ onMounted(() => {
 				<el-date-picker
 					v-model="data[item.field]"
 					:id="itemId"
-					:required="item.required"
-					:class="item.class"
-					:style="item.style"
-					:size="item.size"
-					:disabled="isDisabled()"
-					:readonly="isReadonly()"
+					:required="isRequired"
+					:class="itemClass"
+					:style="itemStyle"
+					:size="itemSize"
+					:disabled="isDisabled"
+					:readonly="isReadonly"
 					v-bind="item.props"
 					v-on="item.listeners"></el-date-picker>
 			</template>
@@ -420,14 +384,14 @@ onMounted(() => {
 				<vb-select-tree
 					v-model="data[item.field]"
 					:id="itemId"
-					:required="item.required"
+					:required="isRequired"
 					:data="itemData"
-					:placeholder="item.placeholder ?? item.label"
-					:class="item.class"
-					:style="item.style"
-					:size="item.size"
-					:disabled="isDisabled()"
-					:readonly="isReadonly()"
+					:placeholder="itemPlaceholder || itemLabel"
+					:class="itemClass"
+					:style="itemStyle"
+					:size="itemSize"
+					:disabled="isDisabled"
+					:readonly="isReadonly"
 					v-bind="item.props"
 					v-on="item.listeners"></vb-select-tree>
 			</template>
@@ -435,14 +399,14 @@ onMounted(() => {
 				<vb-editor
 					v-model="data[item.field]"
 					:id="itemId"
-					:required="item.required"
-					:class="item.class"
-					:style="item.style"
-					:placeholder="item.placeholder ?? item.label"
-					:size="item.size"
+					:required="isRequired"
+					:class="itemClass"
+					:style="itemStyle"
+					:placeholder="itemPlaceholder || itemLabel"
+					:size="itemSize"
 					:data="itemData"
-					:disabled="isDisabled()"
-					:readonly="isReadonly()"
+					:disabled="isDisabled"
+					:readonly="isReadonly"
 					v-bind="item.props"
 					v-on="item.listeners"></vb-editor>
 			</template>
@@ -452,13 +416,13 @@ onMounted(() => {
 					v-model="data[item.field]"
 					v-model:value="data[item.field]"
 					:id="itemId"
-					:required="item.required"
-					:class="item.class"
-					:style="item.style"
-					:size="item.size"
-					:placeholder="item.placeholder ?? item.label"
-					:disabled="isDisabled()"
-					:readonly="isReadonly()"
+					:required="isRequired"
+					:class="itemClass"
+					:style="itemStyle"
+					:size="itemSize"
+					:placeholder="itemPlaceholder || itemLabel"
+					:disabled="isDisabled"
+					:readonly="isReadonly"
 					v-bind="item.props"
 					v-on="item.listeners"></component>
 			</template>

+ 23 - 25
UI/VAP_V3.VUE/src/components/form/models.ts

@@ -5,16 +5,16 @@ import type { AlertProps } from "element-plus/es/components/alert/src/alert"
 export interface VbFormItem {
 	id?: string | (() => string)
 	field: string
-	label?: string
+	label?: string | ((v?: any) => string)
 	show?: boolean | ((v?: any) => boolean)
 	disabled?: boolean | ((v?: any) => boolean)
 	readonly?: boolean | ((v?: any) => boolean)
-	required?: boolean
-	itemClass?: string
-	class?: string
-	itemStyle?: string
-	style?: string
-	type?: string
+	required?: boolean | ((v?: any) => boolean)
+	itemClass?: string | ((v?: any) => string)
+	class?: string | ((v?: any) => string)
+	itemStyle?: string | ((v?: any) => string)
+	style?: string | ((v?: any) => string)
+	type?: string | ((v?: any) => string)
 	component?:
 		| "slot"
 		| "Slot" // name 为 {field} 如果在modal里 则为 {field}_form
@@ -28,21 +28,19 @@ export interface VbFormItem {
 		| "VbUpload"
 		| "Dict" // DictSelect
 		| "DictSelect"
-		| "VS" // VbSelect
-		| "VbSelectTree"
 		| "VST" // VbSelectTree
+		| "VbSelectTree"
 		| "I" // ElInput
 		| "S" // ElSelect
 		| "C" // ElCheckboxGroup
 		| "R" // ElRadioGroup
 		| "D" // ElDatePicker
 		| Component
-	innerText?: string // component 为 innerText 时,优先显示的文本,否则会显示当前的字段值
-	span?: number
-	labelWidth?: number
-
-	size?: "large" | "default" | "small"
-	placeholder?: string
+	innerText?: string | ((v?: any) => string) // component 为 innerText 时,优先显示的文本,否则会显示当前的字段值
+	span?: number | ((v?: any) => number)
+	labelWidth?: number | ((v?: any) => number)
+	size?: "large" | "default" | "small" | ((v?: any) => "large" | "default" | "small")
+	placeholder?: string | ((v?: any) => string)
 	rules?: Array<string | FormItemRule[] | FormItemRule> // 全局规则的KEY 或 自定义的规则
 	ruleFormat?: Array<Array<string | number> | undefined> // 数组索引对应rules顺序,对应的rule是自定义的(或者没有format数据)要填undefined  eg: [[0,1],undfiend,["名称"]]
 	data?:
@@ -58,21 +56,21 @@ export interface VbFormItem {
 				value: string | number
 				class?: string
 				style?: string
-				disabled?: string | ((v: any) => boolean)
+				disabled?: boolean | ((v: any) => boolean)
 		  }>)
 	itemProps?: any // 注入到 el-form-item 的属性
 	props?: any // 当 component 为渲染组件时,注入到渲染组件当中的属性
-	listeners?: any // 当 component 为渲染组件时,注入到渲染组件当中的事件
-	tips?: string
-	labelStyle?: string // label样式 tip有值生效
-	tipsIcon?: string // 默认值  bi bi-question-circle-fill
-	tipsIconClass?: string // 默认值  text-muted
-	prepend: string | "slot" | "Slot" | "icon" | "Icon"
-	prependIcon?: string // prepend 为 el-button 中 icon 时显示的图标 默认值:bi bi-search text-primary
+	listeners?: any | ((v?: any) => any) // 当 component 为渲染组件时,注入到渲染组件当中的事件
+	tips?: string | ((v?: any) => string)
+	labelStyle?: string | ((v?: any) => string) // label样式 tip有值生效
+	tipsIcon?: string | ((v?: any) => string) // 默认值  bi bi-question-circle-fill
+	tipsIconClass?: string | ((v?: any) => string) // 默认值  text-muted
+	prepend?: string | "slot" | "Slot" | "icon" | "Icon"
+	prependIcon?: string | ((v?: any) => string) // prepend 为 el-button 中 icon 时显示的图标 默认值:bi bi-search text-primary
 	prependClickFunc?: (v: any) => void
 	prependDisabled?: boolean | ((v: any) => boolean)
-	append: string | "slot" | "Slot" | "icon" | "Icon"
-	appendIcon?: string // append 为 el-button 中 icon 时显示的图标 默认值:bi bi-search text-primary
+	append?: string | "slot" | "Slot" | "icon" | "Icon"
+	appendIcon?: string | ((v?: any) => string) // append 为 el-button 中 icon 时显示的图标 默认值:bi bi-search text-primary
 	appendClickFunc?: (v: any) => void
 	appendDisabled?: boolean | ((v: any) => boolean)
 }