当前位置: 首页 > article >正文

Vue进阶之旅:核心技术与实战(自定义指令、插槽与路由入门)

Vue进阶之旅:核心技术与实战

文章目录

  • Vue进阶之旅:核心技术与实战
    • 一、自定义指令
      • (一)注册方式
      • (二)指令的值
      • (三)v-loading指令封装
    • 二、插槽
      • (一)默认插槽
      • (二)后备内容(默认值)
      • (三)具名插槽
      • (四)作用域插槽
    • 三、路由入门
      • (一)单页应用程序(SPA)
      • (二)路由概念
      • (三)VueRouter的基本使用
      • (四)组件目录存放问题
    • 四、综合案例:商品列表
      • (一)MyTag 组件
      • (二)TableCase 组件
      • (三)MyTable 组件

在Vue开发中,自定义指令、插槽与路由是非常重要的技术点,它们能够极大地提升我们开发的效率和灵活性。今天我们就一起来深入学习这些内容。

一、自定义指令

自定义指令允许我们封装一些DOM操作,扩展额外功能。

(一)注册方式

自己调用自己:

代码解释

  • 模板部分(<template>
<template>
  <div>
    <h1>自定义指令</h1>
    <input ref="inp" type="text">
  </div>
</template>

此部分定义了一个 Vue 组件的 HTML 结构,包含一个标题 <h1> 和一个文本输入框 <input>ref="inp" 为输入框添加了一个引用名称。

  • 脚本部分(<script>
export default {
  mounted () {
    this.$refs.inp.focus();
  }
}

此部分定义了一个 Vue 组件,在组件挂载完成(mounted 生命周期)时,通过 this.$refs.inp 找到 refinp 的输入框元素,并调用 focus() 方法让其获得焦点。

功能总结

该 Vue 组件的主要功能是在页面上展示一个包含标题和文本输入框的区域,并在组件挂载完成时使输入框自动获得焦点,方便用户直接输入,提升用户体验。

image-20250117114537476

接下来就是使用指令来获得焦点:

  1. 全局注册
    全局注册使用 Vue.directive 方法,语法如下:
Vue.directive('指令名', {
  inserted (el) {
    // 可以对el标签扩展额外功能,例如让元素获取焦点
    el.focus()
  }
})

在这里,inserted 钩子函数在被绑定元素插入父节点时调用 ,el 表示被绑定的DOM元素。举个例子:

全局注册解释

在 Vue 中,全局注册是将组件、指令或过滤器等添加到 Vue 实例的全局范围,以便在整个应用的任何组件中使用,无需在每个组件中单独导入或声明。

下述代码中的全局注册

Vue.directive('focus', {
  inserted (el) {
    el.focus();
  }
})
  • Vue.directive('focus', {...}) 用于全局注册自定义指令 focus
  • inserted(el) { el.focus() }focus 指令的一个钩子函数,当元素被插入页面时触发,使元素获得焦点。

使用场景

<input v-focus ref="inp" type="text">
  • 这里使用 v-focus 指令,因为它是全局注册的,无需额外导入或声明,元素插入页面时,会自动执行 inserted 函数使输入框聚焦。

总结
全局注册可在多个组件复用,避免重复代码,提高开发效率和可维护性,上述代码的 focus 指令就体现了这点。

  1. 局部注册
    在组件内部通过 directives 选项进行局部注册,示例代码如下:
export default {
  directives: {
    '指令名': {
      inserted (el) {
        el.focus()
      }
    }
  }
}

在标签中使用时,写法为 <input v-指令名 type="text">。通过自定义指令,我们可以避免在 mounted 钩子函数中使用 this.$refs.inp.focus() 这种较为繁琐的获取焦点方式,代码更加简洁。

局部注册解释

在 Vue 中,局部注册是指将自定义指令、组件或过滤器等仅在某个组件内部进行注册的操作,使其仅在该组件内可用,而不会影响到其他组件。

代码中的局部注册

export default {
  directives : {
    focus : {
      inserted (el) {
        el.focus();
      }
    }
  }
}
  • 在这个 Vue 组件的 script 部分,通过 directives 属性进行了自定义指令的注册。
  • focus : {...} 定义了一个名为 focus 的自定义指令。
  • inserted (el) {...} 是该指令的一个钩子函数,当使用 v-focus 指令的元素被插入页面时,会执行 el.focus() 操作,使元素获得焦点。

使用场景

<input v-focus ref="inp" type="text">
  • 这里使用 v-focus 指令,在上述代码中,focus 指令只在当前组件的输入框中使用,确保了其仅在该组件内发挥作用,不会影响其他组件,这就是典型的局部注册使用场景。

总结
上述代码中的 directives 属性的使用就是局部注册的体现,仅在当前组件内注册了 focus 指令,使其仅能在本组件内使用,与全局注册相比,其作用范围受限,但对于只在特定组件中使用的功能,局部注册能保持代码的简洁和独立性。

(二)指令的值

指令可以通过 “等号” 的形式绑定参数值,如 <div v-color="color">我是内容</div>。在指令的定义中,通过 binding.value 可以拿到指令值,指令值修改会触发 update 函数。示例代码如下:

directives: {
  color: {
    inserted (el, binding) {
      // 设置元素文字颜色为指令值
      el.style.color = binding.value
    },
    update (el, binding) {
      // 指令值更新时,更新元素文字颜色
      el.style.color = binding.value
    }
  }
}

这种方式可以应对更复杂的指令封装场景。

代码示例

<template>
  <div>
    <!-- 使用 v-color 指令,并将其绑定到 color1 数据 -->
    <h1 v-color="color1">自定义指令1</h1> 
    <!-- 使用 v-color 指令,并将其绑定到 color2 数据 -->
    <h1 v-color="color2">自定义指令2</h1> 
  </div>
</template>

<script>
export default {
  data () {
    return {
      // 定义 color1 的数据,初始值为红色
      color1 : 'red', 
      // 定义 color2 的数据,初始值为绿色
      color2 : 'green' 
    }
  },

  directives : {
    color : {
      inserted (el, binding) {
        // inserted 钩子函数:当指令所在元素插入页面时,将元素的颜色设置为指令绑定的值
        el.style.color = binding.value; 
      },

      update (el, binding) {
        // update 钩子函数:当指令绑定的值更新时,更新元素的颜色
        el.style.color = binding.value; 
      }
    }
  }
}
</script>

解释

  • template 部分,我们使用了 v-color 自定义指令,并将其绑定到组件中的 color1color2 数据。
  • script 部分:
    • 首先,通过 data 函数定义了 color1color2 的数据,分别初始化为 redgreen
    • 然后,在 directives 对象中定义了 color 自定义指令。
    • 对于 color 自定义指令:
      • inserted 钩子函数:当使用 v-color 指令的元素插入到页面时,会调用该函数。通过 binding.value 可以获取到指令绑定的值,将元素的颜色样式设置为该值。例如,对于 <h1 v-color="color1">自定义指令1</h1>,会将该 h1 元素的颜色设置为 red
      • update 钩子函数:当指令绑定的值发生更新时,会调用该函数。同样通过 binding.value 获取更新后的值,并更新元素的颜色。例如,如果 color1 的值从 red 变为 blue,元素的颜色会更新为 blue

使用场景和优势

  • 这种自定义指令的方式适用于对元素样式或其他 DOM 属性进行动态控制的场景。
  • 通过 binding.value 可以灵活地将不同的数据值绑定到指令上,实现元素样式或行为的动态调整。
  • 当数据发生变化时,update 钩子函数会自动更新元素的样式,无需手动操作 DOM,符合 Vue 的数据驱动视图的理念。

注意事项

  • 确保 binding.value 提供的值符合元素属性的要求,例如,在设置颜色时,需要提供有效的颜色值,如颜色名称、十六进制代码或 RGB 值等。
  • 合理使用 insertedupdate 钩子函数,根据不同的生命周期阶段实现相应的逻辑,避免冗余代码和性能问题。

通过这种方式,可以实现对元素样式的动态管理,在开发中可以根据不同的业务需求,扩展更多功能,如动态改变元素的大小、位置等,为开发更具交互性和动态性的 Vue 组件提供了很大的便利。

(三)v-loading指令封装

在实际开发中,数据请求时页面可能会空白,影响用户体验。我们可以封装 v-loading 指令来实现加载中的效果。

  1. 核心思路
    • 准备一个 loading 类,通过伪元素定位,设置宽高实现蒙层效果,让用户知晓数据正在加载。。
    • 利用 v-loading 指令,将其绑定到需要显示加载状态的元素上,并关联一个布尔变量。在数据请求开始时,将该变量置为 true,添加 loading 类,显示蒙层;数据请求结束后,将该变量置为 false,移除 loading 类,隐藏蒙层。
  2. 代码实现

CSS 部分

.loading:before {
  content: '';
  position: absolute;
  left: 0;
  top: 0;
  width: 100%;
  height: 100%;
  background: #fff url('./loading.gif') no-repeat center;
}

.box {
  width: 800px;
  min-height: 500px;
  border: 3px solid orange;
  border-radius: 5px;
  position: relative;
}
.news {
  display: flex;
  height: 120px;
  width: 600px;
  margin: 0 auto;
  padding: 20px 0;
  cursor: pointer;
}
.news.left {
  flex: 1;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  padding-right: 10px;
}
.news.left.title {
  font-size: 20px;
}
.news.left.info {
  color: #999999;
}
.news.left.info span {
  margin-right: 20px;
}
.news.right {
  width: 160px;
  height: 120px;
}
.news.right img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}
  • .loading:before 样式:
    • content: '';:使用伪元素添加内容。
    • position: absolute;:设置绝对定位,使其覆盖父元素。
    • left: 0; top: 0; width: 100%; height: 100%;:确保覆盖整个父元素。
    • background: #fff url('./loading.gif') no-repeat center;:使用白色背景并在中心位置显示 loading.gif 图片,实现加载效果。

JavaScript 部分

<template>
  <div class="main">
    <div class="box" v-loading="isLoading">
      <ul>
        <li v-for="item in list" :key="item.id" class="news">
          <div class="left">
            <div class="title">{{ item.title }}</div>
            <div class="info">
              <span>{{ item.source }}</span>
              <span>{{ item.time }}</span>
            </div>
          </div>
          <div class="right">
            <img :src="item.img" alt="">
          </div>
        </li>
      </ul>
    </div>
  </div>
</template>

<script>
import axios from 'axios';

export default {
  data() {
    return {
      list: [],
      isLoading: true
    };
  },
  async created() {
    // 发送请求获取数据
    const res = await axios.get('http://hmajax.itheima.net/api/news');
    setTimeout(() => {
      // 模拟请求延迟,将数据更新到 list 中并将 isLoading 设为 false
      this.list = res.data.data;
      this.isLoading = false;
    }, 2000);
  },
  directives: {
    loading: {
      inserted(el, binding) {
        // 当指令插入元素时,根据 binding.value 添加或移除 loading 类
        binding.value? el.classList.add('loading') : el.classList.remove('loading');
      },
      update(el, binding) {
        // 当指令值更新时,根据 binding.value 添加或移除 loading 类
        binding.value? el.classList.add('loading') : el.classList.remove('loading');
      }
    }
  }
};
</script>
  • 在 Vue 组件的 template 部分:
    • <div class="box" v-loading="isLoading">:使用 v-loading 指令将 isLoading 变量绑定到 div.box 元素,控制加载状态。
    • v-for="item in list":遍历 list 数组,显示新闻列表。
  • 在 Vue 组件的 script 部分:
    • data():定义 list 数组存储新闻列表,isLoading 布尔变量控制加载状态,初始为 true
    • created() 生命周期方法:
      • const res = await axios.get('http://hmajax.itheima.net/api/news');:使用 axios 发送请求获取新闻数据。
      • setTimeout(() => {...}, 2000);:模拟 2 秒的请求延迟,完成后更新 list 并将 isLoading 设为 false
    • directives.loading
      • inserted 钩子函数:当指令插入元素时,根据 binding.value 决定是否添加 loading 类。
      • update 钩子函数:当 binding.value 更新时,相应更新 loading 类。

使用说明

  1. 首先,将上述代码添加到你的 Vue 组件中,确保样式和脚本都正确引入。
  2. data 函数中初始化 isLoadingtrue,表示初始加载状态。
  3. 在需要显示加载效果的元素上使用 v-loading 指令,如 <div v-loading="isLoading">...</div>
  4. 当发送数据请求时,保持 isLoadingtrue,请求完成后将其置为 false

二、插槽

插槽用于让组件内部的一些结构支持自定义。

(一)默认插槽

  1. 作用与使用步骤
    当我们希望组件的部分内容可以在使用时自定义时,可使用默认插槽。在组件内需要定制的结构部分,改用 <slot></slot> 占位,使用组件时,在组件标签内部传入结构替换 slot。例如,封装一个对话框组件:
<template>
  <div class="dialog">
    <div class="dialog-header">
      <h3>友情提示</h3>
      <span class="close">✖️</span>
    </div>
    <div class="dialog-content">
      <slot></slot>
    </div>
    <div class="dialog-footer">
      <button>取消</button>
      <button>确认</button>
    </div>
  </div>
</template>

使用组件时:

<MyDialog>你确认要退出本系统么?</MyDialog>

这样,对话框的内容部分就可以根据需求自定义了。

(二)后备内容(默认值)

如果希望插槽在没有传入内容时显示默认内容,可以在 <slot> 标签内放置内容作为后备内容。例如:

<template>
  <div class="dialog">
    <div class="dialog-header">
      <h3>友情提示</h3>
      <span class="close">✖️</span>
    </div>
    <div class="dialog-content">
      <slot>我是后备内容</slot>
    </div>
    <div class="dialog-footer">
      <button>取消</button>
      <button>确认</button>
    </div>
  </div>
</template>

当使用组件未传入内容时,会显示 “我是后备内容”;传入内容时,则显示传入的内容。

(三)具名插槽

当组件内有多处结构需要外部传入标签进行定制时,使用具名插槽。

  1. 语法
    在组件内,多个 slot 使用 name 属性区分名字,例如:
<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>

在上述代码中,我们定义了一个 MyDialog 组件,它的模板包含了多个 slot 元素,每个 slot 都有一个 name 属性:

    • <slot name="head"></slot>:定义了一个名为 head 的插槽,用于显示对话框的头部内容。
    • <slot name="content"></slot>:定义了一个名为 content 的插槽,用于显示对话框的主体内容。
    • <slot name="footer"></slot>:定义了一个名为 content 的插槽,用于显示对话框的底部按钮或操作区域。

在使用组件时,通过 template 配合 v-slot:名字 或简化语法 #名字 来分发对应标签:

<MyDialog>
  <template v-slot:head>大标题</template>
  <!-- 简化语法 -->
  <template #content>内容文本</template> 
  <template #footer>
    <button>按钮</button>
  </template>
</MyDialog>

通过具名插槽,可以精确地对组件内不同位置的结构进行定制。

如:

<template>
  <div>
    <MyDialog>
      <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>
  • 这里我们导入了 MyDialog 组件并在父组件中使用它。

  • 使用具名插槽时,可以通过以下两种语法:

    • v-slot:head:这是完整的写法,用于向 head 插槽中插入内容。

    • #footer:这是简化写法,用于向 footer 插槽中插入内容。

使用具名插槽的步骤和优势

步骤

  1. 在组件内部,使用 <slot name="插槽名称"></slot> 标记不同的插槽位置。
  2. 在使用该组件的父组件中,使用 <template v-slot:插槽名称><template #插槽名称> 语法将内容插入到相应的插槽中。

优势

  • 精确控制:可以精确地将内容分发到组件内的不同位置,使组件更加灵活和可定制。例如,在 MyDialog 组件中,我们可以分别将大标题、内容和按钮分发到不同的位置,而不会混淆。
  • 提高组件复用性:可以使用同一个组件,根据不同的使用场景插入不同的内容,无需创建多个相似的组件。例如,我们可以使用 MyDialog 组件创建不同的对话框,只需要修改具名插槽内的内容,而不需要修改组件的结构。

注意事项

  • 确保在使用具名插槽时,插槽名称在组件内是唯一的,避免混淆。
  • 可以使用 v-slot# 简化语法,但要注意在 Vue 2.6 及以后的版本中,v-slot 应在 <template> 元素上使用,而 #v-slot 的简化,仅在 <template> 元素上使用。
  • 当使用具名插槽时,插入的内容可以是 HTML 元素、文本或其他 Vue 组件。
<MyDialog>
  <template v-slot:head>
    <h1>欢迎使用</h1>
  </template>
  <template #content>
    <p>请输入你的信息:</p>
    <input type="text">
  </template>
  <template #footer>
    <button @click="submit">提交</button>
  </template>
</MyDialog>

(四)作用域插槽

作用域插槽可以在定义 slot 插槽的同时传值,供使用组件时使用。例如封装表格组件时,可用于定制操作列。

  1. 使用步骤
    在组件内,给 slot 标签以添加属性的方式传值:
<slot :id="item.id" msg="测试文本"></slot>

所有添加的属性会被收集到一个对象中。在使用组件时,通过 template 配合 #插槽名="obj" 接收,例如:

<MyTable :list="list">
  <template #default="obj">
    <button @click="del(obj.id)">删除</button>
  </template>
</MyTable>

这里 default 为默认插槽名,通过这种方式可以在组件外部访问到组件内部的数据。

三、路由入门

(一)单页应用程序(SPA)

单页应用程序(SPA)是指所有功能在一个 html 页面上实现。其优点包括按需更新性能高、开发效率高、用户体验好;缺点是学习成本高、首屏加载慢、不利于SEO。适用于系统类网站、内部网站、文档类网站、移动端站点等。

(二)路由概念

在Vue中,路由是路径和组件的映射关系。例如,http://localhost:8080/#/home 对应首页组件,http://localhost:8080/#/comment 对应评论组件等。通过路由,我们可以根据不同的路径匹配渲染相应的组件。

(三)VueRouter的基本使用

VueRouter是Vue官方的路由插件,用于实现路径改变时切换显示匹配的组件。

  1. 使用步骤(5 + 2)
    • 5个基础步骤

      • 下载:使用 yarn add vue-router@3.6.5 下载VueRouter模块到当前工程。[注]在下载前,请用rm -r node_modules 删除原先的node_modules,否则会产生报错(因人而异)image-20250118164355364
      • 引入:在项目中引入VueRouter,import VueRouter from 'vue-router'
      • 安装注册:通过 Vue.use(VueRouter) 进行安装注册。
      • 创建路由对象const router = new VueRouter()
      • 注入:将路由对象注入到 new Vue 实例中,new Vue({ render: h => h(App), router }).$mount('#app')
      • import Vue from 'vue'
        import App from './App.vue'
        
        import VueRouter from 'vue-router'
        Vue.use(VueRouter)
        
        const router = new VueRouter()
        
        Vue.config.productionTip = false
        
        new Vue({
          render: h => h(App),
          router : router
        }).$mount('#app')
        
    • 2个核心步骤

      • 创建组件并配置路由规则:在 views 目录下创建组件,如 Find.vueMy.vueFriend.vue,然后配置路由规则:
import Find from './views/Find.vue'
import My from './views/My.vue'
import Friend from './views/Friend.vue'

const router = new VueRouter({
  routes: [
    { path: '/find', component: Find },
    { path: '/friend', component: Friend },
    { path: '/my', component: My }
  ]
})
  - **配置导航和路由出口**:在页面中配置导航链接,如 `<a href="#/find">发现音乐</a>`,并设置路由出口 `<router-view></router-view>`,用于显示匹配路径的组件。

(四)组件目录存放问题

.vue 文件分为页面组件和复用组件两类。页面组件放在 src/views 文件夹,用于配合路由进行页面展示;复用组件放在 src/components 文件夹,用于封装复用。例如,Home.vue(首页)、Category.vue(分类页)等页面组件放在 views 目录,Comment.vue(评价组件)等复用组件放在 components 目录,这样分类存放更易于维护。

通过今天对Vue自定义指令、插槽与路由入门的学习,我们对Vue的核心技术有了更深入的理解。这些技术在实际项目开发中非常实用,能够帮助我们构建出更加灵活、高效的应用程序。在后续的学习和实践中,我们还需要不断地运用和巩固这些知识,提升自己的开发能力。 在深入学习 Vue 开发的过程中,我们已经对自定义指令、插槽和路由入门有了一定的了解。下面我们将通过一些实际案例来进一步加深对这些知识的理解和运用,同时对之前学习的内容进行更全面的总结。

四、综合案例:商品列表

在商品列表的实现中,我们封装了两个重要组件:标签组件(MyTag)和表格组件(MyTable),这两个组件综合运用了之前所学的多种技术。

以下是添加中文注释后的代码:

(一)MyTag 组件

1. 双击标签显示输入框,并且输入框自动获取焦点。

<template>
  <div class="my-tag">
    <!-- 使用 v-if 判断是否显示输入框,根据 isEdit 的值决定 -->
    <input
      v-if="isEdit"
      class="input"
      v-focus
      :value="value"
      ref="inp"
      type="text"
      placeholder="输入标签"
      <!-- 当输入框失去焦点时触发,将 isEdit 设为 false 以隐藏输入框 -->
      @blur="isEdit = false"
      <!-- 当用户按下回车键时触发 handleEnter 方法 -->
      @keyup.enter="handleEnter"
    />
    <!-- 当不满足 v-if 条件时显示该 div 元素,用户双击时触发 handleClick 方法 -->
    <div v-else @dblclick="handleClick" class="text">{{value}}</div>
  </div>
</template>

<script>
export default {
  props : {
    // 接收父组件传递的标签信息,用于显示和修改
    value : String
  },
  data () {
    return {
      // 用于控制输入框的显示和隐藏,初始为 false 表示输入框隐藏
      isEdit : false
    }
  },

  methods : {
    handleClick () {
      // 当用户双击标签时,将 isEdit 设为 true,使输入框显示
      this.isEdit = true
    },
  }
}
</script>
  • 模板部分
    • <div class="my-tag">
      // 定义 MyTag 组件的容器元素。
    • <input v-if="isEdit" class="input" v-focus ref="inp" type="text" placeholder="输入标签" @blur="isEdit = false" @keyup.enter="handleEnter" />
      • v-if="isEdit"
        // 根据 isEdit 变量的值来决定是否显示输入框,初始状态下 isEditfalse,输入框不显示。
      • v-focus
        // 是一个自定义指令,当输入框元素插入页面时,会触发 inserted 钩子函数(该指令在 Vue 实例中已全局注册,未在此组件中显示,但应该存在于项目的其他地方),调用 el.focus() 使输入框自动获得焦点。
      • ref="inp"
        // 为输入框添加一个引用,方便在组件内部通过 this.$refs 访问该元素。
      • @blur="isEdit = false"
        // 当输入框失去焦点时,将 isEdit 的值置为 false,用于后续隐藏输入框的操作。
      • @keyup.enter="handleEnter"
        // 为输入框添加 @keyup.enter 事件监听器,用于处理回车键事件。
    • <div v-else @dblclick="handleClick" class="text">{{value}}</div>
      • v-else
        // 与 v-if 相对应,当 isEditfalse 时显示该 div 元素。
      • @dblclick="handleClick"
        // 为该 div 元素添加双击事件,当用户双击时调用 handleClick 方法。
  • 脚本部分
    • props : {...}
      • value : String
        // 接收父组件传递过来的标签信息,用于显示和修改。
    • data () {...}
      • isEdit : false
        // 一个数据属性,用于控制输入框的显示与隐藏,初始状态下输入框处于隐藏状态。
    • methods : {...}
      • handleClick () {...}
        • this.isEdit = true
          // 双击 div 元素时,将 isEdit 的值置为 true,从而触发 v-if 条件,使输入框显示,同时由于 v-focus 指令的存在,输入框会自动获得焦点。

image-20250118161332247

2. 当输入框失去焦点时,隐藏输入框。

<template>
  <div class="my-tag">
    <input
      v-if="isEdit"
      class="input"
      v-focus
      :value="value"
      ref="inp"
      type="text"
      placeholder="输入标签"
      @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 () {
      this.isEdit = true
    },
  }
}
</script>
  • 模板部分
    • <input... @blur="isEdit = false" />
      • @blur="isEdit = false"
        // 为输入框添加 @blur 事件监听器,当输入框失去焦点时,将 isEdit 的值置为 false,根据 v-if 条件,输入框会隐藏。

