vue2 封装组件使用 v-mode【el-radio,el-input】

v-model 在组件上使用,只能更改一个值。 

sycn [singk]    

  1、在组件上使用 v-model ,父组件 v-model ,子组件接收value , $emit('value',xxxx)
   2、sync [singk].  父组件 :message属性值.sync ='xxxxx' ,,子组件。 $emit('update:message属性值',xxxxx)

父组件

<!---
   1、在组件上使用 v-model ,父组件 v-model ,子组件接收value , $emit('value',xxxx)
   2、sync [singk].  父组件 :message属性值.sync ='xxxxx' ,,子组件。 $emit('update:message属性值',xxxxx)

-->
<template>
  <el-form
    ref="formRef"
    :model="formData"
    label-width="100px"
    style="margin: 20px; border: 1px solid red"
  >
    <!-- <page-one v-model="showflag" :message.sync="msg"></page-one> -->

    <!-- v-model, sync -->

    <!-- 使用封装的单选组件 -->
    <el-form-item label="性别" prop="gender" :rules="genderRules">
      <radio-group v-model="formData.gender" :options="genderOptions" />
    </el-form-item>
    <el-form-item label="学历" prop="education" :rules="educationRules">
      <radio-group v-model="formData.education" :options="educationOptions">
        <!-- 自定义选项内容(插槽示例) -->
        <template #default="{ option }">
          <template v-if="option.label === '本科'">
            <span style="color: #1890ff">{{ option.label }} 👶</span>
          </template>
        </template>
      </radio-group>
    </el-form-item>

    <el-form-item label="兴趣爱好" prop="hobbies" :rules="checkboxRules">
      <custom-checkbox
        v-model="formData.hobbies"
        :options="checkboxlistopts"
        :min="1"
        :max="2"
      />
    </el-form-item>

    <el-form-item label="输入框"  prop="inputmsg" :rules="inputmsgRules">
      <customInput  
        v-model="formData.inputmsg"
        :maxlength="11"
        show-word-limit
        placeholder="请输入用户名"
        clearable
        :inputStyle="{'width':'200px'}"
        :prefixIcon="'el-icon-search'"
        :rows="5"
        type="textarea"
      /> 

    </el-form-item>   

    <el-input placeholder="请输入内容" v-model="input3" class="input-with-select">
    <el-select v-model="select" slot="prepend" placeholder="请选择">
      <el-option label="餐厅名" value="1"></el-option>
      <el-option label="订单号" value="2"></el-option>
      <el-option label="用户电话" value="3"></el-option>
    </el-select>
    <el-button slot="append" icon="el-icon-search"></el-button>
  </el-input>



    <el-form-item>
      <el-button type="primary" @click="handleSubmit">提交</el-button>
      <el-button @click="handleReset">重置</el-button>
    </el-form-item>



  </el-form>
</template>

<script>
import RadioGroup from "./CustomRadio.vue";
import customCheckbox from "./customCheckbox.vue";
import pageOne from "./pageOne.vue";
import customInput from "./customInput.vue";

export default {
  components: { RadioGroup, customCheckbox, pageOne,customInput},
  data() {
    return { // 表单数据(与 prop 对应)
      formData: {
        gender: null, // 性别(初始值为 null,触发必填校验)
        education: "",
        hobbies: [],
        inputmsg:''
      },

      // 选项列表
      genderOptions: [
        { label: "", value: "male" },
        { label: "", value: "female" },
      ],
      educationOptions: [
        { label: "本科", value: "bachelor" },
        { label: "硕士", value: "master" },
        { label: "博士", value: "doctor" },
      ],
      checkboxlistopts: [
        { label: "选项1", value: "1" },
        { label: "选项2", value: "2" },
        { label: "选项3", value: "3" },
      ],

      checkboxRules: [
        // { required: true, message: "请选择checkbox", trigger: "change" },
        {
          type: "array",
          required: true,
          message: "请至少选择",
          trigger: "change",
        },
        {
          validator: (rule, value, callback) => {
            if (value && value.length > 2) {
              callback(new Error("最多两个"));
            } else {
              callback();
            }
          },
          trigger: "change",
        },
      ],
      // 校验规则(与 Element 规则完全一致)
      genderRules: [
        { required: true, message: "请选择性别", trigger: "change" },
      ],
      inputmsgRules:[
        { required: true, message: "不为空", trigger: "blur" },
      ],
      educationRules: [
        { required: true, message: "请选择学历", trigger: "change" },
        // 自定义校验示例:禁止选择“本科”(仅作演示)
        {
          validator: (rule, value, callback) => {
            if (value === "bachelor") {
              callback(new Error("本科学历不符合要求"));
            } else {
              callback(); // 校验通过
            }
          },
          trigger: "change",
        },
      ],
    };
  },
  methods: {
    // 提交表单:全量校验
    handleSubmit() {
      this.$refs.formRef.validate((valid) => {
        if (valid) {
          console.log("表单校验通过,提交数据:", this.formData);
          // 调用接口提交...
        } else {
          console.log("表单校验失败");
          return false;
        }
      });
    },

    // 重置表单
    handleReset() {
      this.$refs.formRef.resetFields();
    },
  },
};
</script>
<style scoped>
.el-select, .el-input {
    width: 130px;
  }
  .input-with-select .el-input-group__prepend {
    background-color: #fff;
  }
