Vue3: Table案列

1、搭建项目基本结构

1.1 初始化项目

  • 在终端运行如下的命令,初始化 vite 项目:

     	npm init vite-app table-demo
    
  • cd 到项目根目录,安装依赖项:

    npm install
    
  • 安装 less 依赖包:

    npm i less -D 
    
  • 使用 vscode或者webstorm 打开项目,并在 vscode或webstorm 集成的终端下运行如下的命令,把项目运行起来:

    npm run dev 
    

1.2 梳理项目结构

  • 重置 App.vue 根组件的代码结构

    <template>
    <div>
    <h1>App 根组件</h1>
    </div>
    </template>
    <script>
    export default {
    name: 'MyApp',
    }
    </script>
    <style lang="less" scoped></style>
    
  • 删除 components 目录下的 HelloWorld.vue 组件

  • 重置 index.css 中的样式:

    :root {
    font-size: 12px;
    }
    body {
    padding: 8px;
    }
    
  • 把资料目录下的 css 文件夹复制、粘贴到 assets 目录中,并在 main.js 入口文件中导入 bootstrap.css :

    import { createApp } from 'vue'
    import App from './App.vue'
    // 导入 bootstrap 样式表
    import './assets/css/bootstrap.css'
    import './index.css'
    createApp(App).mount('#app')
    