3. 能够回显标签原本的信息。

<template>
  <div class="my-tag">
    <input
      v-if="isEdit"
      class="input"
      v-focus
      :value="value"
      ref="inp"
      type="text"
      placeholder="输入标签"
      @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 () {
      this.isEdit = true
    },
  }
}
</script>
  • 模板部分
    • <input v-if="isEdit"... :value="value" />
      • :value="value"
        // 使用 :value 绑定了 value 属性,将父组件传递过来的值显示在输入框中,实现输入框显示标签原本信息的功能。
    • <div v-else @dblclick="handleClick" class="text">{{value}}</div>
      • {{value}}
        // 使用双大括号插值表达式将 value 的值显示在 div 元素中,当输入框隐藏时,回显标签原本的信息。

4. 在输入框中修改内容后,按下回车键可以修改标签信息。

<template>
  <div class="my-tag">
    <input
      v-if="isEdit"
      class="input"
      v-focus
      :value="value"
      ref="inp"
      type="text"
      placeholder="输入标签"
      @blur="isEdit = false"
      @keyup.enter="handleEnter"
    />
    <div v-else @dblclick="handleClick" class="text">{{value}}</div>
</template>

<script>
export default {
  props : {
    value : String
  },
  data () {
    return {
      isEdit : false
    }
  },

  methods : {
    handleClick () {
      this.isEdit = true
    },
    handleEnter (e) {
      // 当用户按下回车键时,检查输入框内容是否为空
      if(e.target.value.trim() === '') 
      {
        // 若输入框内容为空,弹出警告信息
        return alert('标签内容不能为空!')
      }
      // 不为空时,将修改后的值通过 $emit 发送给父组件
      this.$emit('input', e.target.value)
      // 输入框修改完成后,将 isEdit 设为 false 以隐藏输入框
      this.isEdit = false
    },
  }
}
</script>
  • 模板部分
    • <input... @keyup.enter="handleEnter" />
      • @keyup.enter="handleEnter"
        // 为输入框添加 @keyup.enter 事件监听器,当用户在输入框中按下回车键时,会触发 handleEnter 方法。
  • 脚本部分
    • methods : {...}
      • handleEnter (e) {...}
        • if(e.target.value.trim() === '') {...}
          // 检查输入框中的内容是否为空,如果为空则弹出警告信息,防止输入空标签。
        • this.$emit('input', e.target.value)
          // 当用户输入不为空时,通过 $emit 方法触发 input 事件,并将修改后的值作为参数传递出去,通知父组件标签信息已修改。
        • this.isEdit = false
          // 在输入框修改完成后,将 isEdit 的值置为 false,隐藏输入框。

