Vue.js 2 项目实战(九):商品列表

前言

Vue.js 是一个用于构建用户界面的渐进式 JavaScript 框架。它的设计初衷是通过采用简洁且强大的结构,使前端开发变得更简单和高效。以下是对 Vue.js 的详细介绍:

核心特性

  1. 声明式渲染

    • Vue.js 使用声明式语法来描述用户界面,通过数据绑定实现视图的自动更新。
  2. 组件系统

    • Vue.js 提供了一个灵活的组件系统,可以将 UI 拆分为可复用的独立组件,每个组件包含自己的模板、逻辑和样式。
  3. 单文件组件 (SFC)

    • Vue.js 允许将 HTML、CSS 和 JavaScript 放在一个 .vue 文件中,这种单文件组件的结构使得组件开发更加直观和模块化。
  4. 虚拟 DOM

    • Vue.js 使用虚拟 DOM 来优化视图更新,通过最小化实际 DOM 操作,提高性能。
  5. 反应式数据绑定

    • Vue.js 提供了响应式的数据绑定系统,当数据变化时,视图会自动更新。
  6. 指令

    • Vue.js 提供了一组内置指令,如 v-bindv-modelv-for,可以轻松操作 DOM。

优点

  1. 易于上手

    • Vue.js 的学习曲线较低,适合新手入门,并且有详细的文档和教程。
  2. 灵活性

    • Vue.js 可以逐步采用,可以用于小部分的功能增强,也可以用于构建复杂的单页面应用(SPA)。
  3. 高性能

    • 虚拟 DOM 和响应式系统使得 Vue.js 在处理数据密集型操作时表现优异。
  4. 强大的生态系统

    • Vue.js 生态系统丰富,包括 Vue Router、Vuex、Vue CLI 等,提供了强大的开发工具支持。
  5. 活跃的社区

    • Vue.js 拥有一个活跃的社区,有大量的插件、教程和支持资源。

知识点

// my-tag 标签组件的封装
// 1. 创建组件 - 初始化
// import 语句用于导入其他文件或模块
import MyTag from "./components/MyTag.vue";

