first commit

This commit is contained in:
lingxiao865
2026-02-10 08:05:03 +08:00
commit c5af079d8c
1094 changed files with 97530 additions and 0 deletions

View File

@@ -0,0 +1,37 @@
:: BASE_DOC ::
## API
### FormItem Props
name | type | default | description | required
-- | -- | -- | -- | --
custom-style | Object | - | CSS(Cascading Style Sheets) | N
arrow | Boolean | false | \- | N
content-align | String | - | options: left/right | N
for | String | - | \- | N
help | String | - | \- | N
label | String | '' | \- | N
label-align | String | - | options: left/right/top | N
label-width | String / Number | - | \- | N
name | String | - | \- | N
required-mark | Boolean | undefined | \- | N
rules | Array | - | Typescript: `Array<FormRule>` | N
show-error-message | Boolean | undefined | \- | N
### FormItem Slots
name | Description
-- | --
help | \-
label | \-
### CSS Variables
The component provides the following CSS variables, which can be used to customize styles.
Name | Default Value | Description
-- | -- | --
--td-form-item-horizontal-padding | 32rpx | -
--td-form-item-justify-content | space-between | -
--td-form-item-label-width | 160rpx | -
--td-form-item-vertical-padding | 32rpx | -

View File

@@ -0,0 +1,37 @@
:: BASE_DOC ::
## API
### FormItem Props
名称 | 类型 | 默认值 | 描述 | 必传
-- | -- | -- | -- | --
custom-style | Object | - | 自定义样式 | N
arrow | Boolean | false | 是否显示右侧箭头 | N
content-align | String | - | 表单内容对齐方式,优先级高于 Form.contentAlign。可选项left/right | N
for | String | - | label 原生属性 | N
help | String | - | 表单项说明内容 | N
label | String | '' | 字段标签名称 | N
label-align | String | - | 表单字段标签对齐方式:左对齐、右对齐、顶部对齐。默认使用 Form 的对齐方式,优先级高于 Form.labelAlign。可选项left/right/top | N
label-width | String / Number | - | 可以整体设置标签宽度,优先级高于 Form.labelWidth | N
name | String | - | 表单字段名称 | N
required-mark | Boolean | undefined | 是否显示必填符号(*),优先级高于 Form.requiredMark | N
rules | Array | - | 表单字段校验规则。TS 类型:`Array<FormRule>` | N
show-error-message | Boolean | undefined | 校验不通过时,是否显示错误提示信息,优先级高于 `Form.showErrorMessage` | N
### FormItem Slots
名称 | 描述
-- | --
help | 自定义 `help` 显示内容
label | 自定义 `label` 显示内容
### CSS Variables
组件提供了下列 CSS 变量,可用于自定义样式。
名称 | 默认值 | 描述
-- | -- | --
--td-form-item-horizontal-padding | 32rpx | -
--td-form-item-justify-content | space-between | -
--td-form-item-label-width | 160rpx | -
--td-form-item-vertical-padding | 32rpx | -

View File