image-20250118161423408

(二)TableCase 组件

<template>
  <div class="table-case">
    <table class="my-table">
      <thead>
        <tr>
          <th>编号</th>
          <th>名称</th>
          <th>图片</th>
          <th width="100px">标签</th>
        </tr>
      </thead>
      <tbody>
        <tr>
          <td>1</td>
          <td>梨皮朱泥三绝清代小品壶经典款紫砂壶</td>
          <td>
            <img src="https://yanxuan-item.nosdn.127.net/f8c37ffa41ab1eb84bff499e1f6acfc7.jpg" />
          </td>
          <td>
            <!-- 使用 v-model 将 MyTag1 组件的 value 属性和父组件的 tempText 数据进行双向绑定 -->
            <MyTag1 v-model="tempText"></MyTag1>
          </td>
        </tr>
        <tr>
          <td>1</td>
          <td>梨皮朱泥三绝清代小品壶经典款紫砂壶</td>
          <td>
            <img src="https://yanxuan-item.nosdn.127.net/221317c85274a188174352474b859d7b.jpg" />
          </td>
          <td>
            <!-- 使用 v-model 将 MyTag1 组件的 value 属性和父组件的 tempText2 数据进行双向绑定 -->
            <MyTag1 v-model="tempText2"></MyTag1>
          </td>
        </tr>
      </tbody>
    </table>
  </div>
