Vue2 basics eight, slots

Zero, article directory

Vue2 basics eight, slots

1. Slot

(1)Default slot
  • Function: Let some structures inside the component support customization
  • Requirements: Encapsulate a dialog box that needs to be displayed multiple times into a component
  • Question: I don’t want the content of the component to be hard-coded, but I hope it can be customized when using it. what to do?

image-20230714134055594

  • Basic syntax of slots:
    • For the structural parts that need to be customized, use <slot></slot>placeholders instead
    • When using components, inside the <MyDialog></MyDialog> tag, the incoming structure replaces the slot

image-20230714140541881

  • Code

    • parent componentApp.vue
    <template>
      <div>
        <!-- 2. 在使用组件时,组件标签内填入内容 -->
        <MyDialog>
          <div>你确认要删除么</div>
        </MyDialog>
    
        <MyDialog>
          <p>你确认要退出么</p>
        </MyDialog>
      </div>
    </template>
    
    <script>
    import MyDialog from './components/MyDialog.vue'
    export default {
      data () {
        return {
    
        }
      },
      components: {
        MyDialog
      }
    }
    </script>
    
    <style>
    body {
      background-color: #b3b3b3;
    }
    </style>
    
    • SubassemblyMyDialog.vue
    <template>
      <div class="dialog">
        <div class="dialog-header">
          <h3>友情提示</h3>
          <span class="close">✖️</span>
        </div>
    
        <div class="dialog-content">
          <!-- 1. 在需要定制的位置,使用slot占位 -->
          <slot></slot>
        </div>
        <div class="dialog-footer">
          <button>取消</button>
          <button>确认</button>
        </div>
      </div>
    </template>
    
    <script>
    export default {
      data () {
        return {
    
        }
      }
    }
    </script>
    
    <style scoped>
    * {
      margin: 0;
      padding: 0;
    }
    .dialog {
      width: 470px;
      height: 230px;
      padding: 0 25px;
      background-color: #ffffff;
      margin: 40px auto;
      border-radius: 5px;
    }
    .dialog-header {
      height: 70px;
      line-height: 70px;
      font-size: 20px;
      border-bottom: 1px solid #ccc;
      position: relative;
    }
    .dialog-header .close {
      position: absolute;
      right: 0px;
      top: 0px;
      cursor: pointer;
    }
    .dialog-content {
      height: 80px;
      font-size: 18px;
      padding: 15px 0;
    }
    .dialog-footer {
      display: flex;
      justify-content: flex-end;
    }
    .dialog-footer button {
      width: 65px;
      height: 35px;
      background-color: #ffffff;
      border: 1px solid #e1e3e9;
      cursor: pointer;
      outline: none;
      margin-left: 10px;
      border-radius: 3px;
    }
    .dialog-footer button:last-child {
      background-color: #007acc;
      color: #fff;
    }
    </style>
    
(2) Fallback content (default value)
  • Content customization is completed through the slot. What is passed will be displayed, but if it is not passed, it will be blank.
  • Can I set 默认显示内容 for the slot?

image-20230714143219873

  • slot fallback content: When packaging a component, you can provide fallback content (default content) for the reserved <slot> slot.

  • Syntax: Within the <slot> tag, place content as default content,<slot>后备内容</slot>

    • When the component is used externally and nothing is passed, the slot will display the backup content.

      <MyDialog></MyDialog>
      
    • When using components externally, if something is passed, the entire slot will be replaced.

    <MyDialog>我是内容</MyDialog>
    
  • Code

    • parent componentApp.vue
    <template>
      <div>
        <MyDialog></MyDialog>
    
        <MyDialog>
          你确认要退出么
        </MyDialog>
      </div>
    </template>
    
    <script>
    import MyDialog from './components/MyDialog.vue'
    export default {
      data () {
        return {
    
        }
      },
      components: {
        MyDialog
      }
    }
    </script>
    
    <style>
    body {
      background-color: #b3b3b3;
    }
    </style>
    
    • SubassemblyMyDialog.vue
    <template>
      <div class="dialog">
        <div class="dialog-header">
          <h3>友情提示</h3>
          <span class="close">✖️</span>
        </div>
    
        <div class="dialog-content">
          <!-- 往slot标签内部,编写内容,可以作为后备内容(默认值) -->
          <slot>
            我是默认的文本内容
          </slot>
        </div>
        <div class="dialog-footer">
          <button>取消</button>
          <button>确认</button>
        </div>
      </div>
    </template>
    
    <script>
    export default {
      data () {
        return {
    
        }
      }
    }
    </script>
    
    <style scoped>
    * {
      margin: 0;
      padding: 0;
    }
    .dialog {
      width: 470px;
      height: 230px;
      padding: 0 25px;
      background-color: #ffffff;
      margin: 40px auto;
      border-radius: 5px;
    }
    .dialog-header {
      height: 70px;
      line-height: 70px;
      font-size: 20px;
      border-bottom: 1px solid #ccc;
      position: relative;
    }
    .dialog-header .close {
      position: absolute;
      right: 0px;
      top: 0px;
      cursor: pointer;
    }
    .dialog-content {
      height: 80px;
      font-size: 18px;
      padding: 15px 0;
    }
    .dialog-footer {
      display: flex;
      justify-content: flex-end;
    }
    .dialog-footer button {
      width: 65px;
      height: 35px;
      background-color: #ffffff;
      border: 1px solid #e1e3e9;
      cursor: pointer;
      outline: none;
      margin-left: 10px;
      border-radius: 3px;
    }
    .dialog-footer button:last-child {
      background-color: #007acc;
      color: #fff;
    }
    </style>
    
