当前位置: 首页 > article >正文

【vue】⾃定义指令+插槽+商品列表案例

代码获取

07-⾃定义指令+插槽+商品列表案例

⼀、⾃定义指令

1. 基本使⽤

1.1 指令介绍
  • 内置指令:v-model、v-for、v-bind、v-on… 这都是Vue给咱们内置的⼀些指令,可以直接使⽤

  • ⾃定义指令:同时Vue也⽀持让开发者,⾃⼰注册⼀些指令。这些指令被称为⾃定义指令

1.2 作⽤

封装⼀段 公共的DOM操作 代码,便于复⽤

1.3 语法
  1. 注册
// main.js 中
app.directive('指令名', {
    // 元素挂载后(成为真实DOM) ⾃动触发⼀次⾃动执⾏
    mounted(el) {
        // el: 指令所在的DOM元素
    }
})
  1. 使⽤
<p v-指令名></p>
1.4 代码⽰例

需求:当⻚⾯加载时, 让元素获取焦点

// main.js
// 注册全局指令

app.directive('focus', {
    mounted(el) {
        console.log(el)// input 元素
        // 聚焦
        el.focus()
    }
})

App.vue

<script setup></script>
<template>
	<div class="app">
		<input type="text" v-focus />
	</div>
</template>
1.5 总结
  1. ⾃定义指令的作⽤是什么?

​ 答:封装⼀段公共的 DOM操作 的代码

  1. 使⽤⾃定义指令的步骤是哪两步?

​ 答: 先注册、后使⽤

  1. 指令配置选项中的 mounted 钩⼦何时执⾏?

​ 答:元素 挂载后 (成为DOM树的⼀部分时) ⾃动执⾏

2. 绑定数据

2.1 需求

实现⼀个 color 指令 :传⼊不同的颜⾊, 给标签设置⽂字颜⾊

2.2 语法

1.在绑定指令时,可以通过“等号”的形式为指令 绑定 具体的参数值

<div v-color="colorStr">Some Text</div>

2.通过 binding.value 可以拿到指令值,指令值修改会 触发 updated 钩⼦

app.directive('指令名', {
    // 挂载后⾃动触发⼀次
    mounted(el, binding) { },
    // 数据更新, 每次都会执⾏
    updated(el, binding) { }
})
2.3 代码⽰例
// main.js
app.directive('color', {
    mounted(el, binding) {
        el.style.color = binding.value
    },
    updated(el, binding) {
        el.style.color = binding.value
    }
})

App.vue

<script setup>
import { ref } from 'vue'
// 颜⾊
const colorStr = ref('red')
</script>

<template>
	<p v-color="colorStr"></p>
</template>
2.4 简化形式

对于⾃定义指令来说,⼀个很常⻅的情况是仅仅需要在 mounted 和 updated 上实现相同的⾏为。这种情况下我们可以直接⽤⼀个函数来定义指令,如下所⽰:

app.directive('color', (el, binding) => {
    // 这会在 `mounted` 和 `updated` 时都调⽤
    el.style.color = binding.value
})

3. 封装v-loading指令

3.1 场景

实际开发过程中,发送请求需要时间,在请求的数据未回来时,⻚⾯会处于空⽩状态 , ⽤⼾体验不好

3.2 解决⽅案

封装⼀个 v-loading 指令,实现加载中的效果

3.3 分析
  1. 本质 loading效果就是⼀个蒙层,盖在了盒⼦上

  2. 数据请求中,开启loading状态,添加蒙层

  3. 数据请求完毕,关闭loading状态,移除蒙层

3.4 实现
  1. 准备⼀个 loading类,通过伪元素定位,设置宽⾼,实现蒙层

  2. 开启关闭 loading状态(添加移除蒙层),本质只需要添加移除类即可

  3. 结合⾃定义指令的语法进⾏封装复⽤

3.5 代码实现

App.vue

<script setup>
import axios from 'axios'
import { ref } from 'vue'

// 新闻列表
const newsList = ref([])
const isFinish = ref(false)

getNewsData()


// 获取新闻列表
async function getNewsData() {
	isFinish.value = false
	// 请求新闻数据
	const resp = await axios.get('http://localhost:4000/api/news')
	// 保存数据
	newsList.value = resp.data.data

	// 睡眠1秒
	await new Promise((resolve) => {
		setTimeout(() => {
			resolve()
		}, 1000)
	})

	isFinish.value = true
}
</script>