</template>

<script>
import MyTag1 from './components/MyTag1.vue';
export default {
  name: 'TableCase',
  components: {
    MyTag1
  },
  data() {
    return {
      // 存储初始的标签信息,将通过 v-model 传递给 MyTag1 组件
      tempText : '茶具',
      tempText2 : '鞋子',
      goods: [
        {
          id: 101,
          picture:
            'https://yanxuan-item.nosdn.127.net/f8c37ffa41ab1eb84bff499e1f6acfc7.jpg',
          name: '梨皮朱泥三绝清代小品壶经典款紫砂壶',
          tag: '茶具',
        },
        {
          id: 102,
          picture:
            'https://yanxuan-item.nosdn.127.net/221317c85274a188174352474b859d7b.jpg',
          name: '全防水HABU旋钮牛皮户外徒步鞋山宁泰抗菌',
          tag: '男鞋',
        },
        {
          id: 103,
          picture:
            'https://yanxuan-item.nosdn.127.net/cd4b840751ef4f7505c85004f0bebcb5.png',
          name: '毛茸茸小熊出没,儿童羊羔绒背心73-90cm',
          tag: '儿童服饰',
        },
        {
          id: 104,
          picture:
            'https://yanxuan-item.nosdn.127.net/56eb25a38d7a630e76a608a9360eec6b.jpg',
          name: '基础百搭,儿童套头针织毛衣1-9岁',
          tag: '儿童服饰',
        },
      ],
    }
  },
}
</script>
  • 模板部分
    • <td><MyTag1 v-model="tempText"></MyTag1></td><td><MyTag1 v-model="tempText2"></MyTag1></td>
      • v-model="tempText"v-model="tempText2"
        // 使用 v-modelMyTag1 组件的 value 属性和父组件的 tempTexttempText2 数据进行双向绑定。将 tempTexttempText2 的初始值传递给 MyTag1 组件,实现标签信息的初始化显示,同时当 MyTag1 组件内部修改标签信息时,也会更新 tempTexttempText2 的值。
  • 脚本部分
    • data() {...}
      • tempText : '茶具'tempText2 : '鞋子'
        // 存储了初始的标签信息,通过 v-model 传递给 MyTag1 组件,实现标签信息的显示和更新。

