gaoluyang
3 天以前 92230c9a97dc9ce9df3313d11d26999c04bb6b26
src/uni_modules/uni-easyinput/components/uni-easyinput/uni-easyinput.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,679 @@
<template>
   <view
      class="uni-easyinput"
      :class="{ 'uni-easyinput-error': msg }"
      :style="boxStyle"
   >
      <view
         class="uni-easyinput__content"
         :class="inputContentClass"
         :style="inputContentStyle"
      >
         <slot name="prefixIcon">
            <uni-icons
               v-if="prefixIcon"
               class="content-clear-icon"
               :type="prefixIcon"
               color="#c0c4cc"
               @click="onClickIcon('prefix')"
               size="22"
            ></uni-icons>
         </slot>
         <textarea
            v-if="type === 'textarea'"
            class="uni-easyinput__content-textarea"
            :class="{ 'input-padding': inputBorder }"
            :name="name"
            :value="val"
            :placeholder="placeholder"
            :placeholderStyle="placeholderStyle"
            :disabled="disabled"
            placeholder-class="uni-easyinput__placeholder-class"
            :maxlength="inputMaxlength"
            :focus="focused"
            :autoHeight="autoHeight"
            @input="onInput"
            @blur="_Blur"
            @focus="_Focus"
            @confirm="onConfirm"
         ></textarea>
         <input
            v-else
            :type="type === 'password' ? 'text' : type"
            class="uni-easyinput__content-input"
            :style="inputStyle"
            :name="name"
            :value="val"
            :password="!showPassword && type === 'password'"
            :placeholder="placeholder"
            :placeholderStyle="placeholderStyle"
            placeholder-class="uni-easyinput__placeholder-class"
            :disabled="disabled"
            :maxlength="inputMaxlength"
            :focus="focused"
            :confirmType="confirmType"
            @focus="_Focus"
            @blur="_Blur"
            @input="onInput"
            @confirm="onConfirm"
         />
         <template v-if="type === 'password' && passwordIcon">
            <!-- å¼€å¯å¯†ç æ—¶æ˜¾ç¤ºå°çœ¼ç› -->
            <uni-icons
               v-if="isVal"
               class="content-clear-icon"
               :class="{ 'is-textarea-icon': type === 'textarea' }"
               :type="showPassword ? 'eye-slash-filled' : 'eye-filled'"
               :size="22"
               :color="focusShow ? primaryColor : '#c0c4cc'"
               @click="onEyes"
            >
            </uni-icons>
         </template>
         <template v-else-if="suffixIcon || $slots.suffixIcon">
            <slot name="suffixIcon">
               <uni-icons
                  v-if="suffixIcon"
                  class="content-clear-icon"
                  :type="suffixIcon"
                  color="#c0c4cc"
                  @click="onClickIcon('suffix')"
                  size="22"
               ></uni-icons>
            </slot>
         </template>
         <template v-else>
            <uni-icons
               v-if="clearable && isVal && !disabled && type !== 'textarea'"
               class="content-clear-icon"
               :class="{ 'is-textarea-icon': type === 'textarea' }"
               type="clear"
               :size="clearSize"
               :color="msg ? '#dd524d' : focusShow ? primaryColor : '#c0c4cc'"
               @click="onClear"
            ></uni-icons>
         </template>
         <slot name="right"></slot>
      </view>
   </view>
