2026/1/28 23:06:59
网站建设
项目流程
三好街 网站建设,微商城分销平台免费,四川省建设工程造价信息网站,做网站价格多少一、设计核心目标
功能完备性#xff1a;覆盖日常/复杂输入场景#xff0c;支持多类型、校验、格式化等高频需求#xff1b;可扩展性#xff1a;预留插槽、配置项#xff0c;支持业务定制化#xff08;如前缀图标、后缀操作区#xff09;#xff1b;性能优化#xff1…一、设计核心目标功能完备性覆盖日常/复杂输入场景支持多类型、校验、格式化等高频需求可扩展性预留插槽、配置项支持业务定制化如前缀图标、后缀操作区性能优化减少不必要渲染兼容大文本输入、高频输入场景易用性API 设计简洁直观TS 类型约束完善支持双向绑定、事件透传规范性统一样式、错误反馈、无障碍访问符合前端工程化最佳实践。二、组件整体架构设计1. 目录结构工程化拆分src/components/Input/ ├─ index.ts // 组件导出全局注册/局部引入入口 ├─ Input.vue // 核心组件模板逻辑 ├─ types.ts // TS 类型定义Props/事件/枚举 ├─ hooks/ // 组合式函数拆分逻辑解耦 │ ├─ useInputValue.ts // 输入值管理双向绑定、格式化 │ ├─ useInputValidate.ts // 输入校验逻辑 │ └─ useInputEvent.ts // 事件处理防抖、透传 └─ style/ // 样式文件支持主题定制 ├─ input.scss // 基础样式 └─ input-theme.scss // 主题变量颜色、尺寸、圆角三、核心设计细节1. TS 类型定义types.ts强类型约束先明确组件输入/输出类型避免类型混乱支持 IDE 智能提示// 输入框类型枚举覆盖主流场景exportenumInputType{TEXTtext,NUMBERnumber,PASSWORDpassword,EMAILemail,PHONEtel,SEARCHsearch,TEXTAREAtextarea,DATEdate,TIMEtime}// 输入框尺寸枚举exportenumInputSize{SMALLsmall,MEDIUMmedium,LARGElarge}// 校验规则类型支持自定义校验、正则、内置规则exporttypeValidateRule{// 内置校验类型可选优先于正则type?:required|email|phone|number|url;// 自定义正则校验regExp?:RegExp;// 校验失败提示文案message:string;// 触发校验时机input/blur/changetrigger?:input|blur|change;// 自定义校验函数返回 boolean 或 Promiseboolean优先级最高validator?:(value:string|number)boolean|Promiseboolean;};// Props 类型定义exportinterfaceInputProps{// 绑定值双向绑定核心modelValue:string|number|undefined|null;// 输入框类型type?:InputType;// 尺寸size?:InputSize;// 占位符placeholder?:string;// 是否禁用disabled?:boolean;// 是否只读readonly?:boolean;// 是否必填配合表单校验仅UI提示required?:boolean;// 最大输入长度maxLength?:number;// 最小输入长度minLength?:number;// 数值类型最小值min?:number;// 数值类型最大值max?:number;// 步长number/date/time类型生效step?:number|string;// 输入值格式化函数输入后立即格式化如手机号加空格formatter?:(value:string|number)string|number;// 输入值反格式化函数提交时还原如手机号去空格parser?:(value:string|number)string|number;// 校验规则数组rules?:ValidateRule[];// 防抖时长ms高频输入场景优化debounceTime?:number;// 文本域行数仅typetextarea生效rows?:number;// 文本域是否可resizeresize?:none|both|horizontal|vertical;// 前缀图标图标组件名/自定义内容预留扩展prefixIcon?:string|JSX.Element;// 后缀图标同上suffixIcon?:string|JSX.Element;// 自定义类名支持外部样式覆盖customClass?:string;// 无障碍访问标签a11yariaLabel?:string;}// 组件事件类型exportinterfaceInputEmits{// 双向绑定更新事件符合Vue3 v-model规范(e:update:modelValue,value:string|number|undefined|null):void;// 输入事件防抖后触发避免高频回调(e:input,value:string|number):void;// 失焦事件(e:blur,event:FocusEvent):void;// 聚焦事件(e:focus,event:FocusEvent):void;// 回车事件(e:enter,value:string|number):void;// 校验失败事件返回失败信息(e:validate-error,message:string):void;// 校验成功事件(e:validate-success):void;// 原生change事件透传(e:change,event:Event):void;}2. 组合式函数拆分逻辑解耦复用性拉满1useInputValue.ts输入值管理核心逻辑负责双向绑定、格式化/反格式化、值同步隔离输入值核心逻辑import{ref,watch,toRefs,Ref}fromvue;importtype{InputProps,InputEmits}from../types;exportconstuseInputValue(props:InputProps,emit:InputEmits){const{modelValue,formatter,parser}toRefs(props);// 内部输入值避免直接修改props符合单向数据流constinnerValuerefstring|number|null|undefined(modelValue.value)asRefstring|number;// 监听外部modelValue变化同步到内部值支持外部强制更新watch(modelValue,(newVal){if(newVal!innerValue.value){innerValue.valuenewValasstring|number;}},{immediate:true});// 输入值变化处理格式化同步外部consthandleValueChange(val:string|number){// 1. 执行格式化如手机号加空格letformattedValformatter?formatter(val):val;// 2. 同步内部值innerValue.valueformattedVal;// 3. 同步外部v-modelemit(update:modelValue,formattedVal);// 4. 返回反格式化后的值供提交使用可选returnparser?parser(formattedVal):formattedVal;};// 获取提交用的值反格式化后constgetSubmitValue(){returnparser?parser(innerValue.value):innerValue.value;};return{innerValue,handleValueChange,getSubmitValue};};2useInputValidate.ts输入校验独立复用支持同步/异步校验、多触发时机可复用至其他表单组件import{ref,watch,toRefs,Ref}fromvue;importtype{InputProps,InputEmits,ValidateRule}from../types;// 内置校验规则覆盖常用场景可扩展constbuiltInValidators{required:(val:string|number)!!valval.toString().trim()!,email:(val:string)/^[\w-](\.[\w-])*([\w-]\.)[a-zA-Z]{2,7}$/.test(val),phone:(val:string)/^1[3-9]\d{9}$/.test(val),number:(val:string|number)!isNaN(Number(val)),url:(val:string)/^(https?:\/\/)?([\da-z.-])\.([a-z.]{2,6})([\/\w.-]*)*\/?$/.test(val)};exportconstuseInputValidate(innerValue:Refstring|number,props:InputProps,emit:InputEmits){const{rules,type}toRefs(props);// 校验失败提示文案consterrorMessageref();// 是否校验中处理异步校验loading状态constisValidatingref(false);// 单个规则校验支持同步/异步constvalidateSingleRuleasync(rule:ValidateRule,value:string|number){letisValidfalse;const{type:ruleType,regExp,validator,message}rule;// 1. 优先执行自定义校验函数if(validator){isValidawaitvalidator(value);}// 2. 执行内置规则校验elseif(ruleTypebuiltInValidators[ruleTypeaskeyoftypeofbuiltInValidators]){isValidbuiltInValidators[ruleTypeaskeyoftypeofbuiltInValidators](value);}// 3. 执行自定义正则校验elseif(regExp){isValidregExp.test(value.toString());}if(!isValid){errorMessage.valuemessage;emit(validate-error,message);returnfalse;}returntrue;};// 全量规则校验返回校验结果constvalidateasync(){if(!rules.value||rules.value.length0)returntrue;isValidating.valuetrue;constvalueinnerValue.value;letallValidtrue;// 遍历所有规则执行校验for(construleofrules.value){constvalidawaitvalidateSingleRule(rule,value);if(!valid){allValidfalse;break;// 只要有一条失败终止校验}}// 全量校验成功if(allValid){errorMessage.value;emit(validate-success);}isValidating.valuefalse;returnallValid;};// 清除校验状态constclearValidate(){errorMessage.value;};// 监听输入值变化触发对应时机的校验watch(innerValue,async(newVal){if(!rules.value||rules.value.length0)return;// 筛选出triggerinput的规则constinputRulesrules.value.filter(rulerule.triggerinput);if(inputRules.length0){awaitPromise.all(inputRules.map(rulevalidateSingleRule(rule,newVal)));}});// 失焦校验consthandleBlurValidateasync(){if(!rules.value||rules.value.length0)return;constblurRulesrules.value.filter(rulerule.triggerblur||!rule.trigger);if(blurRules.length0){awaitPromise.all(blurRules.map(rulevalidateSingleRule(rule,innerValue.value)));}};return{errorMessage,isValidating,validate,clearValidate,handleBlurValidate};};3useInputEvent.ts事件处理防抖优化处理高频输入、事件透传避免组件逻辑臃肿import{ref,toRefs}fromvue;importtype{InputProps,InputEmits}from../types;exportconstuseInputEvent(innerValue:string|number,props:InputProps,emit:InputEmits,handleValueChange:(val:string|number)void){const{debounceTime,type}toRefs(props);// 防抖定时器letdebounceTimer:NodeJS.Timeout|nullnull;// 原生输入事件防抖处理consthandleInput(e:Event){consttargete.targetasHTMLInputElement|HTMLTextAreaElement;constvaltarget.value;// 1. 同步值格式化handleValueChange(val);// 2. 防抖触发input事件if(debounceTimer)clearTimeout(debounceTimer);debounceTimersetTimeout((){emit(input,val);},debounceTime.value||300);};// 失焦事件透传校验consthandleBlur(e:FocusEvent){emit(blur,e);};// 聚焦事件透传consthandleFocus(e:FocusEvent){emit(focus,e);};// 回车事件consthandleEnter(e:KeyboardEvent){if(e.keyEnter){emit(enter,innerValue);}};// 原生change事件透传consthandleChange(e:Event){emit(change,e);};// 清除防抖定时器组件卸载时调用避免内存泄漏constclearDebounceTimer(){if(debounceTimer){clearTimeout(debounceTimer);debounceTimernull;}};return{handleInput,handleBlur,handleFocus,handleEnter,handleChange,clearDebounceTimer};};3. 核心组件模板Input.vueUI逻辑整合模板结构清晰支持插槽扩展适配多类型输入框template div classinput-container :class[ input-size-${size}, { input-disabled: disabled, input-readonly: readonly, input-error: errorMessage, input-required: required }, customClass ] !-- 前缀区域图标插槽 -- div classinput-prefix v-ifprefixIcon || $slots.prefix icon v-iftypeof prefixIcon string :nameprefixIcon classinput-icon / slot nameprefix v-else-if$slots.prefix/slot /div !-- 输入框主体区分普通输入框/文本域 -- template v-iftype ! InputType.TEXTAREA input :typetype :valueinnerValue ?? :placeholderplaceholder :disableddisabled :readonlyreadonly :maxlengthmaxLength :minmin :maxmax :stepstep :aria-labelariaLabel :aria-invalid!!errorMessage :aria-describedbyerrorMessage ? input-error : inputhandleInput blurhandleBlur; handleBlurValidate focushandleFocus keydown.enterhandleEnter changehandleChange classinput-core / /template template v-else textarea :valueinnerValue ?? :placeholderplaceholder :disableddisabled :readonlyreadonly :maxlengthmaxLength :rowsrows || 3 :resizeresize || none :aria-labelariaLabel :aria-invalid!!errorMessage :aria-describedbyerrorMessage ? input-error : inputhandleInput blurhandleBlur; handleBlurValidate focushandleFocus keydown.enterhandleEnter changehandleChange classinput-core input-textarea /textarea /template !-- 后缀区域图标插槽校验loading -- div classinput-suffix v-ifsuffixIcon || $slots.suffix || isValidating loading v-ifisValidating classinput-loading sizesmall / icon v-else-iftypeof suffixIcon string :namesuffixIcon classinput-icon / slot namesuffix v-else-if$slots.suffix/slot /div /div !-- 错误提示 -- p classinput-error-message idinput-error v-iferrorMessage {{ errorMessage }} /p /template script setup langts import { onUnmounted, toRefs } from vue; import { InputType, InputSize } from ./types; import { useInputValue } from ./hooks/useInputValue; import { useInputValidate } from ./hooks/useInputValidate; import { useInputEvent } from ./hooks/useInputEvent; // 引入通用组件图标、加载态项目自有组件库即可 import Icon from ../Icon/Icon.vue; import Loading from ../Loading/Loading.vue; // 1. 接收Props定义Emits const props definePropsInputProps(); const emit defineEmitsInputEmits(); const { type, size InputSize.MEDIUM } toRefs(props); // 2. 整合组合式函数逻辑 const { innerValue, handleValueChange, getSubmitValue } useInputValue(props, emit); const { errorMessage, isValidating, validate, clearValidate, handleBlurValidate } useInputValidate(innerValue, props, emit); const { handleInput, handleBlur, handleFocus, handleEnter, handleChange, clearDebounceTimer } useInputEvent(innerValue.value, props, emit, handleValueChange); // 3. 组件卸载清理避免内存泄漏 onUnmounted(() { clearDebounceTimer(); }); // 4. 暴露组件方法外部可调用校验、清除校验等 defineExpose({ validate, clearValidate, getSubmitValue, focus: () { const input document.querySelector(.input-core) as HTMLInputElement | HTMLTextAreaElement; input?.focus(); }, blur: () { const input document.querySelector(.input-core) as HTMLInputElement | HTMLTextAreaElement; input?.blur(); } }); // 5. 导出枚举模板中使用 defineOptions({ name: Input }); /script4. 样式设计input.scss支持定制响应式采用 BEM 命名规范支持尺寸切换、主题定制兼容无障碍访问// 基础容器样式 .input-container { position: relative; display: inline-flex; align-items: center; width: 100%; border-radius: var(--input-border-radius, 4px); border: 1px solid var(--input-border-color, #e5e7eb); background-color: var(--input-bg-color, #fff); transition: all 0.2s ease; box-sizing: border-box; // 禁用状态 .input-disabled { background-color: var(--input-disabled-bg, #f9fafb); border-color: var(--input-disabled-border, #e5e7eb); cursor: not-allowed; } // 只读状态 .input-readonly { background-color: var(--input-readonly-bg, #f9fafb); } // 错误状态 .input-error { border-color: var(--input-error-border, #ef4444); :focus-within { box-shadow: 0 0 0 2px rgba(239, 68, 68, 0.2); } } // 聚焦状态子元素聚焦时容器生效 :focus-within { border-color: var(--input-focus-border, #3b82f6); box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.2); outline: none; } // 尺寸变体small/medium/large .input-size-small { height: var(--input-small-height, 32px); padding: 0 8px; font-size: var(--input-small-font-size, 12px); } .input-size-medium { height: var(--input-medium-height, 40px); padding: 0 12px; font-size: var(--input-medium-font-size, 14px); } .input-size-large { height: var(--input-large-height, 48px); padding: 0 16px; font-size: var(--input-large-font-size, 16px); } } // 前缀区域 .input-prefix { display: flex; align-items: center; margin-right: 8px; color: var(--input-icon-color, #6b7280); } // 后缀区域 .input-suffix { display: flex; align-items: center; margin-left: 8px; color: var(--input-icon-color, #6b7280); } // 图标样式 .input-icon { width: 16px; height: 16px; } // 加载态样式 .input-loading { width: 14px; height: 14px; } // 核心输入框样式 .input-core { flex: 1; width: 100%; height: 100%; border: none; outline: none; background: transparent; color: var(--input-text-color, #111827); font-family: inherit; box-sizing: border-box; ::placeholder { color: var(--input-placeholder-color, #9ca3af); } :disabled { color: var(--input-disabled-text, #9ca3af); cursor: not-allowed; } :readonly { color: var(--input-readonly-text, #6b7280); cursor: default; } } // 文本域样式 .input-textarea { resize: var(--input-textarea-resize, none); min-height: inherit; padding: 8px 0; line-height: 1.5; } // 错误提示样式 .input-error-message { margin: 4px 0 0 0; font-size: 12px; color: var(--input-error-text, #ef4444); line-height: 1.4; }5. 组件导出index.ts支持全局/局部引入import{App}fromvue;importInputfrom./Input.vue;importtype{InputProps,InputType,InputSize}from./types;// 全局注册方法exportconstinstall(app:App){app.component(Input,Input);};// 导出组件及类型支持局部引入TS类型提示export{Input,InputType,InputSize};exporttype{InputProps};exportdefaultInput;四、组件使用示例覆盖常见场景1. 基础使用双向绑定template Input v-modelusername placeholder请输入用户名 / /template script setup langts import { ref } from vue; import { Input } from /components/Input; const username ref(); /script2. 带校验的输入框手机号校验template Input v-modelphone typephone placeholder请输入手机号 :rules[ { type: required, message: 手机号不能为空 }, { type: phone, message: 请输入正确的手机号 } ] / /template script setup langts import { ref } from vue; import { Input, InputType } from /components/Input; const phone ref(); /script3. 带格式化的输入框手机号加空格template Input v-modelphone typephone placeholder请输入手机号 :formatterformatPhone :parserparsePhone / /template script setup langts import { ref } from vue; import { Input } from /components/Input; const phone ref(); // 格式化13800138000 → 138 0013 8000 const formatPhone (val: string) { return val.replace(/\D/g, ).replace(/(\d{3})(\d{4})(\d{4})/, $1 $2 $3); }; // 反格式化138 0013 8000 → 13800138000提交用 const parsePhone (val: string) { return val.replace(/\s/g, ); }; /script4. 带前缀图标的输入框template Input v-modelpassword typepassword placeholder请输入密码 template #prefix Icon namelock / /template /Input /template script setup langts import { ref } from vue; import { Input } from /components/Input; import Icon from /components/Icon; /script5. 文本域输入框template Input v-modeldesc typetextarea placeholder请输入描述 :rows5 resizevertical :maxLength200 / /template script setup langts import { ref } from vue; import { Input, InputType } from /components/Input; const desc ref(); /script五、进阶优化与扩展点1. 性能优化输入防抖默认300ms防抖高频输入如搜索框无卡顿减少渲染组合式函数拆分逻辑避免组件内冗余状态innerValue仅同步必要更新大文本优化文本域支持maxLength限制避免输入过多导致性能下降组件卸载清理清除防抖定时器避免内存泄漏。2. 功能扩展支持密码可见切换通过后缀插槽添加「眼睛图标」点击切换typetext/password支持标签输入扩展typetag实现多标签输入如关键词标签支持远程搜索整合el-select逻辑实现输入联想需扩展remote/remoteMethodProps主题定制通过CSS变量覆盖默认样式支持多主题切换如暗黑模式。3. 质量保障无障碍访问添加aria-label/aria-invalid等属性支持屏幕阅读器单元测试用Vitest测试核心逻辑值同步、校验、格式化覆盖率≥80%兼容性适配Chrome/Firefox/Safari/Edge主流浏览器支持移动端适配。六、设计总结该 Input 组件核心遵循「高内聚、低耦合」原则通过组合式函数拆分逻辑支持多场景复用同时兼顾性能、易用性与扩展性功能上覆盖输入、校验、格式化、事件透传等全场景需求架构上逻辑拆分清晰可独立复用校验、事件处理等能力工程化上TS 强类型约束样式支持定制符合前端最佳实践扩展性上预留插槽、配置项可快速扩展标签、远程搜索等高级功能。日常开发中可根据业务需求基于该架构补充定制化功能同时保持组件核心逻辑的稳定性与复用性。