<template>
	<div class="box">
		<ul>
			<li v-for="item in newsList" :key="item.id" class="news" v-loading="!isFinish">
				<div class="left">
					<div class="title">{{ item.title }}</div>
					<div class="info">
						<span>{{ item.source }}</span>
						<span>{{ item.time }}</span>
					</div>
				</div>
				<div class="right">
					<img :src="item.img" alt="img" />
				</div>
			</li>
		</ul>
	</div>
</template>

<style>
.loading:before {
	z-index: 999;
	content: '';
	position: absolute;
	left: 0;
	top: 0;
	width: 100%;
	height: 100%;
	background: #fff url('./assets/loading.gif') no-repeat center;
	background-size: auto;
}

.box {
	position: relative;
	width: 800px;
	min-height: 600px;
	margin: 10px auto;
	border-radius: 5px;
}

.news {
	display: flex;
	margin: 0 auto;
	padding: 20px 0;
	cursor: pointer;
}

.news .left {
	flex: 1;
	display: flex;
	flex-direction: column;
	justify-content: space-between;
	padding-right: 10px;
}

.news .left .title {
	font-size: 20px;
}

.news .left .info {
	color: #999999;
}

.news .left .info span {
	margin-right: 20px;
}

.news .right {
	width: 160px;
	height: 120px;
}

.news .right img {
	width: 100%;
	height: 100%;
	object-fit: cover;
}
</style>

main.js

import App from "./App.vue";
import { createApp } from "vue";

const app = createApp(App);

// main.js
app.directive('loading', (el, binding) => {
    if (binding.value) {
        el.classList.add('loading')
    } else {
        el.classList.remove('loading')
    }
})

app.mount("#app");

⼆、插槽

插槽分类

  • 默认插槽

  • 具名插槽

  • 作⽤域插槽

1. 默认插槽

1.1 作⽤

让组件内部的⼀些 结构 ⽀持 ⾃定义

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

1.2 需求

将需要多次显⽰的对话框,封装成⼀个组件

1.3 问题

组件的内容部分,不希望写死,希望能使⽤的时候⾃定义。怎么办

1.4 插槽的基本语法
  1. 组件内需要定制的结构部分,改⽤ <slot></slot> 占位

  2. 使⽤组件时, <MyDialog></MyDialog>写成双标签 , 包裹结构, 传⼊替换slot

1.5 代码⽰例

MyDialog.vue

<script setup>
</script>
<template>
    <div class="dialog">
        <div class="dialog-header">
            <h3>友情提⽰</h3>
            <span class="close">✖️</span>
        </div>

        <div class="dialog-content">
            <slot></slot>
        </div>
        <div class="dialog-footer">
            <button>取消</button>
            <button>确认</button>
        </div>
    </div>
</template>

<style scoped>
* {
    margin: 0;
    padding: 0;
}

.dialog {
    width: 470px;
    height: 230px;
    padding: 0 25px;
    background-color: #ffffff;
    margin: 40px auto;
    border-radius: 5px;
}

.dialog-header {
    height: 70px;
    line-height: 70px;
    font-size: 20px;
    border-bottom: 1px solid #ccc;
    position: relative;
}

.dialog-header .close {
    position: absolute;
    right: 0px;
    top: 0px;
    cursor: pointer;
}

.dialog-content {
    height: 80px;
    font-size: 18px;
    padding: 15px 0;
}

.dialog-footer {
    display: flex;
    justify-content: flex-end;
}

.dialog-footer button {
    width: 65px;
    height: 35px;
    background-color: #ffffff;
    border: 1px solid #e1e3e9;
    cursor: pointer;
    outline: none;
    margin-left: 10px;
    border-radius: 3px;
}

.dialog-footer button:last-child {
    background-color: #007acc;
    color: #fff;
}
</style>

App.vue

<script setup>
import MyDialog from '@/components/MyDialog.vue';
</script>

<template>
	<div>
		<MyDialog>
			你确定要删除吗?
		</MyDialog>

		<MyDialog>
			你确定要退出吗?
		</MyDialog>
	</div>
</template>

<style scoped></style>
1.6 总结
  1. 组件内某⼀部分结构不确定,想要⾃定义怎么办?

​ 答:使⽤ 插槽 (技术)

  1. 插槽的步骤分为哪⼏步?