</style>

 

子组件 CustomRadio.vue

<template>
  <!-- 利用 el-form-item 承接校验规则和错误提示 -->
  <el-radio-group
    v-model="innerValue"
    :disabled="disabled"
    :size="size"
    @change="handleChange"
  >
    <!-- 遍历选项生成单选按钮 -->
    <el-radio
      v-for="(option, index) in options"
      :key="option.value || index"
      :label="option.value"
      :disabled="option.disabled || disabled"
      :border="border"
    >
      <!-- 支持默认文本或自定义插槽 -->
      <slot :option="option">
        {{ option.label }}
      </slot>
    </el-radio>
  </el-radio-group>
</template>

<script>
export default {
  name: "RadioGroup",
  props: {
    // 3. v-model 绑定值(父组件表单数据)
    value: {
      type: [String, Number, Boolean],
      default: null,
    },

    // 4. 选项列表(格式:[{ label: '显示文本', value: '值', disabled: false }])
    options: {
      type: Array,
      required: true,
      validator: (val) => {
        // 校验选项必须包含 label 和 value
        return val.every((item) => "label" in item && "value" in item);
      },
    },

    disabled: {
      type: Boolean,
      default: false,
    },
    border: {
      type: Boolean,
      default: false,
    },

    // 手动控制错误信息(可选,优先级高于校验规则的错误)
    errorMsg: {
      type: String,
      default: "",
    },
  },
  data() {
    return {
      // 内部值,避免直接修改 props
      // innerValue: this.value,
    };
  },
  // watch: {
  //   // 同步父组件传入的 value 到内部
  //   value(newVal) {
  //     this.innerValue = newVal;
  //   },
  //   // 内部值变化时同步到父组件(v-model)
  //   innerValue(newVal) {
  //     this.$emit('input', newVal);
  //   },
  // },
  computed: {
    size() {
      // 实际根据系统获取值 store
      return "medium";
    },
    innerValue: {
      get() {
        return this.value;
      },
      set(val) {
        this.$emit("input", val);
      },
    },
  },
  methods: {
    // 处理选择变化,触发校验通知
    handleChange(val) {
      this.$emit("input", val);
    },
  },
};
</script>

 

子组件 customCheckbox.vue

<template>
  <div>
    <!-- 复选框组 -->
    <el-checkbox-group
      v-model="checkedValues"
      :disabled="disabled"
      @change="handleChange"
      :size="size"
    >
      <el-checkbox
        v-for="(option, index) in options"
        :key="option.value || index"
        :label="option.value"
        :disabled="option.disabled || disabled"
      >
        <template v-if="option.slot">
          <slot :name="option.slot" :option="option"></slot>
        </template>
        <template v-else>
          {{ option.label }}
        </template>
      </el-checkbox>
    </el-checkbox-group>
  </div>
</template>

<script>
export default {
  name: "CustomCheckbox",
  props: {
    value: {
      type: Array,
      default: () => [],
    },

    options: {
      type: Array,
      required: true,
      default: () => [],
    },
    min:{
        type: Number,
        default: null
    },
     max:{
        type: Number,
        default: null
    },

    disabled: {
      type: Boolean,
      default: false,
    },
    errorMsg: {
      type: String,
      default: "",
    },
  },
  computed: {
    size() {
      return "medium"; // 可根据实际需求从全局获取
    },
    checkedValues: {
      get() {
        return Array.isArray(this.value) ? [...this.value] : [];
      },
      set(val) {
        const uniqueVal = [...new Set(val)]; // 去重
        this.$emit("input", uniqueVal);
        this.$emit("change", uniqueVal);
      },
    },
  },
  methods: {
    handleChange(val) {
      this.$emit("change", val);
      // 通知 el-form-item 触发验证
      this.$parent.$emit("el.form.change", val);
    },
  },
};
</script>