总结

MyTag 组件通过 Vue 的 v-ifv-else、自定义指令 v-focus、事件监听器 @dblclick@blur@keyup.enterv-model 等技术,实现了以下四个功能:

  • 双击标签显示输入框,并且输入框自动获取焦点:通过 v-ifv-else 控制输入框的显示和隐藏,利用 @dblclick 事件处理双击操作,结合 v-focus 指令实现输入框自动聚焦。
  • 当输入框失去焦点时,隐藏输入框:使用 @blur 事件监听器,当输入框失去焦点时将 isEdit 置为 false,实现输入框的隐藏。
  • 能够回显标签原本的信息:通过 v-model 实现输入框和父组件数据的双向绑定,使用双大括号插值表达式 {{value}} 回显标签信息。
  • 在输入框中修改内容后,按下回车键可以修改标签信息:利用 @keyup.enter 事件监听器,在用户按下回车键时调用 handleEnter 方法,该方法中通过 $emit 向父组件发送更新事件和修改后的数据,同时将 isEdit 置为 false 隐藏输入框。

注意事项

  • 确保 v-model 的双向绑定正确工作,数据在父组件和子组件之间能准确传递和更新。
  • 对于自定义指令 v-focus,需确保其在 Vue 实例中已正确注册,且作用符合预期。
  • handleEnter 方法中,可根据实际需求添加更多的输入验证逻辑,以确保输入内容的合法性和合理性。