</template>
<script>
/**
 * Easyinput è¾“入框
 * @description æ­¤ç»„件可以实现表单的输入与校验,包括 "text" å’Œ "textarea" ç±»åž‹ã€‚
 * @tutorial https://ext.dcloud.net.cn/plugin?id=3455
 * @property {String}   value   è¾“入内容
 * @property {String }   type   è¾“入框的类型(默认text) password/text/textarea/..
 *    @value text         æ–‡æœ¬è¾“入键盘
 *    @value textarea   å¤šè¡Œæ–‡æœ¬è¾“入键盘
 *    @value password   å¯†ç è¾“入键盘
 *    @value number      æ•°å­—输入键盘,注意iOS上app-vue弹出的数字键盘并非9宫格方式
 *    @value idcard      èº«ä»½è¯è¾“入键盘,信、支付宝、百度、QQ小程序
 *    @value digit      å¸¦å°æ•°ç‚¹çš„æ•°å­—键盘   ï¼ŒApp的nvue页面、微信、支付宝、百度、头条、QQ小程序支持
 * @property {Boolean}   clearable   æ˜¯å¦æ˜¾ç¤ºå³ä¾§æ¸…空内容的图标控件,点击可清空输入框内容(默认true)
 * @property {Boolean}   autoHeight   æ˜¯å¦è‡ªåŠ¨å¢žé«˜è¾“å…¥åŒºåŸŸï¼Œtype为textarea时有效(默认true)
 * @property {String }   placeholder   è¾“入框的提示文字
 * @property {String }   placeholderStyle   placeholder的样式(内联样式,字符串),如"color: #ddd"
 * @property {Boolean}   focus   æ˜¯å¦è‡ªåŠ¨èŽ·å¾—ç„¦ç‚¹ï¼ˆé»˜è®¤false)
 * @property {Boolean}   disabled   æ˜¯å¦ç¦ç”¨ï¼ˆé»˜è®¤false)
 * @property {Number }   maxlength   æœ€å¤§è¾“入长度,设置为 -1 çš„æ—¶å€™ä¸é™åˆ¶æœ€å¤§é•¿åº¦ï¼ˆé»˜è®¤140)
 * @property {String }   confirmType   è®¾ç½®é”®ç›˜å³ä¸‹è§’按钮的文字,仅在type="text"时生效(默认done)
 * @property {Number }   clearSize   æ¸…除图标的大小,单位px(默认15)
 * @property {String}   prefixIcon   è¾“入框头部图标
 * @property {String}   suffixIcon   è¾“入框尾部图标
 * @property {String}   primaryColor   è®¾ç½®ä¸»é¢˜è‰²ï¼ˆé»˜è®¤#2979ff)
 * @property {Boolean}   trim   æ˜¯å¦è‡ªåŠ¨åŽ»é™¤ä¸¤ç«¯çš„ç©ºæ ¼
 * @value both   åŽ»é™¤ä¸¤ç«¯ç©ºæ ¼
 * @value left   åŽ»é™¤å·¦ä¾§ç©ºæ ¼
 * @value right   åŽ»é™¤å³ä¾§ç©ºæ ¼
 * @value start   åŽ»é™¤å·¦ä¾§ç©ºæ ¼
 * @value end      åŽ»é™¤å³ä¾§ç©ºæ ¼
 * @value all      åŽ»é™¤å…¨éƒ¨ç©ºæ ¼
 * @value none   ä¸åŽ»é™¤ç©ºæ ¼
 * @property {Boolean}   inputBorder   æ˜¯å¦æ˜¾ç¤ºinput输入框的边框(默认true)
 * @property {Boolean}   passwordIcon   type=password时是否显示小眼睛图标
 * @property {Object}   styles   è‡ªå®šä¹‰é¢œè‰²
 * @event {Function}   input   è¾“入框内容发生变化时触发
 * @event {Function}   focus   è¾“入框获得焦点时触发
 * @event {Function}   blur   è¾“入框失去焦点时触发
 * @event {Function}   confirm   ç‚¹å‡»å®ŒæˆæŒ‰é’®æ—¶è§¦å‘
 * @event {Function}   iconClick   ç‚¹å‡»å›¾æ ‡æ—¶è§¦å‘
 * @slot prefixIcon è¾“入框头部插槽
 * @slot suffixIcon è¾“入框尾部插槽
 * @example <uni-easyinput v-model="mobile"></uni-easyinput>
 */
