This commit is contained in:
2026-09-24 16:25:22 +08:00
commit 7428184f01
1198 changed files with 314515 additions and 0 deletions
@@ -0,0 +1,509 @@
<script lang="ts">
import { PropType } from "vue"
import { getUid } from "../../core/util/xCoreUtil.uts"
import { getDefaultColor } from "../../core/util/xCoreColorUtil.uts"
import { checkIsCssUnit } from "../../core/util/xCoreUtil.uts"
import { xConfig } from "../../config/xConfig.uts"
import { FORM_RULE, FORM_SUBMIT_OBJECT} from "../../interface.uts"
import { FORM_ITEM } from "../x-form/interface.uts"
import { formVaild} from "../x-form/util.uts"
/**
* @name 表单子组件 xFormItem
* @description 从1.1.2开始允许非xform直接子节点了,也就是在xform可以嵌套view进行form-item布局了,但建议不要嵌套太深,影响性能.
* 1.1.17开始支持嵌套对象字段,key必须/分割如'user/id'sdk不支持以.分割符。
* @page /pages/index/form
* @category 表单组件
* @constant 平台兼容
* | Harmony | H5 | andriod | IOS | 小程序 | UTS | UNIAPP-X SDK | version |
| --- | --- | --- | --- | --- | --- | --- | --- |
| ☑ | ☑ | ☑️ | ☑️ | ☑️ | ☑️ | 4.76+ | 1.1.18 |
*/
export default {
name:"xFormItem",
data() {
return {
id: ("xFormItem-" + getUid()) as string,
errorText: "请正确填写",
isError: false,
first: true,
tid: 0
}
},
props: {
/**
* 表单名称
*/
label: {
type: String,
default: ""
},
/**
* 是否显示标题
*/
showLabel: {
type: [Boolean, null] as PropType<boolean | null>,
default: null
},
/**
* 校验的字段名称
* 字段名称一定要存在于form表单数据中。否则报错。
*/
field: {
type: String,
default: ""
},
/**
* 是否是必填项.
* 设置了此值,才会执行校验。
*/
required: {
type: Boolean,
default: false
},
/**
* 是否显示校验必填项前面的红*符号
*/
showRequired: {
type: Boolean,
default: true
},
/**
* 校验规则对象,uniappx暂不支持对象中嵌套泛型函数
* 从1.1.10开始rule对象中增加了trigger:change,blur校验时机,blur主要是用在input组件上的
* 如果你内部没有input而使用blur,会造成不校验的问题,请认真阅读文档.
*/
rule: {
type: Array as PropType<FORM_RULE[]>,
default: () : FORM_RULE[] => [] as FORM_RULE[]
},
/**
* 标签宽(横向表单时有效)
* 默认空值取父form统一的设置。
*/
labelWidth: {
type: String,
default: ""
},
/**
* 默认空值取父form统一的设置。
* vertical|horizontal
*/
labelDirection: {
type: String,
default: ""
},
/**
* 默认空值取父form统一的设置。
* 标签的文本颜色
*/
labelFontColor: {
type: String,
default: ""
},
/**
* 默认空值取父form统一的设置。
* 标签的文本颜色
*/
labelFontSize: {
type: String,
default: ""
},
/**
* 标签标题对齐方式
* left,right,center
*/
labelAlign:{
type:String,
default:"left"
},
/**
* 是否显示底部边框
*/
showBottomBorder: {
type: Boolean,
default: true
},
/**
* 排版布局上和下的间隙。
* 数组必须是2长度
* 第一个是上间隙,第二个是下间隙,
* 如果showBottomBorder为false时下间隙第二个参数会被取消。
* 如果label为horizontal横向时,第一个参数的上间隙生效,如果是上下排列不生效。
*/
cellPadding: {
type: Array as PropType<string[]>,
default: () : string[] => ['10', '10'] as string[]
},
/**
* 排版布局上和下的间隙。
* 数组必须是2长度
* 第一个是上间隙,第二个是下间隙,
* 如果label为horizontal横向时,些参数失效,只有竖向时才生效。
*/
labelPadding: {
type: Array as PropType<string[]>,
default: () : string[] => ['12', '12'] as string[]
},
/**
* 校验时,是否显示下边的出错信息
*/
showError:{
type:Boolean,
default:true
},
/**
* 默认区域内的自定样式
*/
contentStyle:{
type:String,
default:""
}
},
inject: {
XFORMITEM_TOP: { type: Number, default: 0 },
XFORMITEM_LABEL_WIDTH: { type: String, default: '100' },
XFORMITEM_LABEL_Direction: { type: String, default: 'horizontal' },
XFORMITEM_LABEL_FontColor: { type: String, default: '#333333' },
XFORMITEM_LABEL_SCROLL: { type: Boolean, default: true },
XFORMITEM_LABEL_FontSize: { type: String, default: '16' },
XFORMITEM_SHOWLABEL: { type: Boolean, default: true },
XFORMITEM_ERROR_ALIGN: { type: String, default: 'right' },
},
mounted() {
this.pushDataToParent();
},
beforeUnmount() {
this.removeSelf();
clearTimeout(this.tid);
},
computed: {
_contentStyle():string{
return this.contentStyle
},
_showError():boolean{
return this.showError
},
_cellPadding() : string[] {
let mb = checkIsCssUnit(this.cellPadding[0], xConfig.unit)
let pb = checkIsCssUnit(this.cellPadding[1], xConfig.unit)
return [mb, pb]
},
_labelPadding() : string[] {
let mb = checkIsCssUnit(this.labelPadding[0], xConfig.unit)
let pb = checkIsCssUnit(this.labelPadding[1], xConfig.unit)
return [mb, pb]
},
_parentTop() : number {
return this.XFORMITEM_TOP
},
_labelWidth() : string {
if (this.labelWidth != "") return checkIsCssUnit(this.labelWidth, xConfig.unit)
return checkIsCssUnit(this.XFORMITEM_LABEL_WIDTH, xConfig.unit)
},
_labelFontSize() : string {
let labelsize = checkIsCssUnit(this.XFORMITEM_LABEL_FontSize, xConfig.unit)
if (this.labelFontSize != "") {
labelsize = checkIsCssUnit(this.labelFontSize, xConfig.unit)
}
return labelsize
},
_labelDirection() : string {
if (this.labelDirection != "") return this.labelDirection
return this.XFORMITEM_LABEL_Direction
},
_labelFontColor() : string {
if (this.labelFontColor != "") return getDefaultColor(this.labelFontColor)
return getDefaultColor(this.XFORMITEM_LABEL_FontColor)
},
_label() : string {
return this.label
},
_showLabel() : boolean {
let show = this.XFORMITEM_SHOWLABEL;
if (this.showLabel != null) {
show = this.showLabel! as boolean;
}
return show
},
_showRequired() : boolean {
return this.showRequired
},
_rule() : FORM_RULE[] {
return this.rule;
},
_showBottomBoder() : boolean {
return this.showBottomBorder
},
_required() : boolean {
return this.required
},
_errorFontsize() : string {
return (xConfig.fontScale * 14).toString() + 'px'
},
_bordrBottomSolid() : string {
if (!this.showBottomBorder) return "border-bottom:none";
let lightSolid = "border-bottom: 1px solid #f5f5f5";
let darkSolid = `border-bottom: 1px solid ${xConfig.borderDarkColor}`
return xConfig.dark == 'dark' ? darkSolid : lightSolid
},
_labelAlign():string{
let dq = 'flex-start'
if(this.labelAlign=='right'){
dq = 'flex-end'
}else if(this.labelAlign=='center'){
dq = 'center'
}
return dq;
}
},
watch: {
field() {
this.pushDataToParent();
},
},
methods: {
pushDataToParent() {
let pelement = this.findParent(this);
if (pelement == null) return;
// @ts-ignore
let parent : XFormComponentPublicInstance = pelement as XFormComponentPublicInstance;
// 预先传递,怕子组件内的内容复杂导致查询延误,导致顺序错乱。
parent.pushAdd({
id: this.id,
ele: this,
top: 0,
name: this.field,
} as FORM_ITEM)
let t = this;
uni.createSelectorQuery().in(t)
.select(".xFormItem")
.boundingClientRect().exec((ret) => {
let nodeinfo = ret[0] as NodeInfo
parent.pushAdd({
id: t.id,
ele: t,
top: nodeinfo.top!,
name: t.field,
} as FORM_ITEM)
})
},
removeSelf() {
let pelement = this.findParent(this);
if (pelement == null) return;
// @ts-ignore
let parent : XFormComponentPublicInstance = pelement as XFormComponentPublicInstance;
parent.delItem(this.id)
},
getParentRules():FORM_RULE[] {
let pelement = this.findParent(this);
if (pelement == null) return [] as FORM_RULE[];
// @ts-ignore
let parent : XFormComponentPublicInstance = pelement as XFormComponentPublicInstance;
return parent.getRules(this.field) as FORM_RULE[]
},
vaildCompele(val : any|null,isSkipBlur:boolean|null = true) : FORM_SUBMIT_OBJECT {
this.first = false;
if (!this._required) {
return {
valid: true,
key: this.field,
value: val,
errorMessage: ""
} as FORM_SUBMIT_OBJECT;
}
const rulesList = [...this._rule,...this.getParentRules()] as FORM_RULE[]
if (rulesList.length == 0 && this._required) {
let isSuccess = formVaild(val,{} as FORM_RULE)
this.isError = !isSuccess
return {
valid: isSuccess,
key: this.field,
value: val,
errorMessage: isSuccess ? "" : '请正确填写/选择'
} as FORM_SUBMIT_OBJECT;
}
let isSuccess = true;
for (let i = 0; i < rulesList.length; i++) {
let item = rulesList[i]
if(item.trigger == 'blur'&&isSkipBlur==true) continue;
isSuccess = formVaild(val,item)
this.isError = !isSuccess
if (!isSuccess) {
if (item.errorMessage != "" && typeof item.errorMessage == 'string') {
this.errorText = item.errorMessage! as string
} else {
this.errorText = '请正确填写/选择'
}
break;
}
}
return {
valid: isSuccess,
key: this.field,
value: val,
errorMessage: isSuccess ? "" : this.errorText
} as FORM_SUBMIT_OBJECT;
},
clearValid(){
this.isError=false;
this.first = true;
},
/**
* 实时校验结果。form中要开启,如果没有这种需求可以不开启,会损耗性能。
*/
getVaildStatus(val : any|null) : FORM_SUBMIT_OBJECT {
if (!this._required) {
return {
valid: true,
key: this.field,
value: val,
errorMessage: ""
} as FORM_SUBMIT_OBJECT;
}
const rulesList = [...this._rule,...this.getParentRules()] as FORM_RULE[]
if (rulesList.length == 0 && this._required) {
let isSuccess = formVaild(val,{} as FORM_RULE)
return {
valid: isSuccess,
key: this.field,
value: val,
errorMessage: isSuccess ? "" : '请正确填写/选择'
} as FORM_SUBMIT_OBJECT;
}
let isSuccess = true;
let errotips = ''
for (let i = 0; i < rulesList.length; i++) {
let item = rulesList[i]
isSuccess = formVaild(val,item)
if (!isSuccess) {
if (item.errorMessage != "" && typeof item.errorMessage == 'string') {
errotips = item.errorMessage! as string
} else {
errotips = '请正确填写/选择'
}
break;
}
}
return {
valid: isSuccess,
key: this.field,
value: val,
errorMessage: isSuccess ? "" : errotips
} as FORM_SUBMIT_OBJECT;
},
/**
* 为Input定制校验函数.
*/
validByblur(value:any){
this.vaildCompele(value,false)
},
findParent(parent:VueComponent|null):VueComponent|null{
if(parent == null) return null;
// #ifdef WEB||APP-IOS|| MP-WEIXIN
if(parent.$parent?.id?.indexOf('xForm')>-1) return parent.$parent;
// #endif
// #ifdef APP-HARMONY
if(parent.$parent?.$options?.name?.indexOf('xForm')>-1) return parent.$parent;
// #endif
// #ifdef APP-ANDROID
if(parent.$parent instanceof XFormComponentPublicInstance) return parent.$parent;
// #endif
let parents = this.findParent(parent.$parent)
// #ifdef WEB||APP-IOS || MP-WEIXIN
if(parents?.id?.indexOf('xForm')>-1) return parents;
// #endif
// #ifdef APP-HARMONY
if(parents?.$options?.name?.indexOf('xForm')>-1) return parents;
// #endif
// #ifdef APP-ANDROID
if(parents instanceof XFormComponentPublicInstance) return parents;
// #endif
return null;
}
}
}
</script>
<template>
<view class="xFormItem" ref="xFormItem" :style="[
_bordrBottomSolid,
{
paddingTop: _cellPadding[0],
paddingBottom: _cellPadding[1]
}
]">
<view class="xFormIitemWrap" :style="{flexDirection:_labelDirection=='horizontal'?'row':'column'}">
<view v-if="_showLabel" class="xFormIteLabel" :style="{
width:_labelDirection=='horizontal'?_labelWidth:'auto',
paddingBottom:_labelDirection=='horizontal'?'0':_labelPadding[1],
paddingTop:_labelDirection=='horizontal'?'0':_labelPadding[0],
'justify-content': _labelAlign
}">
<!--
@slot 标题
-->
<slot name="label">
<text v-if="_showRequired&&_required"
:style="{fontSize:_labelFontSize,color:'red',paddingRight:'8rpx'}">*</text>
<x-text v-if="_label" :font-size="_labelFontSize" :color="_labelFontColor">
{{_label}}
</x-text>
</slot>
</view>
<view class="xFormIteContent" :style="[{flex:_labelDirection=='horizontal'?'1':'auto'},_contentStyle]">
<!--
@slot 默认内容区域
-->
<slot></slot>
</view>
</view>
<view v-if="isError&&!first&&_required&&_showError" class="xFormItemError">
<!--
@slot 出错提示的插槽
-->
<slot name="error">
<text class="xFormItemErrorText" :style="{fontSize:_errorFontsize,textAlign:XFORMITEM_ERROR_ALIGN}">{{errorText}}</text>
</slot>
</view>
</view>
</template>
<style scoped>
.xFormIteLabel {
display: flex;
flex-direction: row;
align-items: center;
}
.xFormItem {
display: flex;
flex-direction: column;
}
.xFormItemErrorText {
color: red;
padding-top: 5px;
display: flex;
}
.xFormIitemWrap{
display: flex;
}
</style>