(三)MyTable 组件

1. 能够动态传递表格数据并进行渲染,以适应不同的数据来源。

<template>
  <div class="table-case">
    <MyTable1 :data="goods">
    </MyTable1>
  </div>
</template>

<script>
import MyTable1 from './components/MyTable1.vue';
import MyTag1 from './components/MyTag1.vue';

export default {
  name: 'TableCase',
  components: {
    MyTag1,
    MyTable1
  },
  data() {
    return {
      goods: [
        {
          id: 101,
          picture: 'https://yanxuan-item.nosdn.127.net/f8c37ffa41ab1eb84bff499e1f6acfc7.jpg',
          name: '梨皮朱泥三绝清代小品壶经典款紫砂壶',
          tag: '茶具',
        },
        {
          id: 102,
          picture: 'https://yanxuan-item.nosdn.127.net/221317c85274a188174352474b859d7b.jpg',
          name: '全防水HABU旋钮牛皮户外徒步鞋山宁泰抗菌',
          tag: '男鞋',
        },
        {
          id: 103,
          picture: 'https://yanxuan-item.nosdn.127.net/cd4b840751ef4f7505c85004f0bebcb5.png',
          name: '毛茸茸小熊出没,儿童羊羔绒背心73-90cm',
          tag: '儿童服饰',
        },
        {
          id: 104,
          picture: 'https://yanxuan-item.nosdn.127.net/56eb25a38d7a630e76a608a9360eec6b.jpg',
          name: '基础百搭,儿童套头针织毛衣1-9岁',
          tag: '儿童服饰',
        },
      ],
    }
  },
}
</script>
<template>
  <table class="my-table">
    <tbody>
      <tr v-for="(item, index) in data" :key="item.id">
      </tr>
    </tbody>
  </table>