​ 答:两步; 先占位、后传⼊

2. 插槽默认值

2.1 问题

通过插槽完成了内容的定制,传什么显⽰什么, 但是如果不传,则是空⽩

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

能否给插槽设置 默认显⽰内容 呢?

2.2 解决⽅案

封装组件时,可以为 <slot></slot> 提供默认内容

2.3 语法

在 <slot></slot> 标签内,放置内容, 作为默认内容

2.4 效果
  • 使⽤组件时,不传,则会显⽰slot的默认内容

  • 使⽤组件时,传了,则slot整体会被换掉, 从⽽显⽰传⼊的

2.5 总结
  1. 插槽默认内容有什么⽤?

​ 答:使⽤组件不传内容时, 防⽌出现空⽩

  1. 默认内容何时显⽰?何时不显⽰?

​ 答:使⽤组件时, 不传内容,则显⽰默认内容; 否则显⽰传递的内容

3. 具名插槽

3.1 需求

⼀个组件内有多处结构,需要外部传⼊标签,进⾏定制

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

上⾯的弹框中有三处不同,但是默认插槽只能定制⼀处内容,这时怎么办?

3.2 具名插槽语法
  • 多个slot使⽤name属性区分名字

  • template配合v-slot:名字 来分发对应标签

3.3 v-slot的简写

v-slot写起来太⻓,vue给我们提供⼀个简单写法 v-slot: 直接简写为 #

3.4 代码实现

MyDialog.vue

<script setup>
</script>
<template>
    <div class="dialog">
        <div class="dialog-header">
            <slot name="header">
                <h3>友情提⽰</h3>
            </slot>
            <span class="close">✖️</span>
        </div>

        <div class="dialog-content">
            <slot name="main">默认值</slot>
        </div>
        <div class="dialog-footer">
            <slot name="footer">
                <button>取消</button>
                <button>确定</button>
            </slot>
        </div>
    </div>
</template>

<style >
* {
    margin: 0;
    padding: 0;
}

.dialog {
    width: 470px;
    height: 230px;
    padding: 0 25px;
    background-color: #ffffff;
    margin: 40px auto;
    border-radius: 5px;
}

.dialog-header {
    height: 70px;
    line-height: 70px;
    font-size: 20px;
    border-bottom: 1px solid #ccc;
    position: relative;
}

.dialog-header .close {
    position: absolute;
    right: 0px;
    top: 0px;
    cursor: pointer;
}

.dialog-content {
    height: 80px;
    font-size: 18px;
    padding: 15px 0;
}

.dialog-footer {
    display: flex;
    justify-content: flex-end;
}

.dialog-footer button {
    width: 65px;
    height: 35px;
    background-color: #ffffff;
    border: 1px solid #e1e3e9;
    cursor: pointer;
    outline: none;
    margin-left: 10px;
    border-radius: 3px;
}

.dialog-footer button:last-child {
    background-color: #007acc;
    color: #fff;
}
</style>

App.vue

<script setup>
import MyDialog from '@/components/MyDialog.vue';
</script>

<template>
	<MyDialog>
		<template #header>
			<h3>友情提示</h3>

		</template>
		<template #main>
			<p>请输入正确手机号</p>

		</template>
		<template #footer>
			<button>取消</button>
			<button>确定</button>
		</template>
	</MyDialog>

	<MyDialog>
		<template #header>
			<h3>警告</h3>
		</template>
		<template #main>
			<p>你确定要退出吗</p>
		</template>
		<template #footer>
			<button>取消</button>
			<button>确定</button>
		</template>
	</MyDialog>
</template>



<style scoped></style>
3.5 总结
  1. 组件内有多处不确定的结构 怎么办?

​ 答:具名插槽

  1. 具名插槽的使⽤语法是什么?

​ 答:1、 <slot name=“名字”> 默认内容 </slot>

​ 2、 <template #名字> 要展⽰的内容 </template>

4. 作⽤域插槽

4.1 作⽤

带数据的插槽, 可以让组件功能更强⼤、更灵活、复⽤性更⾼; ⽤ slot 占位的同时, 还可以给 slot 绑定数据,将来使⽤组件时, 不仅可以传内容, 还能使⽤ slot 带来的数据

4.2 场景

封装表格组件

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

4.3 使⽤步骤
  1. 给 slot 标签, 以添加属性的⽅式传值