(3) Named slot
  • Requirements: There are multiple structures in a component, and external tags need to be passed in for customization.
  • Default slot: a custom location for

image-20230714150015032

  • Named slot syntax:
    • Use the name attribute to distinguish names of multiple slots
    • template cooperatesv-slot:名字 to distribute corresponding tags
    • v-slot:插槽名can be simplified to#插槽名

image-20230714150130056

  • Code:

    • parent componentApp.vue
    <template>
      <div>
        <MyDialog>
          <!-- 需要通过template标签包裹需要分发的结构,包成一个整体 -->
          <template v-slot:head>
            <div>我是大标题</div>
          </template>
          
          <template v-slot:content>
            <div>我是内容</div>
          </template>
    
          <template #footer>
            <button>取消</button>
            <button>确认</button>
          </template>
        </MyDialog>
      </div>
    </template>
    
    <script>
    import MyDialog from './components/MyDialog.vue'
    export default {
      data () {
        return {
    
        }
      },
      components: {
        MyDialog
      }
    }
    </script>
    
    <style>
    body {
      background-color: #b3b3b3;
    }
    </style>
    
    • SubassemblyMyDialog.vue
    <template>
      <div class="dialog">
        <div class="dialog-header">
          <!-- 一旦插槽起了名字,就是具名插槽,只支持定向分发 -->
          <slot name="head"></slot>
        </div>
    
        <div class="dialog-content">
          <slot name="content"></slot>
        </div>
        <div class="dialog-footer">
          <slot name="footer"></slot>
        </div>
      </div>
    </template>
    
    <script>
    export default {
      data () {
        return {
    
        }
      }
    }
    </script>
    
    <style scoped>
    * {
      margin: 0;
      padding: 0;
    }
    .dialog {
      width: 470px;
      height: 230px;
      padding: 0 25px;
      background-color: #ffffff;
      margin: 40px auto;
      border-radius: 5px;
    }
    .dialog-header {
      height: 70px;
      line-height: 70px;
      font-size: 20px;
      border-bottom: 1px solid #ccc;
      position: relative;
    }
    .dialog-header .close {
      position: absolute;
      right: 0px;
      top: 0px;
      cursor: pointer;
    }
    .dialog-content {
      height: 80px;
      font-size: 18px;
      padding: 15px 0;
    }
    .dialog-footer {
      display: flex;
      justify-content: flex-end;
    }
    .dialog-footer button {
      width: 65px;
      height: 35px;
      background-color: #ffffff;
      border: 1px solid #e1e3e9;
      cursor: pointer;
      outline: none;
      margin-left: 10px;
      border-radius: 3px;
    }
    .dialog-footer button:last-child {
      background-color: #007acc;
      color: #fff;
    }
    </style>
    