</template>

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

解释

  • TableCase 组件中:
    • <MyTable1 :data="goods">
      • 这里通过 :data="goods"goods 数组数据传递给 MyTable1 组件。goods 数组存储了表格所需的数据,不同的数据源可以替换这个数组,实现动态传递表格数据的功能,使 MyTable1 组件可以根据不同的数据源进行表格渲染。
  • MyTable1 组件中:
    • props : { data : { type : Array, required : true } }
      • 接收父组件传递过来的 data 属性,要求该属性必须是数组类型,接收的数据将作为表格渲染的数据源,从而实现表格的动态渲染。

2. 表头部分支持用户自定义,满足多样化的展示需求。

<template>
  <div class="table-case">
    <MyTable1 :data="goods">
      <template #head>
        <!-- 用户自定义的表头 -->
        <th>编号</th>
        <th>名称</th>
        <th>图片</th>
        <th width="100px">标签</th>
      </template>
    </MyTable1>
  </div>
</template>
<template>
  <table class="my-table">
    <thead>
      <tr>
        <slot name="head"></slot>
        <!-- 具名插槽 head,用于接收父组件自定义的表头内容 -->
      </tr>
    </thead>
  </table>
</template>

解释

  • TableCase 组件中:
    • <template #head>
      • 使用 #head 具名插槽,用户可以在此自定义表头的内容,这里定义了 “编号”、“名称”、“图片”、“标签” 等表头信息,可根据不同的需求修改这些内容,实现表头的自定义,满足多样化的展示需求。
  • MyTable1 组件中:
    • <slot name="head"></slot>
      • thead 中定义了名为 head 的具名插槽,用于接收父组件传递过来的自定义表头内容。