<slot a="hello" :b="666"></slot>
  1. 所有添加的属性, 都会被收集到⼀个对象中
{ a: 'hello', b: 666 }
  1. 在template中, 通过 #插槽名= “obj” 接收,默认插槽名为 default
<!-- obj会收集 slot 上绑定的所有⾃定义属性 -->
<template #default="obj">
{{ obj }}
</template>
4.4 静态代码

components/MyTable.vue

<script setup>

</script>

<template>
    <table class="my-table">
        <thead>
            <tr>
                <th>序号</th>
                <th>姓名</th>
                <th>年纪</th>
                <th>操作</th>
            </tr>
        </thead>
        <tbody>
            <tr>
                <td>1</td>
                <td>赵⼩云</td>
                <td>19</td>
                <td>
                    <button>查看</button>
                </td>
            </tr>
            <tr>
                <td>1</td>
                <td>张⼩花</td>
                <td>19</td>
                <td>
                    <button>查看</button>
                </td>
            </tr>
            <tr>
                <td>1</td>
                <td>孙⼤明</td>
                <td>19</td>
                <td>
                    <button>查看</button>
                </td>
            </tr>
        </tbody>
    </table>
</template>

<style>
.my-table {
    width: 450px;
    text-align: center;
    border: 1px solid #ccc;
    font-size: 24px;
    margin: 30px auto;
}

.my-table thead {
    background-color: #1f74ff;
    color: #fff;
}

.my-table thead th {
    font-weight: normal;
}

.my-table thead tr {
    line-height: 40px;
}

.my-table th,
.my-table td {
    border-bottom: 1px solid #ccc;
    border-right: 1px solid #ccc;
}

.my-table td:last-child {
    border-right: none;
}

.my-table tr:last-child td {
    border-bottom: none;
}

.my-table button {
    width: 65px;
    height: 35px;
    font-size: 18px;
    border: 1px solid #ccc;
    outline: none;
    border-radius: 3px;
    cursor: pointer;
    background-color: #ffffff;
    margin-left: 5px;
}
</style>

App.vue

<script setup>
import { ref } from 'vue'

import MyTable from './components/MyTable.vue'

const tableData1 = ref([
	{ id: 11, name: '狗蛋', age: 18 },
	{ id: 22, name: '⼤锤', age: 19 },
	{ id: 33, name: '铁棍', age: 17 }
])

const tableData2 = ref([
	{ id: 21, name: 'Jack', age: 18 },
	{ id: 32, name: 'Rose', age: 19 },
	{ id: 43, name: 'Henry', age: 17 }
])
</script>
<template>
	<MyTable />

	<MyTable />
</template>

<style>
body {
	background-color: #fff;
}
</style>
4.5 代码实现

MyTable

<script setup>
const proper = defineProps({
    data: {
        type: Array,
        default: () => []
    }
})
</script>

<template>
    <table class="my-table">
        <thead>
            <tr>
                <th>序号</th>
                <th>姓名</th>
                <th>年纪</th>
                <th>操作</th>
            </tr>
        </thead>
        <tbody v-for="(item, index) in data" :key="item.id">
            <tr>
                <td>{{index + 1}}</td>
                <td>{{item.name}}</td>
                <td>{{item.age}}</td>
                <td>
                    <slot :index="index">
                        <button>none</button>
                    </slot>
                </td>
            </tr>

        </tbody>
    </table>
</template>

<style>
.my-table {
    width: 450px;
    text-align: center;
    border: 1px solid #ccc;
    font-size: 24px;
    margin: 30px auto;
}

.my-table thead {
    background-color: #1f74ff;
    color: #fff;
}

.my-table thead th {
    font-weight: normal;
}

.my-table thead tr {
    line-height: 40px;
}

.my-table th,
.my-table td {
    border-bottom: 1px solid #ccc;
    border-right: 1px solid #ccc;
}

.my-table td:last-child {
    border-right: none;
}

.my-table tr:last-child td {
    border-bottom: none;
}

.my-table button {
    width: 65px;
    height: 35px;
    font-size: 18px;
    border: 1px solid #ccc;
    outline: none;
    border-radius: 3px;
    cursor: pointer;
    background-color: #ffffff;
    margin-left: 5px;
}
</style>

App.vue

<script setup>
import { ref } from 'vue'

import MyTable from './components/MyTable.vue'