(4) Scope slot
  • Scope slot: While defining slot slot, it is possible传值. You can bind data to 插槽 and 使用组件时可以用 in the future.

  • Scenario: Encapsulating table component

    • Pass from father to son, dynamic rendering of table content
    • Use default slots to customize action columns
    • You need to use 当前项的 id to delete or view, which belongs to 组件内部的数据 through value binding 作用域插槽, and then use < /span>

    image-20230714153014884

  • Basic steps:

    • To the slot label, pass the value by adding attributes
    <slot :id="item.id" msg="测试文本"></slot>
    
    • All added attributes will be collected into an object
    { id: 3, msg: '测试文本' }
    
    • in template, received through #插槽名= "obj", the default slot name is default
    <MyTable :list="list">
    	<template #default="obj">
    		<button @click="del(obj.id)">删除</button>
    	</template>
    </MyTable>
    
  • Code

    • parent componentApp.vue
    <template>
      <div>
        <MyTable :data="list">
          <!-- 3. 通过template #插槽名="变量名" 接收 -->
          <template #default="obj">
            <button @click="del(obj.row.id)">
              删除
            </button>
          </template>
        </MyTable>
        
        <MyTable :data="list2">
          <template #default="{ row }">
            <button @click="show(row)">查看</button>
          </template>
        </MyTable>
      </div>
    </template>
    
    <script>
    import MyTable from './components/MyTable.vue'
    export default {
      data () {
        return {
          list: [
            { id: 1, name: '张小花', age: 18 },
            { id: 2, name: '孙大明', age: 19 },
            { id: 3, name: '刘德忠', age: 17 },
          ],
          list2: [
            { id: 1, name: '赵小云', age: 18 },
            { id: 2, name: '刘蓓蓓', age: 19 },
            { id: 3, name: '姜肖泰', age: 17 },
          ]
        }
      },
      methods: {
        del (id) {
          this.list = this.list.filter(item => item.id !== id)
        },
        show (row) {
          // console.log(row);
          alert(`姓名:${row.name}; 年纪:${row.age}`)
        }
      },
      components: {
        MyTable
      }
    }
    </script>
    
    • SubassemblyMyTable.vue
    <template>
      <table class="my-table">
        <thead>
          <tr>
            <th>序号</th>
            <th>姓名</th>
            <th>年纪</th>
            <th>操作</th>
          </tr>
        </thead>
        <tbody>
          <tr v-for="(item, index) in data" :key="item.id">
            <td>{
         
         { index + 1 }}</td>
            <td>{
         
         { item.name }}</td>
            <td>{
         
         { item.age }}</td>
            <td>
              <!-- 1. 给slot标签,添加属性的方式传值 -->
              <slot :row="item" msg="测试文本"></slot>
    
              <!-- 2. 将所有的属性,添加到一个对象中 -->
              <!-- 
                 {
                   row: { id: 2, name: '孙大明', age: 19 },
                   msg: '测试文本'
                 }
               -->
            </td>
          </tr>
        </tbody>
      </table>
    </template>
    
    <script>
    export default {
      props: {
        data: Array
      }
    }
    </script>
    
    <style scoped>
    .my-table {
      width: 450px;
      text-align: center;
      border: 1px solid #ccc;
      font-size: 24px;
      margin: 30px auto;
    }
    .my-table thead {
      background-color: #1f74ff;
      color: #fff;
    }
    .my-table thead th {
      font-weight: normal;
    }
    .my-table thead tr {
      line-height: 40px;
    }
    .my-table th,
    .my-table td {
      border-bottom: 1px solid #ccc;
      border-right: 1px solid #ccc;
    }
    .my-table td:last-child {
      border-right: none;
    }
    .my-table tr:last-child td {
      border-bottom: none;
    }
    .my-table button {
      width: 65px;
      height: 35px;
      font-size: 18px;
      border: 1px solid #ccc;
      outline: none;
      border-radius: 3px;
      cursor: pointer;
      background-color: #ffffff;
      margin-left: 5px;
    }
    </style>
    

2. Comprehensive case-product list

(1) Functional requirements
  • my-tag tag component encapsulation
    • (1) Double-click to display the input box, and the input box gets focus
    • (2) Lose focus and hide the input box
    • (3) Echo label information
    • (4) Modify content and press Enter → Modify label information
  • my-table table component encapsulation
    • (1) Dynamically transfer table data for rendering
    • (2) The header supports user customization
    • (3) The main body supports user customization

image-20230714160132055

(2) Code implementation
  • parent componentApp.vue
<template>
  <div class="table-case">
    <MyTable :data="goods">
      <template #head>
        <th>编号</th>
        <th>名称</th>
        <th>图片</th>
        <th width="100px">标签</th>
      </template>

      <template #body="{ item, index }">
        <td>{
   
   { index + 1 }}</td>
        <td>{
   
   { item.name }}</td>
        <td>
          <img
            :src="item.picture"
          />
        </td>
        <td>
          <MyTag v-model="item.tag"></MyTag>
        </td>
      </template>
    </MyTable>
  </div>
</template>

<script>
// my-tag 标签组件的封装
// 1. 创建组件 - 初始化
// 2. 实现功能
//    (1) 双击显示,并且自动聚焦
//        v-if v-else @dbclick 操作 isEdit
//        自动聚焦:
//        1. $nextTick => $refs 获取到dom,进行focus获取焦点
//        2. 封装v-focus指令

//    (2) 失去焦点,隐藏输入框
//        @blur 操作 isEdit 即可

//    (3) 回显标签信息
//        回显的标签信息是父组件传递过来的
//        v-model实现功能 (简化代码)  v-model => :value 和 @input
//        组件内部通过props接收, :value设置给输入框