2、请求商品列表的数据

  • 运行如下的命令,安装 Ajax 的请求库:

    npm install axios@0.21.0 -S
    
  • 在 main.js 入口模块中,导入并全局配置 axios:

    // 1. 导入 axios
    import axios from 'axios'
    const app = createApp(App)
    // 2. 将 axios 挂载到全局,今后,每个组件中,都可以直接通过
    this.$http 代替 axios 发起 Ajax 请求
    app.config.globalProperties.$http = axios
    // 3. 配置请求的根路径
    axios.defaults.baseURL = 'https://www.escook.cn'
    app.mount('#app')
    
  • 在 App.vue 组件的 data 中声明 goodslist 商品列表数据:

    data() {
    return {
    // 商品列表数据
    goodslist: []
    }
    }
    
  • 在 App.vue 组件的 methods 中声明 getGoodsList 方法,用来从服务器请求商品列表的数据:

    methods: {
    // 初始化商品列表的数据
    async getGoodsList() {
    // 发起 Ajax 请求
    const { data: res } = await this.$http.get('/api/goods')
    // 请求失败
    if (res.status !== 0) return console.log('获取商品列表失
    败!')
    // 请求成功
    this.goodslist = res.data
    }
    }
    
  • 在 App.vue 组件中,声明 created 生命周期函数,并调用 getGoodsList 方法:

    created() {
    this.getGoodsList()
    }
    

3、封装 MyTable 组件

3.1 MyTable 组件的封装要求

  1. 用户通过名为 data 的 prop 属性,为 MyTable.vue 组件指定数据源
  2. 在 MyTable.vue 组件中,预留名称为 header 的具名插槽
  3. 在 MyTable.vue 组件中,预留名称为 body 的作用域插槽

3.2 创建并使用 MyTable 组件

  • 在 components/my-table 目录下新建 MyTable.vue 组件:

    `<template>
    <div>MyTable 组件</div>
    </template>
    <script>
    export default {
    name: 'MyTable',
    }
    </script>
    <style lang="less" scoped></style>`
    
  • 在 App.vue 组件中导入并注册 MyTable.vue 组件:

    // 导入 MyTable 组件
    import MyTable from './components/my-table/MyTable.vue'
    export default {
    name: 'MyApp',
    // ... 省略其它代码
    // 注册 MyTable 组件
    components: {
    MyTable
    }
    }
    
  • 在 App.vue 组件的 DOM 结构中使用 MyTable.vue 组件:

    <template>
    <div>
    <h1>App 根组件</h1>
    <hr />
    <!-- 使用表格组件 -->
    <my-table></my-table>
    </div>
    </template>
    

3.3 为表格声明 data 数据源

  • 在 MyTable.vue 组件的 props 节点中声明表格的 data 数据源:

    export default {
    name: 'MyTable',
    props: {
    // 表格的数据源
    data: {
    type: Array,
    required: true,
    default: [],
    },
    },
    }
    
  • 在 App.vue 组件中使用 MyTable.vue 组件时,通过属性绑定的形式为表格指定 data 数据源:

    <!-- 使用表格组件 -->
    <my-table :data="goodslist"></my-table>
    

3.4 封装 MyTable 组件的模板结构

  • 基于 bootstrap 提供的Tables ,在MyTable.vue 组件中渲染最基本的模板结构:

    <template>
    <table class="table table-bordered table-striped">
    <!-- 表格的标题区域 -->
    <thead>
    <tr>
    <th>#</th>
    <th>商品名称</th>
    <th>价格</th>
    <th>标签</th>
    <th>操作</th>
    </tr>
    </thead>
    <!-- 表格的主体区域 -->
    <tbody></tbody>
    </table>
    </template>
    
  • 为了提高组件的复用性,最好把表格的 标题区域 预留为 具名插槽,方便使用者自定义表格的标题:

    <template>
    <table class="table table-bordered table-striped">
    <!-- 表格的标题区域 -->
    <thead>
    <tr>
    <!-- 命名插槽 -->
    <slot name="header"></slot>
    </tr>
    </thead>
    <!-- 表格的主体区域 -->
    <tbody></tbody>
    </table>
    </template>
    
  • 在 App.vue 组件中,通过具名插槽的形式,为 MyTable.vue 组件指定标题名称:

    <!-- 使用表格组件 -->
    <my-table :data="goodslist">
    <!-- 表格的标题 -->
    <template v-slot:header>
    <th>#</th>
    <th>商品名称</th>
    <th>价格</th>
    <th>标签</th>
    <th>操作</th>
    </template>
    </my-table>
    

    3.5 预留名称为 body 的作用域插槽

  • 在 MyTable.vue 组件中,通过 v-for 指令循环渲染表格的数据行:

    <template>
    <table class="table table-bordered table-striped">
    <thead>
    <tr>
    <slot name="header"></slot>
    </tr>
    </thead>
    <!-- 表格的主体区域 -->
    <tbody>
    <!-- 使用 v-for 指令,循环渲染表格的数据行 -->
    <tr v-for="(item, index) in data" :key="item.id"></tr>
    </tbody>
    </table>
    </template>
    
  • 为了提高 MyTable.vue 组件的复用性,最好把表格数据行里面的 td 单元格预留为 具名插槽。示例代码如下:

    <template>
    <table class="table table-bordered table-striped">
    <thead>
    <tr>
    <slot name="header"></slot>
    </tr>
    </thead>
    <!-- 表格的主体区域 -->
    <tbody>
    <!-- 使用 v-for 指令,循环渲染表格的数据行 -->
    <tr v-for="(item, index) in data" :key="item.id">
    <!-- 为数据行的 td 预留的插槽 -->
    <slot name="body"></slot>
    </tr>
    </tbody>
    </table>
    </template>
    
  • 为了让组件的使用者在提供 body 插槽的内容时,能够自定义内容的渲染方式,需要把body 具名插槽升级为 作用域插槽 :

    <template>
    <table class="table table-bordered table-striped">
    <thead>
    <tr>
    <slot name="header"></slot>
    </tr>
    </thead>
    <!-- 表格的主体区域 -->
    <tbody>
    <!-- 使用 v-for 指令,循环渲染表格的数据行 -->
    <tr v-for="(item, index) in data" :key="item.id">
    <!-- 为数据行的 td 预留的“作用域插槽” -->
    <slot name="body" :row="item" :index="index"></slot>
    </tr>
    </tbody>
    </table>
    </template>
    
  • 在 App.vue 组件中,基于作用域插槽的方式渲染表格的数据

    		<!-- 使用表格组件 -->
    		<my-table :data="goodslist">
    		<!-- 表格的标题 -->
    		<template v-slot:header>
    		<th>#</th>
    		<th>商品名称</th>
    		<th>价格</th>
    		<th>标签</th>
    		<th>操作</th>
    		</template>
    		<!-- 表格每行的单元格 -->
    		<template v-slot:body="{ row, index }">
    		<td>{{ index + 1 }}</td>
    		<td>{{ row.goods_name }}</td>
    		<td>{{ row.goods_price }}</td>
    		<td>{{ row.tags }}</td>
    		<td>
    		<button type="button" class="btn btn-danger btn-sm">删除
    		</button>
    		</td>
    		</template>
    		</my-table>
    

4、实现删除功能

  • 为删除按钮绑定 click事件

    	<td>
    	<button type="button" class="btn btn-danger btn-sm"
    	@click="onRemove(row.id)">删除</button>
    	</td>
    
  • 在 App.vue 组件的 methods 中声明事件处理函数如下:

    methods: {
    // 根据 Id 删除商品信息
    onRemove(id) {
    this.goodslist = this.goodslist.filter(x => x.id !== id)
    },
    }
    

5、实现添加标签的功能

5.1 自定义渲染标签列

  • 根据 bootstrap 提供的Badge效果,循环渲染商品的标签信息如下:
```clike
<td>
<span class="badge badge-warning ml-2" v-for="item in row.tags"
:key="item">{{tag}}</span>
</td>
```

5.2 实现 input 和 button 的按需展示

  • 使用 v-if 结合 v-else 指令,控制 input 和 button 的按需展示:

    <td>
    <!-- 基于当前行的 inputVisible,来控制 input 和 button 的按需展
    示-->
    <input type="text" class="form-control form-control-sm ipt-tag"
    v-if="row.inputVisible">
    <button type="button" class="btn btn-primary btn-sm" v-
    else>+Tag</button>
    <span class="badge badge-warning ml-2" v-for="item in row.tags"
    :key="item">{{item}}</span>
    </td>
    
  • 点击按钮,控制 input 和 button 的切换:

    <td>
    <!-- 基于当前行的 inputVisible,来控制 input 和 button 的按需展
    示-->
    <input type="text" class="form-control form-control-sm ipt-tag"
    v-if="row.inputVisible" />
    <button type="button" class="btn btn-primary btn-sm" v-else
    @click="row.inputVisible = true">+Tag</button>
    <span class="badge badge-warning ml-2" v-for="item in row.tags"
    :key="item">{{item}}</span>
    </td>
    

5.3 让 input 自动获取焦点

  • 在 App.vue 组件中,通过 directives 节点自定义 v-focus 指令如下:

    directives: {
    // 封装自动获得焦点的指令
    focus(el) {
    el.focus()
    },
    }
    
  • 为 input 输入框应用 v-focus 指令:

    <input type="text" class="form-control ipt-tag form-control-sm" v-
    if="row.inputVisible" v-focus />
    

5.4 文本框失去焦点自动隐藏

  • 使用 v-model 指令把 input 输入框的值双向绑定到 row.inputValue 中:

    <input
    type="text"
    class="form-control ipt-tag form-control-sm"
    v-if="row.inputVisible"
    v-focus
    v-model.trim="row.inputValue"
    />
    
  • 监听文本框的 blur 事件,在触发其事件处理函数时,把 当前行的数据 传递进去:

    	<input
    	type="text"
    	class="form-control ipt-tag form-control-sm"
    	v-if="row.inputVisible"
    	v-focus
    	v-model.trim="row.inputValue"
    	@blur="onInputConfirm(row)"
    	/>
    
  • 在 App.vue 组件的 methods 节点下声明 onInputConfirm 事件处理函数:

    onInputConfirm(row) {
    	// 1. 把用户在文本框中输入的值,预先转存到常量 val 中
    	const val = row.inputValue
    	// 2. 清空文本框的值
    	row.inputValue = ''
    	// 3. 隐藏文本框
    	row.inputVisible = false
    	}
    

5.5 为商品添加新的 tag 标签

  • 进一步修改 onInputConfirm 事件处理函数如下:

    onInputConfirm(row) {
    // 把用户在文本框中输入的值,预先转存到常量 val 中
    const val = row.inputValue
    // 清空文本框的值
    row.inputValue = ''
    // 隐藏文本框
    row.inputVisible = false
    // 1.1 判断 val 的值是否为空,如果为空,则不进行添加
    // 1.2 判断 val 的值是否已存在于 tags 数组中,防止重复添加
    if (!val || row.tags.indexOf(val) !== -1) return
    // 2. 将用户输入的内容,作为新标签 push 到当前行的 tags 数组中
    row.tags.push(val)
    }
    

5.6 响应文本框的回车按键

  • 当用户在文本框中敲击了 回车键 的时候,也希望能够把当前输入的内容添加为 tag 标签。此时,可以为文本框绑定 keyup 事件如下:

    <input
    type="text"
    class="form-control ipt-tag form-control-sm"
    v-if="row.inputVisible"
    v-focus
    v-model.trim="row.inputValue"
    @blur="onInputConfirm(row)"
    @keyup.enter="onInputConfirm(row)"
    />
    

5.7 响应文本框的 esc 按键

  • 当用户在文本框中敲击了 esc 按键的时候,希望能够快速清空文本框的内容。此时,可以为文本框绑定 keyup 事件如下:

    <input
    type="text"
    class="form-control ipt-tag form-control-sm"
    v-if="row.inputVisible"
    v-focus
    v-model.trim="row.inputValue"
    @blur="onInputConfirm(row)"
    @keyup.enter="onInputConfirm(row)"
    @keyup.esc="row.inputValue = ''"
    />
    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值