标签组件封装
1.双击显示,自动聚焦2.失去焦点,隐藏输入框
标签一列,不同行的标签内容不同,但是除此之外其他基本一致,所以选择用 标签组件 将这一部分封装为一个组件,需要时组件标签展示。首先标签处一进去就是显示的 “茶具” 双击之后才显示输入框。所以输入框和 “茶具”的显示两者是互斥的,用 v-if 和 v-else 来控制。
v-if 传值为“isEdit” 如果为 false 就显示 “茶具”,(最初定义的就是false,也就是一开始进去就是 “茶具”,什么时候会变为true继而显示输入框呢),给v-else 也就是“茶具”所在的元素注册一个双击事件,双击之后 isEdit由false变为true,就会显示输入框了。
接下来是聚焦,想要实现的是双击“茶具”出现输入框之后就自动进行聚焦。开始做法是给输入框dom元素添加ref属性,在显示输入框之后this.isEdit=true,通过$refs立刻获取焦点。但是vue是异步更新,输入框显示但是dom并没有进行更新,所以出错。这时,通过$nextTick检测dom元素更新完成之后就立刻获取焦点。
每次想要实现聚焦效果都要写这一段代码,很是麻烦,可以将这个效果进行指令封装,并全局注册,需要时直接添加指令即可。
输入框和茶具不能一同显示,两个是互斥关系,用v-if和v-else
指令封装,dom元素插入页面自动聚焦或者
输入框失去焦点(显示茶具)
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><!-- 标签组件 --><MyTag></MyTag></td></tr></tbody></table></div> </template><script> // my-tag 标签组建的封装 // 1.创建组件 - 初始化 // 2.实现功能 // (1)双击显示,并且自动聚焦 // v-if v-else @dbclick // &nextTick =>$refs获取到dom ,进行focus获取焦点 // 封装v-focus指令 // (2)失去焦点,隐藏输入框 // (3)回显标签信息 // (4)内容修改了,回车 =》 修改标签信息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><style lang="less" scoped> .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 0.5s;&.red {color: red;}}.none {height: 100px;line-height: 100px;color: #999;}}} </style>
MyTag.vue
<template> <div class="my-tag"><input v-if="isEdit"v-focusref="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// // // 通过ref找到元素,立刻获取焦点// // this.$refs.inp.focus()// // 异步更新,切换到显示状态后,dom并没有进行更新,立即获取焦点实际是获取不到的// // 等dom更新王再获取焦点// this.$nextTick(() => {// this.$refs.inp.focus()// })// // 每次都要点focus进行获取焦点很麻烦,可以将这套指令封装为指令,在main.js中进行全局注册}}} </script><style lang="less" 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>
main.js
3.回显标签信息4.内容修改了,回车 =>修改标签信息
回显的标签信息是父组件传递过来的,在data中定义数据tempText,在子组件标签中通过v-model帮绑定该数据,子组件中通过props接收该数据值value,显示为还未双击之前的值,双击之后输入框出现,显示为输入框回显的值 :value="value"。修改输入框中的内容回车之后希望显示修改后的内容。也就是将修改后的数据传送到父组件,父组件再传送回子组件进行显示。通过键盘回车绑定事件,将用户输入数据传送回父组件,e.target能拿到事件源,也就是dom元素,e.target.value能拿到用户输入的值。
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 v-model="tempText"></MyTag></td></tr><tr><td>1</td><td>梨皮朱泥三绝清代小品壶经典款紫砂壶</td><td><img src="https://yanxuan-item.nosdn.127.net/221317c85274a188174352474b859d7b.jpg" /></td><td><!-- 标签组件 --><MyTag></MyTag></td></tr></tbody></table></div> </template><script> // my-tag 标签组建的封装 // 1.创建组件 - 初始化 // 2.实现功能 // (1)双击显示,并且自动聚焦 // v-if v-else @dbclick // &nextTick =>$refs获取到dom ,进行focus获取焦点 // 封装v-focus指令 // (2)失去焦点,隐藏输入框// (3)回显标签信息 // 回显的标签信息是父组件传递过来的 // v-model实现功能(简化代码) v-model => :value 和 @input(事件监听) // 组件内部通过props接收,:value设置给输入框 // (4)内容修改了,回车 =》 修改标签信息 // @keyup.enter,触发事件$emit('input',e.target.value)import MyTag from './components/MyTag.vue' export default {name: 'TableCase',components: {MyTag,},data() {return {// 测试组件功能临时数据tempText:'茶壶', //希望子组件内容能跟这个数据进行双向绑定,这里传入啥,子组件就显示啥;子组件回车又能修改到这里的数据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> .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 0.5s;&.red {color: red;}}.none {height: 100px;line-height: 100px;color: #999;}}} </style>
MyTag.vue
<template> <div class="my-tag"><!-- v-model不能跟父组件传递过来的数据进行直接绑定,一旦绑定,就相当于直接在修改父组件传递过来的数据,子组件泵你直接修改父组件传递过来的数据 --><!-- :value="value"相当于父组件传递过来什么值,子组件就显示什么值并进行绑定,该值就回显了 --><input v-if="isEdit"v-focusref="inp"class="input"type="text"placeholder="输入标签"@blur="isEdit = false":value="value"@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// // // 通过ref找到元素,立刻获取焦点// // this.$refs.inp.focus()// // 异步更新,切换到显示状态后,dom并没有进行更新,立即获取焦点实际是获取不到的// // 等dom更新王再获取焦点// this.$nextTick(() => {// this.$refs.inp.focus()// })// // 每次都要点focus进行获取焦点很麻烦,可以将这套指令封装为指令,在main.js中进行全局注册},handleEnter(e){// 非空处理if (e.target.value.trim() === '') return alert('标签不能为空')// 需要子传父,回车时,输入框的内容提交给父组件更新,// 父组件是v-model,触发事件,需要触发input事件this.$emit('input',e.target.value)// 或者this.$refs.inp.value// e.target拿到的是触发事件的事件源,也就是dom元素,想拿到输入框输入的值,直接.value就是了// 提交完成,关闭输入状态}}} </script><style lang="less" 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>
my-table表格组件封装
表格中表头和中间体不能写死,类似的表格还有很多,可以复用。
中间体:表格组件中接收父组件传过来的数据,进行遍历显示。因为该部分不能固定着,以便将来能复用,所以将该部分td表格中的内容用slot具名插槽进行占位,并将td表格中内容剪切到父组件的表格组件标签中,用template标签接收插槽名。还因为该部分遍历的数据item和index在表格组件里,所以需要用slot以属性的方式将这两个数据打包成对象传到父组件,父组件直接解构接收。
表头:直接将标头的内容剪切到父组件的子组件标签中,并在子组件中使用具名插槽占位
并且在表格组件标签的“标签”一列显示 MyTag标签组件标签,将其v-model绑定的数据改为item中的tagApp.vue
<template><div class="table-case"><MyTable :data="goods"><tempalte :slot="head"><th>>编号</th><th>>图片</th><th>>名称</th><th width="100px">标签</th></tempalte><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> // my-tag 标签组建的封装 // 1.创建组件 - 初始化 // 2.实现功能 // (1)双击显示,并且自动聚焦 // v-if v-else @dbclick // &nextTick =>$refs获取到dom ,进行focus获取焦点 // 封装v-focus指令 // (2)失去焦点,隐藏输入框// (3)回显标签信息// 回显的标签信息是父组件传递过来// v-moedl实现功能(简化代码) v-model => :value 和 @input// 组件内部通过props接收,:value设置给输入框 // (4)内容修改了,回车 =》 修改标签信息// @keyup.enter,触发事件$emit('input',r.target.value)// my-tag 表格组件的封装 // 1.数据不能写死,动态传递表格渲染的数据 // 2.结构不能写死 - 多处结构自定义// (1)表头支持自定义// (2)主体支持自定义 import MyTag from './components/MyTag.vue' import MyTable from './components/MyTable.vue'export default {// 测试组件功能临时文本tempText:'茶壶',name: 'TableCase',components: {MyTag,MyTable,},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><style lang="less" scoped> .table-case {width: 1000px;margin: 50px auto;img {width: 100px;height: 100px;object-fit: contain;vertical-align: middle;}} </style>
MyTable.vue
<template><table class="my-table"><thead><tr><slot name="head"></slot></tr></thead><tbody><tr v-for="(item,index) in data" :key="item.di"><slot :item="item" :index="index"></slot></tr> </tbody></table> </template><script> export default {props:{data:{type:Array,required:true,}} } </script><style lang="less">.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>
MyTag.vue
<template><div class="my-tag"><input v-if="isEdit"v-focusref="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// // // 通过ref找到元素,立刻获取焦点// // this.$refs.inp.focus()// // 异步更新,切换到显示状态后,dom并没有进行更新,立即获取焦点实际是获取不到的// // 等dom更新王再获取焦点// this.$nextTick(() => {// this.$refs.inp.focus()// })// // 每次都要点focus进行获取焦点很麻烦,可以将这套指令封装为指令,在main.js中进行全局注册},handleEnter(e){if (e.target.value.trim() === '') return alert('标签不能为空')// 子传父,将回车时,输入框的内容提交给父组件更新// 由于父组件是v-model,所以触发事件需要触发input事件this.$emit('input',e.target.value)// 提交完成,关闭输入状态this.isEdit = false}}}</script><style lang="less" 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>
补充:在输入框中的 :value = "value",是指将父组件通过v-model传过来的value值绑定给输入框自定义的属性 :value。后面e.target.value也就是获取元素对象属性value的值。