<style scoped>
.all-checkbox {
  margin-bottom: 8px;
}
.checkbox-list {
  display: flex;
  flex-wrap: wrap;
  gap: 16px;
}
</style>

 

子组件customInput.vue

<template>
  <div class="custom-input-wrapper">
    <el-input
      :id="inputId"
      v-model="currentValue"
      :placeholder="placeholder || `请输入${label || ''}`"
      :type="type"
      :disabled="disabled"
      :size="size"
      :maxlength="maxlength"
      :show-word-limit="showWordLimit"
      :clearable="clearable"
      :prefix-icon="prefixIcon"
      :suffix-icon="suffixIcon"
      :readonly="readonly"
      :autocomplete="autocomplete"
      :validate-status="validateStatus"
      :style="inputStyle"
      :rows="rows"
      :autosize="autosize"
      @input="handleInput"
      @change="handleChange"
      @focus="handleFocus"
      @blur="handleBlur"
      @clear="handleClear"
    />

    <!-- 错误提示(可选) -->
    <div
      v-if="errorMessage && validateStatus === 'error'"
      class="error-message"
    >
      {{ errorMessage }}
    </div>
  </div>
</template>

<script>
export default {
  name: "CustomInput",
  props: {
    // 绑定值(v-model)
    value: {
      type: [String, Number],
      default: "",
    },
    // 输入框ID(用于label关联)
    inputId: {
      type: String,
      default: () => `custom-input-${Date.now()}`,
    },
    rows: {
      type: Number,
      default: null,
    },
    autosize: {
      type: Object,
      default: null,
    },
    // 标签文本
    label: {
      type: String,
      default: "",
    },
    // 是否必填
    required: {
      type: Boolean,
      default: false,
    },
    // 输入框类型(text/password/number等)
    type: {
      type: String,
      default: "text",
    },
    // 占位符
    placeholder: {
      type: String,
      default: "",
    },
    // 是否禁用
    disabled: {
      type: Boolean,
      default: false,
    },
    // 尺寸(medium/small/mini)
    size: {
      type: String,
      default: "medium",
      validator: (val) => ["medium", "small", "mini"].includes(val),
    },
    // 最大长度
    maxlength: {
      type: Number,
      default: null,
    },
    // 是否显示字数统计
    showWordLimit: {
      type: Boolean,
      default: false,
    },
    // 是否显示清空按钮
    clearable: {
      type: Boolean,
      default: false,
    },
    // 前缀图标
    prefixIcon: {
      type: String,
      default: "",
    },
    // 后缀图标
    suffixIcon: {
      type: String,
      default: "",
    },
    // 是否只读
    readonly: {
      type: Boolean,
      default: false,
    },
    // 自动完成(on/off)
    autocomplete: {
      type: String,
      default: "off",
    },
    // 验证状态(success/error/validating)
    validateStatus: {
      type: String,
      default: "",
    },
    // 错误提示信息
    errorMessage: {
      type: String,
      default: "",
    },
    // 自定义输入框样式
    inputStyle: {
      type: Object,
      default: () => ({}),
    },
  },
  data() {
    return {
      // 内部维护的绑定值
      //   currentValue: this.value
    };
  },
  computed: {
    currentValue: {
      get() {
        // 监听外部值变化,同步到内部
        return this.value;
      },
      set(val) {
        // 监听内部值变化,同步到外部
        this.$emit("input", val);
      },
    },
  },

  methods: {
    // 输入事件(实时触发)
    handleInput(val) {
      this.$emit("input", val); // 确保v-model同步
      this.$emit("input-change", val); // 自定义事件,区分原生input
    },
    // 改变事件(失焦时值变化触发)
    handleChange(val) {
      this.$emit("change", val);
    },
    // 聚焦事件
    handleFocus(e) {
      this.$emit("focus", e);
    },
    // 失焦事件
    handleBlur(e) {
      this.$emit("blur", e);
    },
    // 清空事件
    handleClear() {
      this.$emit("clear");
    },
  },
};
</script>

<style scoped>
.custom-input-wrapper {
  margin-bottom: 16px;
}

.custom-input-label {
  display: inline-block;
  margin-bottom: 8px;
  font-size: 14px;
  color: #606266;
}

.required-mark {
  color: #f56c6c;
  margin-left: 4px;
}

.error-message {
  margin-top: 4px;
  font-size: 12px;
  color: #f56c6c;
}
</style>

 

posted on 2025-10-27 17:36  Mc525  阅读(3)  评论(0)    收藏  举报

导航