const tableData1 = ref([
	{ id: 11, name: '狗蛋', age: 18 },
	{ id: 22, name: '⼤锤', age: 19 },
	{ id: 33, name: '铁棍', age: 17 }
])

const tableData2 = ref([
	{ id: 21, name: 'Jack', age: 18 },
	{ id: 32, name: 'Rose', age: 19 },
	{ id: 43, name: 'Henry', age: 17 }
])

const showDetail = (index) => {
	alert('查看详情: ' + JSON.stringify(tableData2.value[index]))
}

</script>
<template>
	<MyTable :data="tableData1" >
		<template #default="{ index }">
			<button @click="tableData1.splice(index, 1)">删除</button>
		</template>
	</MyTable>

	<MyTable :data="tableData2">
		<template #default="{ index }">
			<button @click="showDetail(index)">查看</button>
		</template>
	</MyTable>

</template>

<style>
body {
	background-color: #fff;
}
</style>
4.6 总结
  1. 作⽤域插槽的作⽤是什么?

​ 答:让插槽带数据, 使得组件功能更强⼤、更灵活

  1. 作⽤域插槽的使⽤步骤是什么?

    答:1、 slot上绑定数据

​ 2、 <template #名字=“{ 数据 }”> </template>

三、综合案例

1. 整体效果和分析

1.1 整体效果
image-20241015161639285
1.2 结构分析
image-20241015161714190
1.3 需求说明

1、my-table 表格组件封装

  • 动态传递表格数据渲染

  • 表头⽀持⽤⼾⾃定义

  • 主体⽀持⽤⼾⾃定义

2、my-tag 标签组件封装

  • 双击显⽰输⼊框,输⼊框获取焦点

  • 失去焦点,隐藏输⼊框

  • 回显标签信息

  • 内容修改, 回⻋修改标签信息

2. 封装 MyTable 并渲染

2.1 静态代码

components/MyTable.vue

<script setup>
</script>

<template>
    <table class="my-table">
        <thead>
            <tr>
                <th>编号</th>
                <th>名称</th>
                <th>图⽚</th>
                <th width="100px">标签</th>
            </tr>
        </thead>
        <tbody>
            <tr>
                <td>1</td>
                <td>⽑茸茸⼩熊出没,⼉童⽺羔绒背⼼73-90cm</td>
                <td>
                    <img src="https://yanxuanitem.nosdn.127.net/cd4b840751ef4f7505c85004f0bebcb5.png" />
                </td>
                <td>
                    标签内容1
                </td>
            </tr>
            <tr>
                <td>2</td>
                <td>⽑茸茸⼩熊出没,⼉童⽺羔绒背⼼73-90cm</td>
                <td>
                    <img src="https://yanxuanitem.nosdn.127.net/cd4b840751ef4f7505c85004f0bebcb5.png" />
                </td>
                <td>
                    标签内容2
                </td>
            </tr>
        </tbody>
    </table>
</template>

<style lang="scss" scoped>
.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;
    }
}
</style>

App.vue

<script setup>
import { ref } from 'vue'
import MyTable from './components/MyTable.vue'

// 商品列表
const goodsList = ref([
	{
		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>
<template>
	<MyTable />
</template>

<style lang="scss">
#app {
	width: 1000px;
	margin: 50px auto;

	img {
		width: 100px;
		height: 100px;
		object-fit: contain;
		vertical-align: middle;
	}
}
</style>

3. MyTable插槽⾃定义

MyTable.vue

<script setup>
// 接受父组件传递的商品列表
const props = defineProps({
    goodsList: {
        type: Array,
        required: true
    }
})
</script>

<template>
    <table class="my-table">
        <thead>

            <slot name="theadSlot"></slot>
        </thead>
        <tbody v-for="(goods, index) in goodsList" :key="goods.id">
            <slot name="tbodySlot" :goods="goods" :index="index"></slot>
        </tbody>
    </table>
</template>

<style lang="scss"  >
 .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;
    }
}
</style>

App.vue

<script setup>
import { ref } from 'vue'
import MyTable from './components/MyTable.vue'

