一、 最终效果
二、实现了功能
1、支持输入正整数---设置`specifyType=integer`
2、支持输入数字(含小数点)---设置`specifyType=decimal`,可设置`decimalLimit`来调整小数点位数
3、支持输入手机号--设置`specifyType=phone`
4、支持输入身份证号---设置`specifyType=idCard`
4、支持失焦后校验错误提示
5、支持title自定义或者slotLabel插槽
6、支持inputType输入框类型可选值:input/select
7、支持自带下拉选择
三、TInput 参数配置
1、代码示例:
<TInput v-model="value" titlt="标题" />
2. 配置参数(Attributes)继承wu-input及wu-popup的所有参数事件
参数 | 说明 | 类型 | 默认值 |
---|---|---|---|
v-model | input绑定值 | String /number | - |
v-model:select-value | inputType:select 的绑定值 | String /number | - |
title | label标题 | String | - |
inputType | 输入框类型可选值:input/select | String | input |
required | 是否必填红点标识 | boolean | false |
decimalLimit | 小数点后保留几位 | number | 2 |
specifyType | 指定输入类型"text"/“decimal”/“phone” /“integer” / “idCard” | String | text |
showThousands | 是否显示千分位 | boolean | false |
isShowErrorTip | 输入框失焦是否提示错误信息 | boolean | false |
isLink | 是否展示右侧箭头(inputType=select自动展示) | boolean | false |
iconArrow | 右侧箭头图标 | String | ‘arrow-right’ |
iconArrowColor | 右侧箭头颜色 | String | #969799 |
isColon | label标题是否显示冒号 | boolean | true |
isShowBorder | 是否显示下边框 | boolean | false |
list | inputType=select自带下拉数据源 | array | [] |
customKey | 列表项自定义key | String | key |
customLabel | 列表项自定义label | string | label |
popupTitle | 自带下拉框标题显示 | string | - |
isPopupTitleBorder | 自带下拉框标题与列表边框显示 | boolean | false |
3. Events
事件名 | 说明 | 返回值 |
---|---|---|
clickInput | list没有设置时点击input触发 | - |
4. Slot
事件名 | 说明 |
---|---|
slotInput | 右侧input框插槽 |
slotLabel | 左侧label插槽 |
prefix | input前缀插槽 |
suffix | input后缀插槽 |
四、源码
<template><view class="t_input"><view class="list-call" :class="{ is_border: isShowBorder }"><view class="t_input_title" v-if="isShow('slotLabel')"><slot name="slotLabel" /></view><view class="t_input_title" v-else><view class="t_input_required" v-if="required">*</view>{{ title }}<text v-if="isColon">: </text></view><view class="t_input_value_slot" v-if="isShow('slotInput')"><slot name="slotInput" /></view><view class="t_input_value" v-else @click="openPopup"><wu-input v-bind="fieldAttrs" v-model="selectValue" @blur="handleBlur" v-if="inputType === 'input'"><template #prefix><slot name="prefix" /></template><template #suffix><slot name="suffix" /></template></wu-input><wu-input v-bind="fieldAttrs" v-model="finallySelectLabel" v-else><template #prefix><slot name="prefix" /></template><template #suffix><slot name="suffix" /></template></wu-input><wu-icon :name="iconArrow" :color="iconArrowColor" v-if="isLink || inputType === 'select'"></wu-icon></view></view><wu-popup ref="popup" v-bind="{ closeable: true, mode: 'bottom', round: 15, ...attrs }"><view class="t_input_popup-list"><view class="t_input_popup-title" :class="{ 't_input_popup-title-border': isPopupTitleBorder }">{{ popupTitle }}</view><viewclass="t_input_popup-item"v-for="(item, index) in list":key="index"@click="selectItem(item)":class="{ is_active: selectLabel === item[props.customLabel] }">{{ item[props.customLabel] }}</view></view></wu-popup></view>
</template><script lang="ts" setup>
import { ref, computed, useAttrs, useSlots } from "vue";
// 定义 ListItem 接口
interface ListItem {[key: string]: string | number; // 支持任意字符串键customKey: string | number;customLabel: string;
}
interface TInputProps {title?: string;inputType: "input" | "select";decimalLimit?: number; // 小数点后保留几位specifyType?: "text" | "decimal" | "phone" | "integer" | "idCard"; // 指定输入类型showThousands?: boolean; // 是否显示千分位isShowErrorTip?: boolean; // 是否显示错误提示required?: boolean; // 是否必填isLink?: boolean; // 是否展示右侧箭头iconArrow?: string; // 右侧箭头图标iconArrowColor?: string; // 右侧箭头颜色isColon?: boolean; // 是否显示冒号isShowBorder?: boolean; // 是否显示下边框list?: ListItem[]; // 列表项customKey?: string; // 列表项的keycustomLabel?: string; // 列表项的labelpopupTitle?: string; // 弹出框标题isPopupTitleBorder?: boolean; // 是否显示弹出框title的边框modelValue?: string | number; // type:input 的绑定值selectValue?: string | number; // type:select 的绑定值
}
const props = withDefaults(defineProps<TInputProps>(), {title: "",inputType: "input",specifyType: "text",showThousands: false,isShowErrorTip: false,decimalLimit: 2,required: false,list: () => [] as ListItem[],customKey: "key",customLabel: "label",popupTitle: "",isPopupTitleBorder: false,isLink: false,isShowBorder: true,isColon: true,iconArrow: "arrow-right",iconArrowColor: "#969799",modelValue: "",selectValue: ""
});const emit = defineEmits<{(event: "update:modelValue", value: string | number): void;(event: "update:selectValue", value: string | number): void;(event: "clickInput"): void;
}>();
const attrs = useAttrs();
const popup = ref<null | any>(null);
const slots = useSlots();const isShow = (name: string) => {return Object.keys(slots).includes(name);
};const selectLabel = ref<string | number>("");const selectValue = computed({get: () => props.modelValue,set: (val: string | number) => {emit("update:modelValue", val);}
});
const selectModelLabel = computed({get: () => props.selectValue,set: (val: string | number) => {emit("update:selectValue", val);}
});
const finallySelectLabel = computed(() => {if (props?.list?.length > 0) {return selectLabel.value;} else {return selectModelLabel.value;}
});
const handleBlur = () => {let formattedValue = selectValue.value;const formatValue = (value: any, formatter: (val: any) => any) => {if (formatter) {return formatter(value);}return value;};switch (props.specifyType) {case "decimal": // 小数点后保留几位formattedValue = formatValue(Number(selectValue.value), value =>formatDecimal(value, props.decimalLimit));break;case "phone": // 手机号码formattedValue = formatValue(selectValue.value.toString(), validatePhone);break;case "integer": // 整数formattedValue = formatValue(selectValue.value.toString(), validateInteger);break;case "idCard": // 身份证号码formattedValue = formatValue(selectValue.value.toString(), validateIdCard);break;default: // 默认处理formattedValue = selectValue.value;}selectValue.value = formattedValue;
};
// 手机号码校验
const validatePhone = (value: string) => {const phoneReg = /^1[3456789]\d{9}$/;if (phoneReg.test(value)) {return value;} else {props.isShowErrorTip &&uni.showToast({title: "请输入正确的手机号码",icon: "none"});return "";}
};
// 身份证号码校验
const validateIdCard = (value: string) => {const idCardReg = /(^\d{15}$)|(^\d{18}$)|(^\d{17}(\d|X|x)$)/;if (idCardReg.test(value)) {return value;} else {props.isShowErrorTip &&uni.showToast({title: "请输入正确的身份证号码",icon: "none"});return "";}
};
// 整数校验
const validateInteger = (value: string) => {const integerReg = /^\d+$/;if (integerReg.test(value)) {return value;} else {props.isShowErrorTip &&uni.showToast({title: "请输入正确的整数",icon: "none"});return "";}
};
// 小数转换
const formatDecimal = (value: number, decimalLimit: number) => {if (!value) {props.isShowErrorTip &&uni.showToast({title: "请输入正确的数字",icon: "none"});return "";}// 格式化千分号if (props.showThousands) {const val = value.toFixed(decimalLimit).toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");return val;} else {return value.toFixed(decimalLimit);}
};
// 计算属性 fieldAttrs
const fieldAttrs = computed(() => ({border: "none",placeholder: props.inputType === "select" ? `请选择${props.title}` : `请输入${props.title}`,readonly: props.inputType === "select",type: "text",inputAlign: "right",clearable: true,...attrs
}));// 选择列表项
const selectItem = (item: ListItem) => {if (props.customLabel && item[props.customLabel]) {selectLabel.value = item[props.customLabel];emit("update:modelValue", item[props.customKey]);popup.value?.close();} else {console.error("Invalid customLabel or item:", props.customLabel, item);}
};// 打开弹窗
const openPopup = () => {if (props.inputType === "select" && props.list.length > 0) {popup.value?.open();} else {emit("clickInput");}
};
</script><style lang="scss" scoped>
.t_input {.list-call {display: flex;height: 54px;align-items: center;padding: 0 10px;&.is_border {border-bottom: 1px solid #eee;}.t_input_title {display: flex;height: inherit;align-items: center;.t_input_required {color: red;}}.t_input_value {flex: 1;display: flex;height: inherit;align-items: center;justify-content: flex-end;}.t_input_value_slot {display: flex;height: inherit;align-items: center;justify-content: flex-end;}}.t_input_popup-list {.t_input_popup-title {display: flex;align-items: center;justify-content: center;font-size: 16px;color: #101010;height: 44px;font-weight: bold;&.t_input_popup-title-border {border-bottom: 1px solid #eee;}}.t_input_popup-item {text-align: center;line-height: 45px;&.is_active {background-color: #f0f0f0;}}}
}
</style>
相关文章
基于ElementUi再次封装基础组件文档
Vue3+Vite+Ts+Pinia+Qiankun后台管理系统
vue3+ts基于Element-plus再次封装基础组件文档