实现步骤
① 搭建项目的基本结构
② 请求商品列表的数据
③ 封 装 MyTable 组 件
④ 实现删除功能
⑤ 实现添加标签的功能
1. 搭建项目基本结构
1.1初始化项目
1.在终端运行如下的命令,初始化 vite 项:
npm init vite-app table-demo
2.cd到项目根目录,安装依赖项:
npm install
3.安装less依赖包:
npm i less -D
4.打开项目,并在终端下运行如下的命令,把项目运行起来:
npm run dev
1.2梳理项目结构
1.重置 App.vue 根组件的代码结构:
<template>
<div>
<h1>App 根组件</h1>
</div>
</template>
<script>
export default {
name: 'MyApp', }
</script>
<style lang="less" scoped></style>
2.删除 components 目录下的 HelloWorld.vue 组件
3.重置 index.css 中的样式:
:root {
font-size: 12px; }
body {
padding: 8px; }
4.把资料目录下的 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.请求商品列表的数据
1.运行如下的命令,安装 Ajax 的请求库:
npm install axios@0.21.0 -S
2.在 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')
3.在 App.vue 组件的 data 中声明 goodslist 商品列表数据:
data() {
return {
// 商品列表数据
goodslist: []
}
}
4.在 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
}
}
5.在 App.vue 组件中,声明 created 生命周期函数,并调用 getGoodsList 方法:
created() {
this.getGoodsList()
}
3.封装 MyTable 组件
3.0 MyTable 组件的封装要求
1.用户通过名为 data 的 prop 属性,为 MyTable.vue 组件指定数据源
2.在 MyTable.vue 组件中,预留名称为 header 的具名插槽
3.在 MyTable.vue 组件中,预留名称为 body 的作用域插槽
3.1 创建并使用 MyTable 组件
1.在 components/my-table 目录下新建 MyTable.vue 组件:
<template>
<div>MyTable 组件</div>
</template> <script>
export default {
name: 'MyTable',
}
</script> <style lang="less" scoped></style>
2.在 App.vue 组件中导入并注册 MyTable.vue 组件:
// 导入 MyTable 组件
import MyTable from './components/my-table/MyTable.vue'
export default {
name: 'MyApp',
// ... 省略其它代码
// 注册 MyTable 组件
components: {
MyTable
}
}
3.在 App.vue 组件的 DOM 结构中使用 MyTable.vue 组件:
<template>
<div>
<h1>App 根组件</h1>
<hr />
<!-- 使用表格组件 -->
<my-table></my-table>
</div>
</template>
3.2 为表格声明 data 数据源
1.在 MyTable.vue 组件的 props 节点中声明表格的 data 数据源:
export default {
name: 'MyTable',
props: {
// 表格的数据源
data: {
type: Array,
required: true,
default: [],
},
},
}
2.在 App.vue 组件中使用 MyTable.vue 组件时,通过属性绑定的形式为表格指定 data 数据源:
<!-- 使用表格组件 -->
<my-table :data="goodslist"></my-table>
3.3 封装 MyTable 组件的模板结构
1.基于 bootstrap 提供的Tables( https://v4.bootcss.com/docs/content/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>
2.为了提高组件的复用性,最好把表格的 标题区域 预留为 <slot> 具名插槽,方便使用者自定义表格的标题:
<template>
<table class="table table-bordered table-striped">
<!-- 表格的标题区域 -->
<thead>
<tr>
<!-- 命名插槽 -->
<slot name="header"></slot>
</tr>
</thead>
<!-- 表格的主体区域 -->
<tbody></tbody>
</table>
</template>
<!-- 使用表格组件 -->
<my-table :data="goodslist">
<!-- 表格的标题 -->
<template v-slot:header>
<th>#</th>
<th>商品名称</th>
<th>价格</th>
<th>标签</th>
<th>操作</th>
</template>
</my-table>
3.4 预留名称为 body 的作用域插槽
1.在 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>
2.为了提高 MyTable.vue 组件的复用性,最好把表格数据行里面的 td 单元格预留为 <slot> 具名插槽。示例代码如下:
<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>
<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>
<!-- 使用表格组件 -->
<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.实现删除功能
1.为删除按钮绑定 click 事件处理函数:
<td>
<button type="button" class="btn btn-danger btn-sm"
@click="onRemove(row.id)">删除</button>
</td>
2.在 App.vue 组件的 methods 中声明事件处理函数如下:
methods: {
// 根据 Id 删除商品信息
onRemove(id) {
this.goodslist = this.goodslist.filter(x => x.id !== id)
},
}
5.实现添加标签的功能
5.1 自定义渲染标签列
<td>
<span class="badge badge-warning ml-2" v-for="item in row.tags"
:key="item">{{tag}}</span>
</td>
5.2 实现 input 和 button 的按需展示
1.使用 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" velse>+Tag</button>
<span class="badge badge-warning ml-2" v-for="item in row.tags"
:key="item">{{item}}</span>
</td>
2.点击按钮,控制 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 自动获取焦点
1.在 App.vue 组件中,通过 directives 节点自定义 v-focus 指令如下:
directives: {
// 封装自动获得焦点的指令
focus(el) {
el.focus()
},
}
2.为 input 输入框应用 v-focus 指令:
<input type="text" class="form-control ipt-tag form-control-sm" v-
if="row.inputVisible" v-focus />
5.4 文本框失去焦点自动隐藏
1.使用 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"
/>
2.监听文本框的 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)"
/>
3.在 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 响应文本框的回车按键
<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 按键
<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 = ''"
/>