// 商品列表
const goodsList = ref([
	{
		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>
<template>
	<MyTable :goodsList="goodsList" >
		<template #theadSlot>
			<tr>
				<th>编号</th>
				<th>名称</th>
				<th>图⽚</th>
				<th width="100px">标签</th>
				
			</tr>
		</template>
		<template #tbodySlot="{ goods, index }">
			<tr>
				<td>{{ index + 1 }}</td>
				<td>{{ goods.name }}</td>
				<td>
					<img :src="goods.picture" />
				</td>
				<td>
					{{ goods.tag }}
				</td>

			</tr>
		</template>

	</MyTable>

</template>

<style lang="scss">
#app {
	width: 1000px;
	margin: 50px auto;

	img {
		width: 100px;
		height: 100px;
		object-fit: contain;
		vertical-align: middle;
	}
}
</style>

4. 封装MyTag组件

MyTag.vue

<!-- @format -->

<script setup>
	import { ref } from 'vue'

	const model = defineModel()

	// 是否处于编辑状态
	const isEdit = ref(false)

	// 双击
	const onDblClick = () => {
		isEdit.value = true
	}

	// 在输入框上敲击了回车
	const onEnter = (e) => {
		// 获取输入框的值,并去除首尾空格
		const tagName = e.target.value.trim()
		if (tagName) {
			// 如果有值,需要把这个同步到父组件中(直接修改 model )
			model.value = tagName
		}
		// 敲完回车,不管输入框有没有值,都要让输入框消失
		isEdit.value = false
	}
</script>

<template>
	<div class="my-tag">
		<input
			v-focus
			v-if="isEdit"
			class="input"
			type="text"
			:value="model"
			@blur="isEdit = false"
			placeholder="输入标签"
			@keydown.enter="onEnter" />
		<div
			class="text"
			v-else
			@dblclick="onDblClick">
			{{ model }}
		</div>
	</div>
</template>

<style lang="scss" 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>

总体代码获取

e lang=“scss”>
#app {
width: 1000px;
margin: 50px auto;

img {
	width: 100px;
	height: 100px;
	object-fit: contain;
	vertical-align: middle;
}

}




### 4. 封装MyTag组件

`MyTag.vue`

```vue
<!-- @format -->

<script setup>
	import { ref } from 'vue'

	const model = defineModel()

	// 是否处于编辑状态
	const isEdit = ref(false)

	// 双击
	const onDblClick = () => {
		isEdit.value = true
	}

	// 在输入框上敲击了回车
	const onEnter = (e) => {
		// 获取输入框的值,并去除首尾空格
		const tagName = e.target.value.trim()
		if (tagName) {
			// 如果有值,需要把这个同步到父组件中(直接修改 model )
			model.value = tagName
		}
		// 敲完回车,不管输入框有没有值,都要让输入框消失
		isEdit.value = false
	}
</script>

<template>
	<div class="my-tag">
		<input
			v-focus
			v-if="isEdit"
			class="input"
			type="text"
			:value="model"
			@blur="isEdit = false"
			placeholder="输入标签"
			@keydown.enter="onEnter" />
		<div
			class="text"
			v-else
			@dblclick="onDblClick">
			{{ model }}
		</div>
	</div>
</template>

<style lang="scss" 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>

总体代码获取


http://www.kler.cn/news/356544.html

相关文章:

  • Windows git 配置
  • HarmonyOS NEXT 应用开发实战(五、页面的生命周期及使用介绍)
  • 人工智能 MiniCPM-V-8B-2.6:单图、多图、视频多模态大模型
  • js 鼠标拖动canvas画布
  • RHCE第三次笔记SSH
  • ParallelsDesktop20最新版本虚拟机 一键切换系统 游戏娱乐两不误
  • 【服务器虚拟化】
  • linux一二三章那些是重点呢
  • SCI英文文献阅读工具【全文翻译】【逐句翻译】
  • python 猜数字游戏
  • Tomcat日志文件详解及catalina.out日志清理方法
  • 鸿蒙ArkTS实用开发技巧: 提高效率的关键知识点
  • 12.个人博客系统(Java项目基于spring和vue)
  • 尚硅谷rabbitmq 2024 Federation配置 第60节答疑
  • 【如何获取股票数据10】Python、Java等多种主流语言实例演示获取股票行情api接口之沪深A股历史分时KDJ数据获取实例演示及接口API说明文档
  • 「从零开始的 Vue 3 系列」:第十二章——Element Plus 组件的二次封装实践(保姆式)
  • 母婴商城(论文+源码)_kaic
  • 音视频入门基础:H.264专题(18)——AVCDecoderConfigurationRecord简介
  • conda打包
  • Vue 3中集成Element Plus组件库