// export default 是 ES6 模块的导出语法,表示将对象作为模块的默认导出。这里导出的是一个对象,它定义了 Vue 组件的选项
export default {
  // name: 'TableCase' 定义了组件的名称
  name: 'TableCase',
  // components 是一个对象,用于注册子组件
  components: {
    MyTag
  }

// 2. 实现功能
//    (1) 双击显示,并且自动聚焦
//      v-if v-else @dblclick(双击) 操作 isEdit 自动聚焦:
//      $nextTick =>$refs 获取到dom,进行 focus 获取焦点
//      封装 v-focus 指令在 main.js
//    (2) 失去焦点,隐藏输入框
//      @blur(失去焦点) 操作 isEdit 即可
//    (3) 回显标签信息
//      回显的标签信息是父组件传递过来的
//      v-model 实现功能(简化代码) v-model=>:value 和 @input 组件内部通过 props 接收
//      :value 设置给输入框内容修改了
//    (4) 内容修改了,回车 => 修改标签信息
//      @keyup.enter,触发事件 $emit('input',e.target.value)


// 1.my-tag 标签组件封装
// (1)双击显示输入框,输入框获取焦点
// (2)失去焦点,隐藏输入框
// (3)回显标签信息
// (4)内容修改,回车→修改标签信息

// 2.my-table 表格组件封装
// (1)动态传递表格数据渲染
// (2)表头支持用户自定义
// (3)主体支持用户自定义

项目效果

源代码

App.vue

<template>
  <div class="table-case">
    <MyTable :data="goods">
      <!-- 使用 <template #head> 来指定插入到子组件 head 插槽的内容 -->
      <template #head>
        <th>编号</th>
        <th>图片</th>
        <th>名称</th>
        <th>标签</th>
      </template>
      <!-- slotProps:这是一个插槽属性对象,包含了父组件通过插槽传递给子组件的数据 -->
      <template #body="slotProps">
        <td>{{ slotProps.index + 1 }}</td>
        <td><img :src="slotProps.item.picture"  alt="#"/></td>
        <td>{{ slotProps.item.name }}</td>
        <td>
          <!-- 父传子 -->
          <MyTag v-model="slotProps.item.tag"></MyTag>
        </td>
      </template>
    </MyTable>

  </div>
</template>

<script>
// my-tag 标签组件的封装
// 1. 创建组件 - 初始化
// 2. 实现功能
//    (1) 双击显示,并且自动聚焦
//      v-if v-else @dblclick(双击) 操作 isEdit 自动聚焦:
//      $nextTick =>$refs 获取到dom,进行 focus 获取焦点
//      封装 v-focus 指令在 main.js
//    (2) 失去焦点,隐藏输入框
//      @blur(失去焦点) 操作 isEdit 即可
//    (3) 回显标签信息
//      回显的标签信息是父组件传递过来的
//      v-model 实现功能(简化代码) v-model=>:value 和 @input 组件内部通过 props 接收
//      :value 设置给输入框内容修改了
//    (4) 内容修改了,回车 => 修改标签信息
//      @keyup.enter,触发事件 $emit('input',e.target.value)

// 1.my-tag 标签组件封装
// (1)双击显示输入框,输入框获取焦点
// (2)失去焦点,隐藏输入框
// (3)回显标签信息
// (4)内容修改,回车→修改标签信息

// 2.my-table 表格组件封装
// (1)动态传递表格数据渲染
// (2)表头支持用户自定义
// (3)主体支持用户自定义

// import 语句用于导入其他文件或模块
// import MyTag from "./components/MyTag.vue";
import MyTable from "@/components/MyTable.vue";
import MyTag from "@/components/MyTag.vue";

// export default 是 ES6 模块的导出语法,表示将对象作为模块的默认导出。这里导出的是一个对象,它定义了 Vue 组件的选项
export default {
  // name: 'TableCase' 定义了组件的名称
  name: 'TableCase',
  // components 是一个对象,用于注册子组件
  components: {
    MyTag,
    MyTable
  },
  data () {
    return {
      // 测试功能临时数据
      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>

<style lang="less" scoped>

</style>

MyTable.vue

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

<template>
  <table class="my-table">
    <thead>
    <tr>
      <!-- <slot> 是一种用于组件内容分发的机制,允许你将内容从父组件传递到子组件 -->
      <!-- <slot> 元素被称为插槽(slot),它定义了子组件中可以插入内容的位置 -->
      <!-- name="head":这是一个可选的属性,用于为插槽指定一个名称。通过名称,你可以在父组件中指定哪些内容应该插入到这个插槽中 -->
      <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>

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

.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;
  }
}

.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>

MyTag.vue

<script>
  export default {
    data () {
      return {
        isEdit: false
      }
    },
    props: {
      value: String
    },
    methods: {
      handleClick () {
        // 双击显示
        this.isEdit = true
        // this.$nextTick 是 Vue 实例的一个方法,用于延迟回调的执行,直到下次 DOM 更新完成后(原因是 Vue 是异步更新)
        // 等 dom 更新完了再聚焦
        // this.$nextTick(() => {
        //   // 立刻获取焦点
        //   this.$refs.inp.focus()
        // })
        // 因为是重复调用所以可以封装到全局指令 v-focus
      },

      // 参数 e 是事件对象
      handleEnter (e) {
        // 非空处理
        if (e.target.value.trim() === '') return alert('标签内容不能为空')

        // 子传父
        // 由于父组件是 v-model,触发事件,需要触发 input 事件
        // e.target.value:将输入框的值作为参数传递给事件
        this.$emit('input', e.target.value)
        // 修改完后隐藏输入框
        this.isEdit = false
      }
    }
  }
</script>

<template>
  <div class="my-tag">
    <!-- v-if v-else 控制输入框 -->

    <!-- ref="inp" 解析:-->
    <!-- ref 属性用于注册引用信息。通过给 DOM 元素或子组件添加 ref 属性,你可以在组件的实例中通过 this.$refs 访问到对应的 DOM 元素或子组件实例 -->
    <!-- ref="inp" 表示将输入框元素注册为组件实例的 $refs 对象中的 inp 属性。因此,你可以在组件的方法中通过 this.$refs.inp 访问到这个输入框元素 -->

    <!-- 失去焦点关闭输入框 -->
    <!-- @blur 是一种事件监听的简写语法,用于监听 DOM 元素上的失去焦点(blur)事件 -->

    <!-- :value:这是 Vue 的属性绑定语法,用于将 DOM 元素的属性值与 Vue 实例的数据属性绑定 -->
    <input v-if="isEdit" v-focus @blur="isEdit = false" :value="value" @keyup.enter="handleEnter"
      class="input"
      type="text"
      placeholder="输入标签"
    />
    <!-- @dblclick 是一个事件监听的简写语法,用于监听 DOM 元素上的双击事件 -->
    <div class="text" v-else @dblclick="handleClick">{{ value }}</div>
  </div>
</template>

<style scoped lang="less">
.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>

main.js

import Vue from 'vue'
import App from './App.vue'

Vue.config.productionTip = false

// 封装全局指令 focus
// Vue.directive 是 Vue 的一个全局 API,用于注册全局自定义指令
// 'focus' 是指令的名称,这里定义了一个名为 focus 的指令
// { ... } 是一个对象,定义了该指令的钩子函数
Vue.directive('focus', {
  // inserted 钩子在被绑定元素插入到 DOM 中时被调用
  // el 是钩子函数的参数,表示被绑定的 DOM 元素
  // el.focus() 是一个 DOM 方法,用于将焦点移动到指定的元素上。在这个钩子函数中,一旦元素被插入到 DOM 中,就会自动调用 focus 方法,使得该元素获得焦点
  inserted(el) {
    el.focus()
  }
})

new Vue({
  render: h => h(App),
}).$mount('#app')
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Suc2es2

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值