function obj2strClass(obj) {
   let classess = "";
   for (let key in obj) {
      const val = obj[key];
      if (val) {
         classess += `${key} `;
      }
   }
   return classess;
}
function obj2strStyle(obj) {
   let style = "";
   for (let key in obj) {
      const val = obj[key];
      style += `${key}:${val};`;
   }
   return style;
}
export default {
   name: "uni-easyinput",
   emits: [
      "click",
      "iconClick",
      "update:modelValue",
      "input",
      "focus",
      "blur",
      "confirm",
      "clear",
      "eyes",
      "change",
   ],
   model: {
      prop: "modelValue",
      event: "update:modelValue",
   },
   options: {
      virtualHost: true,
   },
   inject: {
      form: {
         from: "uniForm",
         default: null,
      },
      formItem: {
         from: "uniFormItem",
         default: null,
      },
   },
   props: {
      name: String,
      value: [Number, String],
      modelValue: [Number, String],
      type: {
         type: String,
         default: "text",
      },
      clearable: {
         type: Boolean,
         default: true,
      },
      autoHeight: {
         type: Boolean,
         default: false,
      },
      placeholder: {
         type: String,
         default: " ",
      },
      placeholderStyle: String,
      focus: {
         type: Boolean,
         default: false,
      },
      disabled: {
         type: Boolean,
         default: false,
      },
      maxlength: {
         type: [Number, String],
         default: 140,
      },
      confirmType: {
         type: String,
         default: "done",
      },
      clearSize: {
         type: [Number, String],
         default: 24,
      },
      inputBorder: {
         type: Boolean,
         default: true,
      },
      prefixIcon: {
         type: String,
         default: "",
      },
      suffixIcon: {
         type: String,
         default: "",
      },
      trim: {
         type: [Boolean, String],
         default: true,
      },
      passwordIcon: {
         type: Boolean,
         default: true,
      },
      primaryColor: {
         type: String,
         default: "#2979ff",
      },
      styles: {
         type: Object,
         default() {
            return {
               color: "#333",
               disableColor: "#F7F6F6",
               borderColor: "#e5e5e5",
            };
         },
      },
      errorMessage: {
         type: [String, Boolean],
         default: "",
      },
   },
   data() {
      return {
         focused: false,
         val: "",
         showMsg: "",
         border: false,
         isFirstBorder: false,
         showClearIcon: false,
         showPassword: false,
         focusShow: false,
         localMsg: "",
      };
   },
   computed: {
      // è¾“入框内是否有值
      isVal() {
         const val = this.val;
         // fixed by mehaotian å¤„理值为0的情况,字符串0不在处理范围
         if (val || val === 0) {
            return true;
         }
         return false;
      },
      msg() {
         // console.log('computed', this.form, this.formItem);
         // if (this.form) {
         //    return this.errorMessage || this.formItem.errMsg;
         // }
         // TODO å¤„理头条 formItem ä¸­ errMsg ä¸æ›´æ–°çš„问题
         return this.localMsg || this.errorMessage;
      },
      // å› ä¸ºuniapp的input组件的maxlength组件必须要数值,这里转为数值,用户可以传入字符串数值
      inputMaxlength() {
         return Number(this.maxlength);
      },
      // å¤„理外层样式的style
      boxStyle() {
         return `color:${
            this.inputBorder && this.msg ? "#e43d33" : this.styles.color
         };`;
      },
      // input å†…容的类和样式处理
      inputContentClass() {
         return obj2strClass({
            "is-input-border": this.inputBorder,
            "is-input-error-border": this.inputBorder && this.msg,
            "is-textarea": this.type === "textarea",
            "is-disabled": this.disabled,
         });
      },
      inputContentStyle() {
         const focusColor = this.focusShow
            ? this.primaryColor
            : this.styles.borderColor;
         const borderColor = this.inputBorder && this.msg ? "#dd524d" : focusColor;
         return obj2strStyle({
            "border-color": borderColor || "#e5e5e5",
            "background-color": this.disabled
               ? this.styles.disableColor
               : this.styles.backgroundColor,
         });
      },
      // input右侧样式
      inputStyle() {
         const paddingRight =
            this.type === "password" || this.clearable || this.prefixIcon
               ? ""
               : "10px";
         return obj2strStyle({
            "padding-right": paddingRight,
            "padding-left": this.prefixIcon ? "" : "10px",
         });
      },
   },
   watch: {
      value(newVal) {
         this.val = newVal;
      },
      modelValue(newVal) {
         this.val = newVal;
      },
      focus(newVal) {
         this.$nextTick(() => {
            this.focused = this.focus;
            this.focusShow = this.focus;
         });
      },
   },
   created() {
      this.init();
      // TODO å¤„理头条vue3 computed ä¸ç›‘听 inject æ›´æ”¹çš„问题(formItem.errMsg)
      if (this.form && this.formItem) {
         this.$watch("formItem.errMsg", (newVal) => {
            this.localMsg = newVal;
         });
      }
   },
   mounted() {
      this.$nextTick(() => {
         this.focused = this.focus;
         this.focusShow = this.focus;
      });
   },
   methods: {
      /**
       * åˆå§‹åŒ–变量值
       */
      init() {
         if (this.value || this.value === 0) {
            this.val = this.value;
         } else if (this.modelValue || this.modelValue === 0) {
            this.val = this.modelValue;
         } else {
            this.val = null;
         }
      },
      /**
       * ç‚¹å‡»å›¾æ ‡æ—¶è§¦å‘
       * @param {Object} type
       */
      onClickIcon(type) {
         this.$emit("iconClick", type);
      },
      /**
       * æ˜¾ç¤ºéšè—å†…容,密码框时生效
       */
      onEyes() {
         this.showPassword = !this.showPassword;
         this.$emit("eyes", this.showPassword);
      },
      /**
       * è¾“入时触发
       * @param {Object} event
       */
      onInput(event) {
         let value = event.detail.value;
         // åˆ¤æ–­æ˜¯å¦åŽ»é™¤ç©ºæ ¼
         if (this.trim) {
            if (typeof this.trim === "boolean" && this.trim) {
               value = this.trimStr(value);
            }
            if (typeof this.trim === "string") {
               value = this.trimStr(value, this.trim);
            }
         }
         if (this.errMsg) this.errMsg = "";
         this.val = value;
         // TODO å…¼å®¹ vue2
         this.$emit("input", value);
         // TODO 兼容 vue3
         this.$emit("update:modelValue", value);
      },
      /**
       * å¤–部调用方法
       * èŽ·å–ç„¦ç‚¹æ—¶è§¦å‘
       * @param {Object} event
       */
      onFocus() {
         this.$nextTick(() => {
            this.focused = true;
         });
         this.$emit("focus", null);
      },
      _Focus(event) {
         this.focusShow = true;
         this.$emit("focus", event);
      },
      /**
       * å¤–部调用方法
       * å¤±åŽ»ç„¦ç‚¹æ—¶è§¦å‘
       * @param {Object} event
       */
      onBlur() {
         this.focused = false;
         this.$emit("focus", null);
      },
      _Blur(event) {
         let value = event.detail.value;
         this.focusShow = false;
         this.$emit("blur", event);
         // æ ¹æ®ç±»åž‹è¿”回值,在event中获取的值理论上讲都是string
         this.$emit("change", this.val);
         // å¤±åŽ»ç„¦ç‚¹æ—¶å‚ä¸Žè¡¨å•æ ¡éªŒ
         if (this.form && this.formItem) {
            const { validateTrigger } = this.form;
            if (validateTrigger === "blur") {
               this.formItem.onFieldChange();
            }
         }
      },
      /**
       * æŒ‰ä¸‹é”®ç›˜çš„发送键
       * @param {Object} e
       */
      onConfirm(e) {
         this.$emit("confirm", this.val);
         this.$emit("change", this.val);
      },
      /**
       * æ¸…理内容
       * @param {Object} event
       */
      onClear(event) {
         this.val = "";
         // TODO å…¼å®¹ vue2
         this.$emit("input", "");
         // TODO å…¼å®¹ vue2
         // TODO 兼容 vue3
         this.$emit("update:modelValue", "");
         // ç‚¹å‡»å‰å·è§¦å‘
         this.$emit("clear");
      },
      /**
       * åŽ»é™¤ç©ºæ ¼
       */
      trimStr(str, pos = "both") {
         if (pos === "both") {
            return str.trim();
         } else if (pos === "left") {
            return str.trimLeft();
         } else if (pos === "right") {
            return str.trimRight();
         } else if (pos === "start") {
            return str.trimStart();
         } else if (pos === "end") {
            return str.trimEnd();
         } else if (pos === "all") {
            return str.replace(/\s+/g, "");
         } else if (pos === "none") {
            return str;
         }
         return str;
      },
   },
};
</script>
<style lang="scss">
$uni-error: #e43d33;
$uni-border-1: #dcdfe6 !default;
.uni-easyinput {
   /* #ifndef APP-NVUE */
   width: 100%;
   /* #endif */
   flex: 1;
   position: relative;
   text-align: left;
   color: #333;
   font-size: 14px;
}
.uni-easyinput__content {
   flex: 1;
   /* #ifndef APP-NVUE */
   width: 100%;
   display: flex;
   box-sizing: border-box;
   // min-height: 36px;
   /* #endif */
   flex-direction: row;
   align-items: center;
   // å¤„理border动画刚开始显示黑色的问题
   border-color: #fff;
   transition-property: border-color;
   transition-duration: 0.3s;
}
.uni-easyinput__content-input {
   /* #ifndef APP-NVUE */
   width: auto;
   /* #endif */
   position: relative;
   overflow: hidden;
   flex: 1;
   line-height: 1;
   font-size: 14px;
   height: 35px;
   // min-height: 36px;
}
.uni-easyinput__placeholder-class {
   color: #999;
   font-size: 12px;
   // font-weight: 200;
}
.is-textarea {
   align-items: flex-start;
}
.is-textarea-icon {
   margin-top: 5px;
}
.uni-easyinput__content-textarea {
   position: relative;
   overflow: hidden;
   flex: 1;
   line-height: 1.5;
   font-size: 14px;
   margin: 6px;
   margin-left: 0;
   height: 80px;
   min-height: 80px;
   /* #ifndef APP-NVUE */
   min-height: 80px;
   width: auto;
   /* #endif */
}
.input-padding {
   padding-left: 10px;
}
.content-clear-icon {
   padding: 0 5px;
}
.label-icon {
   margin-right: 5px;
   margin-top: -1px;
}
// æ˜¾ç¤ºè¾¹æ¡†
.is-input-border {
   /* #ifndef APP-NVUE */
   display: flex;
   box-sizing: border-box;
   /* #endif */
   flex-direction: row;
   align-items: center;
   border: 1px solid $uni-border-1;
   border-radius: 4px;
   /* #ifdef MP-ALIPAY */
   overflow: hidden;
   /* #endif */
}
.uni-error-message {
   position: absolute;
   bottom: -17px;
   left: 0;
   line-height: 12px;
   color: $uni-error;
   font-size: 12px;
   text-align: left;
}
.uni-error-msg--boeder {
   position: relative;
   bottom: 0;
   line-height: 22px;
}
.is-input-error-border {
   border-color: $uni-error;
   .uni-easyinput__placeholder-class {
      color: mix(#fff, $uni-error, 50%);
   }
}
.uni-easyinput--border {
   margin-bottom: 0;
   padding: 10px 15px;
   // padding-bottom: 0;
   border-top: 1px #eee solid;
}
.uni-easyinput-error {
   padding-bottom: 0;
}
.is-first-border {
   /* #ifndef APP-NVUE */
   border: none;
   /* #endif */
   /* #ifdef APP-NVUE */
   border-width: 0;
   /* #endif */
}
.is-disabled {
   background-color: #f7f6f6;
   color: #d5d5d5;
   .uni-easyinput__placeholder-class {
      color: #d5d5d5;
      font-size: 12px;
   }
}
</style>