vue:我可以将v-model名称从slot绑定到子元素中的数组吗?

r6l8ljro  于 2021-09-29  发布在  Java
关注(0)|答案(3)|浏览(399)

今天我被要求使用插槽构建一个动态表单组件。表单组件将包含一个输入的动态列表,用户可以将v-model属性包含在自己的输入字段中。填写最后一行时,应在其下方显示一个新的空行。我从这个开始:
parent.vue(注意:跳过了不相关的代码):

<template>
  <div>
    <FormComponent><input type="text" v-model="something"/></FormComponent>
  <div>
</template>

<script>
import FormComponent from '../components/FormComponent.vue';

export default {
  components: { FormComponent },
  data() {
    return {
      something: []  // could be a list, or a string, number, Object, etc...
    };
  }
}
</script>

formcomponent.vue:

<template>
  <div>
    <div v-for="(item, index) in items" :key="index">
      {{index+1}}<slot v-bind="items"></slot>
    </div>
  </div>
</template>

<script>
export default {
  name: "FormComponent",
  data() {
    return {
      items: []
    }
  },
  props: {
    value: String,
  },
  created() {
    console.log("create newform");
  },
  watch: {
    items(oldValue, newValue) {
      if (oldValue[oldValue.length] == "" && newValue[newValue.length] != "") {
        newValue.push("");
      }
    }
  },

现在我想将父级中定义的v-model绑定到一个数组以自动生成新行,但似乎无法从插槽中提取v-model值。如何将输入中v模型定义的值链接到数组?
另外,如何允许动态更新列表?

xkftehaa

xkftehaa1#

插槽的整个思想是通过父组件而不是子组件来管理它们。所以 v-for 应位于父组件内部,而不是 FormComponent . 父级还负责将新的空字段添加到 v-model S您可能有一个formcomponent,并将几个formfield组件放入其中,每个formfield可能有不同类型的输入。
formcomponent.vue

<template>
  <form v-bind="$attrs" v-on="listeners">
    <slot/>
  </form>
</template>
<script>
export default
{
  name: 'FormValidator',
  data ()
  {
    return {
      dirty: false,
    }
  },
  computed:
    {
      listeners ()
      {
        return {
          ...this.$listeners,
          submit: (evt) =>
          {
            evt.preventDefault();
            this.fields().forEach(item =>
            {
              item.setDirty(true);
            });
            this.$emit('submit', evt);
          }
        };
      }
    },
  methods:
    {
      fields ()
      {
        // we can not use computed property because $children is not reactive
        const child = this.$children.slice();
        const fields = [];
        let cur;
        while (child.length > 0)
        {
          cur = child.shift();
          if (cur.$options.name === 'FormField') fields.push(cur);
          else
          {
            cur = cur.$children.slice();
            for (let i = 0; i < cur.length; i++) child.push(cur[i]);
          }
        }
        return fields;
      },
      valid ()
      {
        const fields = this.fields();
        return !fields.some(item => item.$options.propsData.error && item.dirty);
      },
      reset ()
      {
        this.fields().forEach(item => item.setDirty(false));
      }
    }
}
</script>

formfield.vue

<template>
  <div class="form_field">
    <label v-if="label">{{ label }}</label>
    <div class="form_group" @input="setDirty" @change="setDirty">
      <div v-if="$slots.prepend" class="form_input_prepend">
        <slot name="prepend"/>
      </div>
      <slot/>
      <div v-if="$slots.append" class="form_input_append">
        <slot name="append"/>
      </div>
    </div>
    <div v-if="error && dirty" class="field_error">{{ error }}</div>
  </div>
</template>
<script>
export default
{
  name: 'FormField',
  props:
    {
      label:
        {
          type: String,
          default: ''
        },
      error:
        {
          type: String,
          default: null
        },
    },
  data ()
  {
    return {
      dirty: false,
    }
  },
  computed:
    {
      valid ()
      {
        return !this.error;
      }
    },
  methods:
    {
      setDirty (value)
      {
        this.dirty = value;
      }
    }
}
</script>
<style lang="scss">
  $field_radius: $corner-2;

  .form_field
  {
    display: flex;
    flex-direction: column;
  }

  .form_field + .form_field
  {
    margin-top: 8px;
  }

  .form_field > label
  {
    padding-bottom: 4px;
  }

  .form_group
  {
    border: 1px solid $dialog_border;
    border-radius: $field_radius;
    display: flex;
    align-items: center;
    background-color: $bg_color;
    flex-grow: 1;
  }

  .form_group:focus-within
  {
    border-color: $input_border;
  }

  .form_group input,
  .form_group select,
  .form_group textarea
  {
    border: none;
    flex: 1 1 auto;
    padding: 0 6px;
    margin: 0;
    min-height: calc(1.5rem + 8px);
    min-width: 48px;
    font-size: 1rem;
    line-height: 1.5;
    border-radius: $field_radius; /* probably should be conditional - based on the absence of prepend/append slot contents */
  }

  .form_group select
  {
    background: white;
  }

  .form_group textarea
  {
    height: auto;
  }

  .form_group input[type="file"]
  {
    padding: 0;
    min-height: 0;
  }

  .form_group input:focus,
  .form_group select:focus,
  .form_group textarea:focus
  {
    outline: none;
  }

  .form_group input:disabled,
  .form_group select:disabled,
  .form_group textarea:disabled
  {
    background-color: $input_disabled;
  }

  .form_group > *:not(.form_input_prepend):not(.form_input_append)
  {
    align-self: stretch;
  }

  .form_input_prepend,
  .form_input_append
  {
    background-color: $input_prepend;
    display: flex;
    justify-content: center;
    align-items: center;
  }

  .form_input_prepend
  {
    border-top-left-radius: $field_radius;
    border-bottom-left-radius: $field_radius;
  }

  .form_input_append
  {
    border-top-right-radius: $field_radius;
    border-bottom-right-radius: $field_radius;
  }

  .field_error
  {
    font-family: 'Segoe UI', Tahoma, sans-serif;
    font-size: 85%;
    color: red;
    margin: 3px 0;
  }
</style>

然后,您可以编写如下内容:

<FormComponent ref="frm" @submit.prevent="submitForm">
      <FormField v-for="(field,index) in fields" :key="index" label="Password">
        <input v-model.trim="field.value">
      </FormField>
    </FormComponent>
<script>
export default
{
  name: 'MyComp',
  data()
  {
    return {
      fields: [
        {
          type: 'text',
          value: '',
        },
      ];
    };
  },
  computed:
  {
    allFieldsNotEmpty()
    {
      return this.fields.filter(item => !item.value).length > 0;
    }
  },
  watch:
  {
    allFieldsNotEmpty(newValue, oldValue)
    {
      if (newValue && !oldValue) this.fields.push({type: 'text', value: ''});
    }
  },
  methods:
  {
    submitForm()
    {
      if (this.$refs.frm.valid()) // check if no FormField has a non-empty "error" prop
      {
        // make your AJAX request here
      }
    }
  }
}
</script>
z3yyvxxp

z3yyvxxp2#

我想你需要这样的东西(它是vue 3。只是v模式系统有点不同)
表单组件:

<template>
  <div>
    <div v-for="(item, index) in items" :key="item.id">
      {{ index + 1 }}
      <slot :item="item" :input="onInput" />
    </div>
  </div>
</template>

<script>
export default {
  name: "FormComponent",

  props: {
    value: String,
    items: Array,
  },
  created() {
    console.log("create newform");
  },
  methods: {
    onInput(id, event) {
      console.log("id", id);
      console.log("event", event);
      // HERE you cant handle changing  the item and pass it up
      // this.$emit('update:items', NEW_ARRAY)
    },
  },
  watch: {
    items(oldValue, newValue) {
      if (oldValue[oldValue.length] == "" && newValue[newValue.length] != "") {
        newValue.push("");
      }
    },
  },
};
</script>

parent.vue

<template>
  <div>
    <FormComponent v-model:items="something">
      <template #default="{ item, input }">
        <input type="text" :value="item.text" @input="input(item.id, $event)" />
      </template>
    </FormComponent>
  </div>
</template>

<script>
import FormComponent from "./FormComponent.vue";

export default {
  components: { FormComponent },
  data() {
    return {
      something: [
        {
          id: 1,
          text: "Good",
        },
        {
          id: 2,
          text: "Good",
        },
      ], // could be a list, or a string, number, Object, etc...
    };
  },
};
</script>
iugsix8n

iugsix8n3#

我想达到的目标更像这样:

<!-- What I wanted to achieve -->
    <FormComponent>
      <component />
    </FormComponent>

    <!-- Not what I wanted to achieve! Too much complexity for the end user.  -->
    <FormComponent>
      <FormField>
        <component />
      </FormField>
    </FormComponent>

相关问题