3. 表格主体部分同样支持用户自定义,方便用户根据业务需求定制表格的显示内容和样式。

<template>
  <div class="table-case">
    <MyTable1 :data="goods">
      <template #body="{item, index}">
        <!-- 用户自定义的表格主体内容,根据 item 和 index 渲染每一行的数据 -->
        <td>{{index + 1}}</td>
        <td>{{item.name}}</td>
        <td>
          <img :src="item.picture" />
        </td>
        <td>
          <MyTag1 v-model="item.tag"></MyTag1>
        </td>
      </template>
    </MyTable1>
  </div>
</template>
<template>
  <table class="my-table">
    <tbody>
      <tr v-for="(item, index) in data" :key="item.id">
        <slot :item="item" :index="index" name="body"></slot>
        <!-- 具名插槽 body,用于接收父组件自定义的表格主体内容,并接收 item 和 index 数据 -->
      </tr>
    </tbody>
  </table>
</template>

解释

  • TableCase 组件中:
    • <template #body="{item, index}">
      • 使用 #body 具名插槽并接收 itemindex 参数,用户可以根据 item(每一行的数据对象)和 index(行索引)来自定义表格主体部分的内容,如这里显示了编号、名称、图片和 MyTag1 组件。用户可以根据业务需求修改此处的内容,定制表格主体的显示内容和样式。
  • MyTable1 组件中:
    • <slot :item="item" :index="index" name="body"></slot>
      • tbody 中定义了名为 body 的具名插槽,接收父组件传递过来的自定义表格主体内容,并将 itemindex 作为参数传递给父组件,方便用户使用这些数据进行定制化开发,以实现表格主体部分的自定义显示。

总结

MyTable 组件通过 props 接收父组件传递的数据,并通过具名插槽的方式支持用户对表头和表格主体部分的自定义,以满足不同的数据来源和多样化的展示需求。在 TableCase 组件中,用户可以通过向 MyTable1 组件传递数据和使用具名插槽来自定义表格的显示内容和样式,实现了以下功能:

  • 动态传递表格数据并进行渲染:使用 :data="goods" 传递数据给子组件,并通过 props 接收数据,实现动态渲染表格。
  • 表头部分支持用户自定义:通过 <template #head> 具名插槽,用户可以自定义表头的显示内容。
  • 表格主体部分支持用户自定义:通过 <template #body="{item, index}"> 具名插槽,用户可以根据 itemindex 来自定义表格主体的显示内容和样式,同时还可以使用其他组件(如 MyTag1)来丰富表格的功能。

image-20250118161504468


http://www.kler.cn/a/515051.html

相关文章:

  • IP协议格式
  • 第二届国赛铁三wp
  • jupyter notebook环境问题
  • RV1126+FFMPEG推流项目源码
  • Golang Gin系列-5:数据模型和数据库
  • C/C++、网络协议、网络安全类文章汇总
  • CentOS 7 安装fail2ban hostdeny方式封禁ip —— 筑梦之路
  • vue和reacts数据响应式的差异
  • Flutter:进步器,数量加减简单使用
  • 1.22双指针刷题
  • NewStar CTF week1 web wp
  • 【AI日记】25.01.22
  • GitLab配置免密登录和常用命令
  • python如何使得pdf加水印后的大小尽可能小
  • Zero-Shot Noise2Noise: Efficient Image Denoising without any Data 笔记
  • NHANES指标推荐:TyG!
  • 2.复写零
  • Vue3 中使用组合式API和依赖注入实现自定义公共方法
  • 洛谷P8195
  • c++算法贪心系列
  • 2024.1.22 安全周报
  • 大华Java开发面试题及参考答案 (下)
  • UE5 开启“Python Remote Execution“
  • 解决go.mod文件中replace不生效的问题
  • Mono里运行C#脚本31—mono_arch_create_generic_trampoline
  • YOLOv10-1.1部分代码阅读笔记-predictor.py