文章目录
- 需求说明
- 1.my-tag组件封装(完成初始化)
- 2.may-tag封装(控制显示隐藏)
- 3.my-tag组件封装(v-model处理:信息修改)
- 4.my-table组件封装(整个表格)
- ①数据不能写死,动态传递表格渲染的数据
- ②结构不能写死 - 多处结构自定义【具名插槽】
- 案例完整代码:
- 1.App.vue
- 2.MyTag.vue
- 3.MyTable.vue
- 4.Main.js
需求说明
- my-tag标签封装组件
- 双击显示输入框,输入框获取焦点
- 失去焦点,隐藏输入框
- 回显标签信息
- 修改内容,回车 → 修改标签信息
- my-table表格组件封装
- 动态传递数据渲染
- 表头支持用户自定义
- 主题支持用户自定义
1.my-tag组件封装(完成初始化)
App/vue
<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>
<!-- 标签组件 -->
<MyTag></MyTag>
</td>
</tr>
<tr>
<td>1</td>
<td>梨皮朱泥三绝清代小品壶经典款紫砂壶</td>
<td>
<img src="https://yanxuan-item.nosdn.127.net/221317c85274a188174352474b859d7b.jpg" />
</td>
<td>
<!-- 标签组件 -->
</td>
</tr>
</tbody>
</table>
</div>
</template>
<script>
// may-tag 标签组件的封装
//1.创建组件 - 初始化
//2.实现功能
import MyTag from './components/MyTag.vue'
export default {
name: 'TableCase',
components: {
MyTag
},
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>
MyTag.vue(标签组件)
<template>
<div class="my-tag">
<!-- <input
class="input"
type="text"
placeholder="输入标签"
/> -->
<div class="text">
茶具
</div>
</div>
</template>
2.may-tag封装(控制显示隐藏)
上面这两个不能同时显示,用到
v-if 和 v-else
双击事件:@dblclick=""
自动聚焦:
法①$nextTick => $ref
s 获取到dom,进行focus
获取焦点
法②封装v-focus
指令
失去焦点,隐藏输入框:@blur
操作isEdit = false
MyTag.vue
<template>
<div class="my-tag">
<input
v-if="isEdit"
v-focus
ref="inp"
class="input"
type="text"
placeholder="输入标签"
@blur="isEdit=false"
/>
<div v-else @dblclick="handleClick" class="text">
茶具
</div>
</div>
</template>
<script>
export default {
data () {
return {
isEdit:false
}
},
methods:{
handleClick () {
//双击后,切换到显示状态
this.isEdit = true
// //等dom更新完,再获取焦点
// this.$nextTick(() => {
// this.$refs.inp.focus()
// }) 每次都需要获取焦点,所以可以把它封装(全局)
}
}
}
</script>
全局注册
main.js
Vue.config.productionTip = false
//封装全局指令 focus
Vue.directive('focus',{
//inserted是指令所在的dom元素,被插入到页面中时触发
inserted (el) {
el.focus()
}
})
3.my-tag组件封装(v-model处理:信息修改)
- 回显标签信息:回显的标签信息是父组件传过来的,用v-model实现功能(简化代码)
v-model
=>:value
和@input
组件内部通过props接收"value设置给输入框
- 内容修改了,回车 => 修改标签信息
·@keyup.enter·,回车时触发input
事件。
$emit
写了一个input
,并且把输入框的值实时拿到并提交,用到e.target.value→$emit('input',e.target.value)
以下代码只与此部分有关,上节已展示过的代码不再加入
App.vue
<template>
<div class="table-case">
//两个组件标签都放进去就可以了
<td>
<!-- 标签组件 -->
<MyTag v-model="tempText"></MyTag>
</td>
<td>
<!-- 标签组件 -->
<MyTag v-model="tempText2"></MyTag>
</td>
</div>
</template>
<script>
// may-tag 标签组件的封装
//1.创建组件 - 初始化
//2.实现功能
import MyTag from './components/MyTag.vue'
export default {
name: 'TableCase',
components: {
MyTag
},
data() {
return {
//测试组件功能的临时数据
tempText:'水杯',
tempText2:'钢笔',
}
}
</script>
~.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 () {
//双击后,切换到显示状态
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>
4.my-table组件封装(整个表格)
- 数据不能写死,动态传递表格渲染的数据
- 结构不能写死 - 多处结构自定义【具名插槽】
(1). 表头支持自定义
(2).主体支持自定义
①数据不能写死,动态传递表格渲染的数据
<template>
<table class="my-table">
<thead>
<tr>
<th>编号</th>
<th>名称</th>
<th>图片</th>
<th width="100px">标签</th>
</tr>
</thead>
<tbody>
<tr v-for="(item,index) in data" :key="item.id">
<td>{{ index + 1 }}</td>
<td>{{ item.name }}</td>
<td>
<img :src="item.picture" />
</td>
<td>
标签组件
<!-- <MyTag v-model="tempText"></MyTag> -->
</td>
</tr>
</tbody>
</table>
</template>
<script>
export default {
props:{
data:{
type:Array,
required:true
}
}
}
</script>
②结构不能写死 - 多处结构自定义【具名插槽】
App.vue
<template>
<div class="table-case">
<MyTable :data="goods">
<template #head>
<tr>
<th>编号</th>
<th>名称</th>
<th>图片</th>
<th width="100px">标签</th>
</tr>
</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>
~.vue
<template>
<table class="my-table">
<thead>
<slot name="head"></slot>
</thead>
<tbody>
<tr v-for="(item,index) in data" :key="item.id">
<slot name="body" :item="item" :index="index"></slot>
</tr>
</tbody>
</table>
</template>
案例完整代码:
1.App.vue
<template>
<div class="table-case">
<MyTable :data="goods">
<template #head>
<tr>
<th>编号</th>
<th>名称</th>
<th>图片</th>
<th width="100px">标签</th>
</tr>
</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>
// may-tag 标签组件的封装
//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:
'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 scoped>
.table-case {
width: 1000px;
margin: 50px auto;
img {
width: 100px;
height: 100px;
object-fit: contain;
vertical-align: middle;
}
}
</style>
2.MyTag.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 () {
//双击后,切换到显示状态
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 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>
3.MyTable.vue
<template>
<table class="my-table">
<thead>
<slot name="head"></slot>
</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>
.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 0.5s;
&.red {
color: red;
}
}
.none {
height: 100px;
line-height: 100px;
color: #999;
}
}
</style>
4.Main.js
import Vue from 'vue'
import App from './App.vue'
Vue.config.productionTip = false
//封装全局指令 focus
Vue.directive('focus',{
//inserted是指令所在的dom元素,被插入到页面中时触发
inserted (el) {
el.focus()
}
})
new Vue({
render: h => h(App),
}).$mount('#app')