//    (4) 内容修改了,回车 => 修改标签信息
//        @keyup.enter, 触发事件 $emit('input', e.target.value)

// ---------------------------------------------------------------------

// my-table 表格组件的封装
// 1. 数据不能写死,动态传递表格渲染的数据  props
// 2. 结构不能写死 - 多处结构自定义 【具名插槽】
//    (1) 表头支持自定义
//    (2) 主体支持自定义

import MyTag from './components/MyTag.vue'
import MyTable from './components/MyTable.vue'
export default {
  name: 'TableCase',
  components: {
    MyTag,
    MyTable
  },
  data () {
    return {
      // 测试组件功能的临时数据
      tempText: '水杯',
      tempText2: '钢笔',
      goods: [
        { id: 101, picture: './assets/img/news.png', name: '梨皮朱泥三绝清代小品壶经典款紫砂壶', tag: '茶具' },
        { id: 102, picture: './assets/img/news.png', name: '全防水HABU旋钮牛皮户外徒步鞋山宁泰抗菌', tag: '男鞋' },
        { id: 103, picture: './assets/img/news.png', name: '毛茸茸小熊出没,儿童羊羔绒背心73-90cm', tag: '儿童服饰' },
        { id: 104, picture: './assets/img/news.png', name: '基础百搭,儿童套头针织毛衣1-9岁', tag: '儿童服饰' },
      ]
    }
  }
}
</script>

<style lang="less" scoped>
.table-case {
  width: 1000px;
  margin: 50px auto;
  img {
    width: 100px;
    height: 100px;
    object-fit: contain;
    vertical-align: middle;
  }
}

</style>
  • SubassemblyMyTable.vue
<template>
  <table class="my-table">
    <thead>
      <tr>
        <slot name="head"></slot>
      </tr>
    </thead>
    <tbody>
      <tr v-for="(item, index) in data" :key="item.id">
        <slot name="body" :item="item" :index="index" ></slot>
      </tr>
    </tbody>
  </table>
</template>

<script>
export default {
  props: {
    data: {
      type: Array,
      required: true
    }
  }
};
</script>

<style lang="less" scoped>
.my-table {
  width: 100%;
  border-spacing: 0;
  img {
    width: 100px;
    height: 100px;
    object-fit: contain;
    vertical-align: middle;
  }
  th {
    background: #f5f5f5;
    border-bottom: 2px solid #069;
  }
  td {
    border-bottom: 1px dashed #ccc;
  }
  td,
  th {
    text-align: center;
    padding: 10px;
    transition: all .5s;
    &.red {
      color: red;
    }
  }
  .none {
    height: 100px;
    line-height: 100px;
    color: #999;
  }
}
</style>
  • SubassemblyMyTag.vue
<template>
  <div class="my-tag">
    <input
      v-if="isEdit"
      v-focus
      ref="inp"
      class="input"
      type="text"
      placeholder="输入标签"
      :value="value"
      @blur="isEdit = false"
      @keyup.enter="handleEnter"
    />
    <div 
      v-else
      @dblclick="handleClick"
      class="text">
      {
   
   { value }}
    </div>
  </div>
</template>

<script>
export default {
  props: {
    value: String
  },
  data () {
    return {
      isEdit: false
    }
  },
  methods: {
    handleClick () {
      // 双击后,切换到显示状态 (Vue是异步dom更新)
      this.isEdit = true
      
      // // 等dom更新完了,再获取焦点
      // this.$nextTick(() => {
      //   // 立刻获取焦点
      //   this.$refs.inp.focus()
      // })
    },
    handleEnter (e) {
      // 非空处理
      if (e.target.value.trim() === '') return alert('标签内容不能为空')

      // 子传父,将回车时,[输入框的内容] 提交给父组件更新
      // 由于父组件是v-model,触发事件,需要触发 input 事件
      this.$emit('input', e.target.value)
      // 提交完成,关闭输入状态
      this.isEdit = false
    }
  }
}
</script>

<style lang="less" scoped>
.my-tag {
  cursor: pointer;
  .input {
    appearance: none;
    outline: none;
    border: 1px solid #ccc;
    width: 100px;
    height: 40px;
    box-sizing: border-box;
    padding: 10px;
    color: #666;
    &::placeholder {
      color: #666;
    }
  }
}
</style>
  • Portalmain.jsRegister custom instructions
import Vue from 'vue'
import App from './App.vue'

Vue.config.productionTip = false

// 封装全局指令 focus
Vue.directive('focus', {
    
    
  // 指令所在的dom元素,被插入到页面中时触发
  inserted (el) {
    
    
    el.focus()
  }
})

new Vue({
    
    
  render: h => h(App),
}).$mount('#app')

Guess you like

Origin blog.csdn.net/liyou123456789/article/details/131964380