@@ -0,0 +1,120 @@
/* Less 支持测试 - 监听功能验证 */
.t-form-item {
position: relative;
display: flex;
align-items: flex-start;
justify-content: var(--td-form-item-justify-content, space-between);
width: 100%;
padding: var(--td-form-item-horizontal-padding, 32rpx) var(--td-form-item-vertical-padding, 32rpx);
background-color: #ffffff;
--td-input-vertical-padding: 0rpx;
--td-textarea-vertical-padding: 0rpx;
--td-textarea-horizontal-padding: 0rpx;
box-sizing: border-box;
}
.t-form-item--top {
display: flex;
flex-direction: column;
}
.t-form-item--bordered {
border-bottom: 1rpx solid var(--td-border-color, var(--td-gray-color-3, #e7e7e7));
}
.t-form-item--error .t-form-item__error {
color: var(--td-error-color, var(--td-error-color-6, #d54941));
}
.t-form-item--success .t-form-item__success {
color: var(--td-success-color, var(--td-success-color-5, #2ba471));
}
.t-form-item__label {
position: relative;
display: flex;
align-items: center;
width: var(--td-form-item-label-width, 160rpx);
margin-right: 16rpx;
font-size: var(--td-font-size-m, 32rpx);
color: var(--td-text-color-primary, var(--td-font-gray-1, rgba(0, 0, 0, 0.9)));
line-height: 1.5;
}
.t-form-item__label--required {
color: var(--td-error-color, var(--td-error-color-6, #d54941));
margin-right: 4rpx;
}
.t-form-item__label--colon {
margin-left: 4rpx;
}
.t-form-item__label--left {
display: flex;
align-items: center;
justify-content: flex-start;
}
.t-form-item__label--right {
display: flex;
align-items: center;
justify-content: flex-end;
}
.t-form-item__label--top {
position: relative;
margin-right: 0;
width: 100%;
}
.t-form-item__controls {
flex: 1;
width: 100%;
margin-top: 8rpx;
}
.t-form-item__controls--left {
text-align: left;
}
.t-form-item__controls--right {
text-align: right;
}
.t-form-item__controls-content {
width: 100%;
display: flex;
align-items: center;
justify-content: space-between;
}
.t-form-item__help {
font-size: var(--td-font-size-m, 32rpx);
color: var(--td-font-gray-1, rgba(0, 0, 0, 0.9));
line-height: 1.4;
}
.t-form-item__desc-link {
margin-top: 8rpx;
color: var(--td-primary-color-7, #0052d9);
font-size: var(--td-font-size-s, 24rpx);
line-height: 1.4;
}
.t-form-item__error {
margin-top: 4rpx;
font-size: var(--td-font-size-s, 24rpx);
line-height: 1.4;
}
.t-form-item__error--error {
color: var(--td-error-color, var(--td-error-color-6, #d54941));
}
.t-form-item__error--warning {
color: var(--td-warning-color, var(--td-warning-color-5, #e37318));
}
.t-form-item__success {
margin-top: 4rpx;
font-size: var(--td-font-size-s, 24rpx);
color: var(--td-success-color, var(--td-success-color-5, #2ba471));
line-height: 1.4;
}
.t-form-item__arrow {
display: flex;
align-items: center;
margin-left: 8rpx;
color: var(--td-text-color-placeholder, var(--td-font-gray-3, rgba(0, 0, 0, 0.4)));
}
.t-form-item__extra {
margin-left: 16rpx;
}
.desc-wrapper {
display: flex;
align-items: center;
gap: 12rpx;
height: 50rpx;
--td-button-font-weight: 400;
}

View File

@@ -0,0 +1,396 @@
<template>
<view
:class="
classPrefix +
' ' + classPrefix + '--' + dataLabelAlign +
' ' + classPrefix + '--bordered' +
' ' + classPrefix + '--' + (verifyStatus === 2 ? 'error' : verifyStatus === 1 ? 'success' : '') +
' ' + tClass
"
:style="tools._style([customStyle])"
>
<view
v-if="label"
:class="classPrefix + '__label ' + classPrefix + '__label--' + dataLabelAlign + ' ' + tClassLabel"
:style="'width: ' + dataLabelWidth"
>
<text
v-if="dataRequiredMark && requiredMarkPosition === 'left'"
:class="classPrefix + '__label--required'"
>
*
</text>
<slot
name="label"
>
<text>
{{ label }}
</text>
</slot>
<text
v-if="dataRequiredMark && requiredMarkPosition === 'right'"
:class="classPrefix + '__label--required'"
>
*
</text>
<text
v-if="colon"
:class="classPrefix + '__label--colon'"
>
:
</text>
</view>
<view
:class="classPrefix + '__controls ' + tClassControls"
:style="contentStyle"
>
<view :class="classPrefix + '__controls-content ' + classPrefix + '__controls-content--' + dataContentAlign">
<slot />
<view
v-if="arrow"
:class="classPrefix + '__arrow'"
>
<t-icon
name="chevron-right"
size="16"
/>
</view>
</view>
<view
:class="classPrefix + '__help ' + classPrefix + '__help--' + dataContentAlign + tClassHelp"
:style="errorPosition"
>
<slot name="help">
{{ help }}
</slot>
</view>
<view
v-if="errorList.length > 0 && dataShowErrorMessage"
:class="classPrefix + '__error ' + classPrefix + '__error--' + (errorList[0].type || 'error')"
>
{{ errorList[0].message }}
</view>
<view
v-if="successList.length > 0"
:class="classPrefix + '__success'"
>
{{ successList[0].message }}
</view>
</view>
<view :class="classPrefix + '__extra ' + tClassExtra">
<slot name="extra" />
</view>
</view>
</template>
<script>
import { uniComponent } from '../common/src/index';
import { prefix } from '../common/config';
import props from './props';
import { validate, ValidateStatus } from './form-model';
import TIcon from '../icon/icon.vue';
import TButton from '../button/button.vue';
import { ChildrenMixin, RELATION_MAP } from '../common/relation';
import tools from '../common/utils.wxs';
import { isNumber } from '../common/validator';
const name = `${prefix}-form-item`;
export default uniComponent({
name,
options: {
styleIsolation: 'shared',
},
externalClasses: [
`${prefix}-class`,
`${prefix}-class-label`,
`${prefix}-class-controls`,
`${prefix}-class-help`,
`${prefix}-class-extra`,
],
provide() {
return {
[RELATION_MAP.FormKey]: this,
};
},
mixins: [ChildrenMixin(RELATION_MAP.FormItem)],
components: {
TIcon,
TButton,
},
props: {
...props,
},
data() {
return {
prefix,
classPrefix: name,
errorList: [],
successList: [],
verifyStatus: ValidateStatus.TO_BE_VALIDATED,
needResetField: false,
resetValidating: false,
// rules: [],
form: {},
colon: false,
// showErrorMessage: true,
tools,
dataRules: this.rules,
dataLabelAlign: this.labelAlign,
dataLabelWidth: this.labelWidth,
dataRequiredMark: this.requiredMark,
dataShowErrorMessage: this.showErrorMessage,
dataContentAlign: this.contentAlign,
errorPosition: '',
};
},
computed: {
contentStyle() {
const { labelWidth, labelAlign } = this;
let contentStyle = {};
if (labelWidth && labelAlign !== 'top') {
if (isNumber(labelWidth)) {
contentStyle = { marginLeft: `${labelWidth}px` };
} else {
contentStyle = { marginLeft: labelWidth };
}
}
return tools._style(contentStyle);
},
},
watch: {
},
created() {
// this.initFormItem();
},
onBeforeUnmount() {
if (this.form) {
this.form.unregisterChild(this.name);
}
},
mounted() {
},
methods: {
innerAfterLinked() {
const target = this[RELATION_MAP.FormItem];
target.registerChild(this);
this.form = target;
this.initFormItem();
const { requiredMark, labelAlign, labelWidth, showErrorMessage, contentAlign } = this;
const isRequired = target.rules[this.name]?.some(rule => rule.required);
this.dataRules = target.rules[this.name];
this.colon = target.colon;
this.dataLabelAlign = labelAlign || target.labelAlign || 'right';
this.dataLabelWidth = labelWidth || target.labelWidth;
this.dataContentAlign = contentAlign || target.contentAlign,
this.dataRequiredMark = (() => {
if (!isRequired) {
return false;
}
if (typeof requiredMark === 'boolean') {
return requiredMark;
}
return target.requiredMark || false;
})();
this.dataShowErrorMessage = typeof showErrorMessage === 'boolean' ? showErrorMessage : target.showErrorMessage;
this.requiredMarkPosition = target.requiredMarkPosition;
setTimeout(() => {
this.errorPosition = this.dataLabelAlign !== 'top' && `text-align: ${this.dataContentAlign}`;
}, 33);
},
innerAfterUnlinked() {
if (this.form) {
this.form.unregisterChild(this.name);
}
},
// 处理描述信息链接点击事件
handlePreviewImage(e) {
const { url } = e.currentTarget.dataset;
const urls = url.map(item => item.url) || [];
if (url) {
uni.previewImage({
urls,
current: urls[0],
});
}
},
// 初始化表单项
initFormItem() {
this.setInitialValue();
},
// 设置初始值
setInitialValue() {
const { name } = this;
if (name && this.form) {
const { formData } = this.form;
this.initialValue = formData[name];
}
},
// 获取表单数据
getFormData() {
if (this.form) {
return this.form.formData;
}
return {};
},
// 获取当前值
getValue() {
const { name } = this;
if (name && this.form) {
const { formData } = this.form;
return formData[name];
}
return undefined;
},
// 获取验证规则
getRules() {
const { rules } = this;
// 优先使用组件自身的规则
if (rules && rules.length > 0) {
return rules;
}
// 使用表单的规则
return this.dataRules || [];
},
// 验证表单项
async validate(data, trigger, showErrorMessage) {
const rules = this.getRules();
if (rules.length === 0) {
return { [this.name]: true };
}
// 根据触发方式过滤规则
const filteredRules = trigger === 'all'
? rules
: rules.filter(rule => (rule.trigger || 'change') === trigger);
if (filteredRules.length === 0) {
return { [this.name]: true };
}
const value = data[this.name];
const results = await validate(value, filteredRules);
// 分析验证结果
const analysis = this.analysisValidateResult(results);
// 更新状态
this.updateValidateStatus(analysis, showErrorMessage);
// 返回验证结果
const result = {};
result[this.name] = analysis.errorList.length > 0 ? analysis.errorList : true;
return result;
},
// 纯净验证(不显示错误信息)
async validateOnly(trigger) {
return this.validate(this.getFormData(), trigger, false);
},
// 分析验证结果
analysisValidateResult(results) {
const errorList = results.filter(item => item.result !== true);
const successList = results.filter(item => item.result === true && item.message && item.type === 'success');
return {
errorList,
successList,
resultList: results,
};
},
// 更新验证状态
updateValidateStatus(analysis) {
const { errorList, successList } = analysis;
this.errorList = errorList;
this.successList = successList;
this.verifyStatus = errorList.length > 0 ? ValidateStatus.FAIL : ValidateStatus.SUCCESS;
},
// 清空验证结果
clearValidate() {
this.errorList = [];
this.successList = [];
this.verifyStatus = ValidateStatus.TO_BE_VALIDATED;
},
// 重置字段
resetField() {
const { name } = this;
if (name && this.form) {
const { resetType } = this.form;
if (resetType === 'initial') {
this.form.updateFormData(name, this.initialValue);
} else {
this.form.updateFormData(name, this.getEmptyValue());
}
}
this.clearValidate();
},
// 设置验证信息
setValidateMessage(validateMessage) {
this.errorList = validateMessage.filter(item => item.type === 'error');
this.successList = validateMessage.filter(item => item.type === 'warning');
this.verifyStatus = validateMessage.length > 0 ? ValidateStatus.FAIL : ValidateStatus.SUCCESS;
},
// 获取空值
getEmptyValue() {
const value = this.getValue();
if (Array.isArray(value)) {
return [];
}
if (typeof value === 'object' && value !== null) {
return {};
}
if (typeof value === 'number') {
return 0;
}
return '';
},
// 处理值变化
onValueChange(value) {
const { name } = this;
if (name && this.form) {
this.form.updateFormData(name, value);
// 触发change验证
this.validate(this.getFormData(), 'change', true);
}
},
// 处理失焦事件
onBlur() {
// 触发blur验证
this.validate(this.getFormData(), 'blur', true);
},
},
});
</script>
<style scoped>
@import './form-item.css';
</style>

View File

@@ -0,0 +1,198 @@
// 验证结果接口
interface ValidateResult {
result: boolean;
message?: string;
type?: string;
}
// 工具函数安全获取rule属性的实际值
function getRuleValue(field: any, key?: string): any {
if (field && typeof field === 'object' && 'type' in field) {
// 针对type字段过滤掉'error'和'warning'
if (key === 'type') {
const { value } = field;
if (value === 'error' || value === 'warning') {
return undefined;
}
return value;
}
return field.value;
}
// type字段只允许string
if (key === 'type') {
if (field === 'error' || field === 'warning') {
return undefined;
}
}
return field;
}
// 验证状态枚举
export const ValidateStatus = {
TO_BE_VALIDATED: 0,
SUCCESS: 1,
FAIL: 2,
};
// 执行单个验证规则
async function executeRule(value: any, rule: any): Promise<ValidateResult> {
const result: ValidateResult = {
result: true,
};
// 必填验证
const required = getRuleValue(rule.required);
if (required && (value === undefined || value === null || value === '' || value.length === 0)) {
result.result = false;
result.message = getRuleValue(rule.message) || '此字段为必填项';
result.type = 'error';
return result;
}
// 如果值为空且不是必填,则跳过其他验证
if (value === undefined || value === null || value === '' || value.length === 0) {
return result;
}
// 列表最小值验证
const min = getRuleValue(rule.min);
if (min !== undefined) {
const val = Array.isArray(value) ? value.length : Number(value);
if (val < Number(min)) {
result.result = false;
result.message = getRuleValue(rule.message) || `不能小于 ${min}`;
result.type = 'error';
return result;
}
}
// 列表最大值验证
const max = getRuleValue(rule.max);
if (max !== undefined) {
const val = Array.isArray(value) ? value.length : Number(value);
if (val > Number(max)) {
result.result = false;
result.message = getRuleValue(rule.message) || `不能大于 ${max}`;
result.type = 'error';
return result;
}
}
// 字符串最大长度验证
const maxLength = getRuleValue(rule.maxLength);
if (maxLength !== undefined) {
const len = String(value).length;
if (len > Number(maxLength)) {
result.result = false;
result.message = getRuleValue(rule.message) || `长度不能超过 ${maxLength}`;
result.type = 'error';
return result;
}
}
// 字符串最小长度验证
const minLength = getRuleValue(rule.minLength);
if (minLength !== undefined) {
const len = String(value).length;
if (len < Number(minLength)) {
result.result = false;
result.message = getRuleValue(rule.message) || `长度不能少于 ${minLength}`;
result.type = 'error';
return result;
}
}
// 固定长度验证
const len = getRuleValue(rule.len);
if (len !== undefined) {
const strValue = String(value);
if (strValue.length !== Number(len)) {
result.result = false;
result.message = getRuleValue(rule.message) || `长度必须为 ${len}`;
result.type = 'error';
return result;
}
}
// 正则验证
const pattern = getRuleValue(rule.pattern);
if (pattern) {
let regex;
if (pattern instanceof RegExp) {
regex = pattern;
} else if (typeof pattern === 'string') {
regex = new RegExp(pattern);
} else if (pattern && typeof pattern === 'object' && 'test' in pattern) {
regex = pattern;
} else {
result.result = false;
result.message = getRuleValue(rule.message) || '格式不正确';
result.type = 'error';
return result;
}
if (!regex.test(String(value))) {
result.result = false;
result.message = getRuleValue(rule.message) || '格式不正确';
result.type = 'error';
return result;
}
}
// 类型验证
const type = getRuleValue(rule.type, 'type');
if (typeof type === 'string') {
let isValid = true;
if (type === 'email') {
isValid = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(String(value));
if (!isValid) {
result.result = false;
result.message = getRuleValue(rule.message) || `${type} 格式不正确`;
result.type = 'error';
return result;
}
} else if (type === 'url') {
try {
const url = new URL(String(value));
isValid = !!url;
} catch (err) {
isValid = false;
}
if (!isValid) {
result.result = false;
result.message = getRuleValue(rule.message) || `${type} 格式不正确`;
result.type = 'error';
return result;
}
} else if (type === 'number') {
isValid = !Number.isNaN(Number(value));
if (!isValid) {
result.result = false;
result.message = getRuleValue(rule.message) || `${type} 格式不正确`;
result.type = 'error';
return result;
}
}
}
// 自定义验证
const validator = getRuleValue(rule.validator);
if (validator) {
const validateResult = await validator(value);
if (!validateResult) {
result.result = false;
result.message = getRuleValue(rule.message) || '验证失败';
result.type = 'error';
return result;
}
}
return result;
}
// 验证函数
export async function validate(value: any, rules: any[]): Promise<ValidateResult[]> {
const results: ValidateResult[] = [];
const promises = rules.map(rule => executeRule(value, rule));
const ruleResults = await Promise.all(promises);
results.push(...ruleResults);
return results;
}

View File

@@ -0,0 +1,64 @@
/* eslint-disable */
/**
* 该文件为脚本自动生成文件,请勿随意修改。如需修改请联系 PMC
* */
import type { TdFormItemProps } from './type';
export default {
/** 是否显示右侧箭头 */
arrow: Boolean,
/** 表单内容对齐方式,优先级高于 Form.contentAlign */
contentAlign: {
type: String,
validator(val: TdFormItemProps['contentAlign']): boolean {
if (!val) return true;
return ['left', 'right'].includes(val);
},
},
/** label 原生属性 */
for: {
type: String,
default: '',
},
/** 表单项说明内容 */
help: {
type: String,
},
/** 字段标签名称 */
label: {
type: String,
default: '' as TdFormItemProps['label'],
},
/** 表单字段标签对齐方式:左对齐、右对齐、顶部对齐。默认使用 Form 的对齐方式,优先级高于 Form.labelAlign */
labelAlign: {
type: String,
validator(val: TdFormItemProps['labelAlign']): boolean {
if (!val) return true;
return ['left', 'right', 'top'].includes(val);
},
},
/** 可以整体设置标签宽度,优先级高于 Form.labelWidth */
labelWidth: {
type: [String, Number],
},
/** 表单字段名称 */
name: {
type: String,
default: '',
},
/** 是否显示必填符号(*),优先级高于 Form.requiredMark */
requiredMark: {
type: Boolean,
default: undefined,
},
/** 表单字段校验规则 */
rules: {
type: Array,
},
/** 校验不通过时,是否显示错误提示信息,优先级高于 `Form.showErrorMessage` */
showErrorMessage: {
type: Boolean,
default: undefined,
},
};

View File

@@ -0,0 +1,7 @@
/* eslint-disable */
/**
* 该文件为脚本自动生成文件,请勿随意修改。如需修改请联系 PMC
* */
export type { TdFormItemProps} from '../form/type';