1. 进阶语法
1.1 v-model 简化代码
App.vue
html
复制代码
<template>
<!-- 11-src-下拉封装 -->
<div class="app">
<!-- <BaseSelect :cityId="selectId" @changeId="handleChangeId"></BaseSelect> -->
<!-- v-model 简化代码 → 父组件 v-model 简化代码,实现 子组件 和 父组件数据 双向绑定 -->
<!-- 1. 子组件中:props 通过 value 接收,事件触发 input -->
<!-- 2. 父组件中:v-model 给组件直接绑数据 -->
<BaseSelect :cityId="selectId" v-model="selectId"></BaseSelect>
</div>
</template>
<script>
import BaseSelect from "./components/BaseSelect.vue";
export default {
data() {
return {
selectId: "102",
};
},
components: {
BaseSelect,
},
methods: {
/* handleChangeId(id) {
this.selectId = id;
}, */
},
/* watch: {
selectId(newval) {
console.log(newval);
},
}, */
};
</script>
<style>
</style>
BaseSelect.vue
html
复制代码
<template>
<div>
<!--
表单类组件封装 & v-model 简化代码
实现子组件和父组件数据的双向绑定 (实现App.vue中的selectId和子组件选中的数据进行双向绑定)
下拉菜单 → value 和 input 事件的语法糖
model 不能用 → 双向绑定,代表要修改数据 → cityId来自于父组件,子组件不能直接修改
-->
<select :value="cityId" @change="changeId">
<option value="101">北京</option>
<option value="102">上海</option>
<option value="103">武汉</option>
<option value="104">广州</option>
<option value="105">深圳</option>
</select>
</div>
</template>
<script>
export default {
props: {
cityId: String,
},
methods: {
changeId(e) {
// 通知父组件修改数据 → 当前下拉菜单的value值
// this.$emit("changeId", e.target.value);
this.$emit("input", e.target.value);
},
},
};
</script>
<style>
</style>
1.2 sync 修饰符
App.vue
html
复制代码
<template>
<!-- 12-src-sync修饰符 -->
<div class="app">
<button @click="isShow = true">退出按钮</button>
<!-- isShow.sync => :isShow="isShow" @update:isShow="isShow=$event" -->
<!-- <BaseDialog v-show="isShow"></BaseDialog> -->
<!-- <BaseDialog :isShow="isShow" @closeDialog="handleClose"></BaseDialog> -->
<!--
.sync 修饰符 → 可以实现 子组件 与 父组件数据 的 双向绑定,简化代码
应用场景:封装弹框类的基础组件, visible属性 true显示 false隐藏
.sync修饰符 就是 :属性名 和 @update:属性名 合写
-->
<BaseDialog :isShow.sync="isShow"></BaseDialog>
</div>
</template>
<script>
import BaseDialog from "./components/BaseDialog.vue";
export default {
data() {
return {
isShow: false,
};
},
methods: {
/* handleClose(newVal) {
this.isShow = newVal;
}, */
},
components: {
BaseDialog,
},
};
</script>
<style>
</style>
BaseDialog.vue
html
复制代码
<template>
<div class="base-dialog-wrap" v-show="isShow">
<div class="base-dialog">
<div class="title">
<h3>温馨提示:</h3>
<button class="close" @click="close">x</button>
</div>
<div class="content">
<p>你确认要退出本系统么?</p>
</div>
<div class="footer">
<button>确认</button>
<button>取消</button>
</div>
</div>
</div>
</template>
<script>
export default {
props: {
isShow: Boolean,
},
methods: {
close() {
// this.$emit("closeDialog", false);
this.$emit("update:isShow", false);
},
},
};
</script>
<style scoped>
.base-dialog-wrap {
width: 300px;
height: 200px;
box-shadow: 2px 2px 2px 2px #ccc;
position: fixed;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
padding: 0 10px;
}
.base-dialog .title {
display: flex;
justify-content: space-between;
align-items: center;
border-bottom: 2px solid #000;
}
.base-dialog .content {
margin-top: 38px;
}
.base-dialog .title .close {
width: 20px;
height: 20px;
cursor: pointer;
line-height: 10px;
}
.footer {
display: flex;
justify-content: flex-end;
margin-top: 26px;
}
.footer button {
width: 80px;
height: 40px;
}
.footer button:nth-child(1) {
margin-right: 10px;
cursor: pointer;
}
</style>
1.3 ref 和 $refs
1.3.1 获取 dom
App.vue
html
复制代码
<template>
<!-- 13-src-ref-DOM -->
<div class="app">
<div class="base-chart-box">父组件</div>
<BaseChart></BaseChart>
</div>
</template>
<script>
import BaseChart from "./components/BaseChart.vue";
export default {
components: {
BaseChart,
},
};
</script>
<style>
.base-chart-box {
width: 300px;
height: 200px;
}
</style>
BaseChart.vue
html
复制代码
<template>
<!--
ref 和 $refs
作用:利用 ref 和 $refs 可以用于 获取 dom 元素, 或 组件实例
特点:查找范围 → 当前组件内 (更精确稳定)
-->
<!-- 1. 目标标签 -- 添加 ref 属性 -->
<div class="base-chart-box" ref="chartEl">子组件</div>
</template>
<script>
import * as echarts from "echarts";
export default {
mounted() {
// 基于准备好的dom,初始化echarts实例
// document.querySelector 会查找项目中所有的元素
// $refs只会在当前组件查找盒子
// var myChart = echarts.init(document.querySelector('.base-chart-box'))
// 2. 恰当时机, 通过 this.$refs.xxx, 获取目标标签
var myChart = echarts.init(this.$refs.chartEl);
// 绘制图表
myChart.setOption({
title: {
text: "ECharts 入门示例",
},
tooltip: {},
xAxis: {
data: ["衬衫", "羊毛衫", "雪纺衫", "裤子", "高跟鞋", "袜子"],
},
yAxis: {},
series: [
{
name: "销量",
type: "bar",
data: [5, 20, 36, 10, 10, 20],
},
],
});
},
};
</script>
<style scoped>
.base-chart-box {
width: 400px;
height: 300px;
border: 3px solid #000;
border-radius: 6px;
}
</style>
1.3.2 获取组件
App.vue
html
复制代码
<template>
<!-- 14-src-ref-组件 -->
<div class="app">
<!-- 通过这种方式,可以在父组件中直接操作子组件的方法和数据,这在需要跨组件通信时非常有用。 -->
<!-- 1. 目标组件 -- 添加 ref 属性 -->
<BaseForm ref="myForm"></BaseForm>
<button @click="getValues">获取数据</button>
<button @click="resetValues">重置数据</button>
</div>
</template>
<script>
import BaseForm from "./components/BaseForm.vue";
export default {
components: {
BaseForm,
},
methods: {
getValues() {
// 2. 恰当时机, 通过 this.$refs.xxx, 获取目标组件,就可以调用组件对象里面的方法
// this.$refs.myForm → BaseForm 组件的 Vue 实例对象
console.log(this.$refs.myForm); // BaseForm vue对象
console.log(this.$refs.myForm.getValues()); // {username: '12', password: '12'}
},
resetValues() {
this.$refs.myForm.resetValues();
},
},
};
</script>
<style>
.app {
width: 300px;
height: 200px;
border: 1px solid #000;
border-radius: 10px;
padding-top: 30px;
padding-left: 30px;
}
button {
margin-top: 50px;
margin-left: 30px;
}
</style>
html
复制代码
<template>
<form action="">
账号:<input type="text" v-model="username" /><br /><br />
密码:<input type="text" v-model="password" />
</form>
</template>
<script>
export default {
data() {
return {
username: "",
password: "",
};
},
methods: {
getValues() {
return {
username: this.username,
password: this.password,
};
},
resetValues() {
this.username = "";
this.password = "";
},
},
};
</script>
<style>
</style>
1.4 Vue异步更新、$nextTick
html
复制代码
<template>
<!-- 15-src-$nextTick -->
<div class="app">
<div v-if="isShowEdit">
<input type="text" v-model="editValue" ref="ipt" />
<button @click="hide">确认</button>
</div>
<div v-else>
<span>{{ title }}</span>
<button @click="show">编辑</button>
</div>
</div>
</template>
<script>
export default {
data() {
return {
title: "大标题",
editValue: "",
isShowEdit: false,
};
},
methods: {
// 需求:编辑标题, 编辑框自动聚焦
show() {
// 点击编辑,显示编辑框
this.isShowEdit = true;
// 让编辑框,立刻获取焦点
// 1. 报错 Cannot read properties of undefined (reading 'focus')
// ! Vue 是 异步更新 DOM (提升性能)
// this.$refs.ipt.focus();
// 2. 延迟慢 体验差
/* setTimeout(()=>{
this.$refs.ipt.focus();
},1500) */
// 3. Vue异步更新、$nextTick
// → 等 DOM 更新后, 才会触发执行此方法里的函数体
// 语法: this.$nextTick(函数体)
this.$nextTick(() => {
this.$refs.ipt.focus();
});
},
hide() {
this.isShowEdit = false;
},
},
};
</script>
<style>
span {
font-size: 30px;
margin-right: 30px;
}
</style>
2. 自定义指令
2.1 全局&局部注册
main.js
javascript
复制代码
import Vue from "vue";
import App from "./App.vue";
Vue.config.productionTip = false;
/*
指令介绍
内置指令:v-html、v-if、v-bind、v-on... 这都是Vue给咱们内置的一些指令,可以直接使用
自定义指令:同时Vue也支持让开发者,自己注册一些指令。这些指令被称为自定义指令
每个指令都有自己各自独立的功能
*/
// 自定义指令
// 自定义指令:自己定义的指令,可以封装一些 dom 操作,扩展额外功能
// 1. 全局注册 - 语法
/* Vue.directive("指令名", {
inserted(el) {
// 对el标签扩展额外功能
},
}); */
/* Vue.directive("focus", {
// inserted 指令的生命周期钩子 → 当指令被加到标签上面自动执行的代码
// el 使用指令的那个DOM元素
// 自带一个形参 → 添加当前指令的标签
inserted(el) {
el.focus();
},
}); */
new Vue({
render: (h) => h(App),
}).$mount("#app");
App.vue
html
复制代码
<template>
<!-- 01-src-自定义指令-focus -->
<div id="app">
<h1>自定义指令</h1>
<!--
使用指令
注意:在使用指令的时候,一定要先注册,再使用,否则会报错
使用指令语法:v-指令名 如:<input type="text" v-focus/>
注册指令时不用加v-前缀,但使用时一定要加v-前缀
-->
<input type="text" v-focus />
</div>
</template>
<script>
export default {
// 2. 局部注册 -- 语法 指令名/'指令名'
/* directives: {
指令名: {
inserted(el) {
// 可以对 el 标签,扩展额外功能
el.focus();
},
},
}, */
directives: {
focus: {
inserted(el) {
el.focus();
},
},
},
};
</script>
<style>
</style>
2.2 指令的值
html
复制代码
<template>
<!-- 02-src-自定义指令-值 -->
<!--
1. 通过指令的值相关语法,可以应对更复杂指令封装场景
2. 指令值的语法:
① v-指令名 = "指令值",通过 等号 可以绑定指令的值
② 通过 binding.value 可以拿到指令的值
③ 通过 update 钩子,可以监听指令值的变化,进行dom更新操作
-->
<div id="app">
<!-- 语法:在绑定指令时,可以通过"等号"的形式为指令 绑定 具体的参数值 -->
<h1 v-color="color1">
自定义指令
<button @click="color1 = 'green'">修改颜色</button>
</h1>
<h1 v-color="color2">自定义指令</h1>
</div>
</template>
<script>
export default {
data() {
return {
color1: "red",
color2: "pink",
};
},
// 需求:实现一个 color 指令 - 传入不同的颜色, 给标签设置文字颜色
directives: {
color: {
// binding 对象会包含所有与该指令相关的信息
inserted(el, binding) {
// console.log(el);
// console.log(binding);
// 通过 binding.value 可以拿到指令值,指令值修改会 触发 update 函数
// console.log(binding.value);
el.style.color = binding.value;
},
update(el, binding) {
el.style.color = binding.value;
},
},
},
};
</script>
<style>
</style>
2.3 v-loading 指令封装
main.js
javascript
复制代码
import Vue from "vue";
import App from "./App.vue";
Vue.config.productionTip = false;
// // 1. 全局注册指令
// Vue.directive('focus', {
// // inserted 会在 指令所在的元素,被插入到页面中时触发
// inserted (el) {
// // el 就是指令所绑定的元素
// // console.log(el);
// el.focus()
// }
// })
new Vue({
render: (h) => h(App),
}).$mount("#app");
App.vue
html
复制代码
<template>
<!-- 03-src-封装loading指令 -->
<!--
场景:实际开发过程中,发送请求需要时间,在请求的数据未回来时,页面会处于空白状态 => 用户体验不好
需求:封装一个 v-loading 指令,实现加载中的效果
-->
<div class="box" v-loading="isLoading">
<ul>
<li v-for="item in list" :key="item.id" class="news">
<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="" />
</div>
</li>
</ul>
</div>
</template>
<script>
// 安装axios => yarn add axios
import axios from "axios";
// 接口地址:http://hmajax.itheima.net/api/news
// 请求方式:get
export default {
data() {
return {
list: [],
isLoading: true,
};
},
watch: {
list: {
deep: true,
handler(newList) {
// newList !== [] ? (this.isLoading = false) : (this.isLoading = true);
this.isLoading = newList.length === 0;
},
},
},
async created() {
// 1. 发送请求获取数据
const res = await axios.get("http://hmajax.itheima.net/api/news");
setTimeout(() => {
// 2. 更新到 list 中,用于页面渲染 v-for
this.list = res.data.data;
// this.isLoading = false;
}, 1000);
},
directives: {
loading: {
inserted(el, binding) {
binding.value
? el.classList.add("loading")
: el.classList.remove("loading");
},
update(el, binding) {
binding.value
? el.classList.add("loading")
: el.classList.remove("loading");
},
},
},
};
</script>
<style>
/* 通过css添加一个伪元素 → 假的标签 */
/* ::before:这是一个伪元素选择器,用于在目标元素的内容之前插入额外的内容。
这个伪元素并不存在于文档树中,但它会显示在目标元素的前面。
常用于为正在加载的资源(如图片、视频或音频)添加自定义的加载样式,以提供更丰富的用户体验和视觉反馈。 */
/* 当将.loading和::before组合使用时,就表示在具有loading类的每个元素的内容之前插入额外的内容。 */
.loading::before {
/* 必填属性 */
content: "";
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
background: #fff url("./loading.gif") no-repeat center;
}
.box {
width: 800px;
min-height: 500px;
border: 3px solid orange;
border-radius: 5px;
position: relative;
margin: 0 auto;
}
.news {
display: flex;
height: 120px;
width: 600px;
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>
3. 插槽
3.1 默认插槽
3.2 后备内容
3.3 具名插槽
App.vue
html
复制代码
<template>
<!-- 04-src-封装对话框组件-默认和具名插槽 -->
<div>
<!-- 一、默认插槽 -->
<!-- <MyDialog>文本内容1</MyDialog> -->
<!-- <MyDialog><h3>标题3</h3></MyDialog> -->
<!-- 二、后备内容 -->
<!-- <MyDialog></MyDialog> -->
<!-- 三、具名插槽 -->
<MyDialog>
<template v-slot:head><h4>温馨提示</h4></template>
<template #content>确定要删除吗?</template>
</MyDialog>
</div>
</template>
<script>
import MyDialog from "./components/MyDialog.vue";
export default {
data() {
return {};
},
components: {
MyDialog,
},
};
</script>
<style>
body {
background-color: #b3b3b3;
}
</style>
MyDialog.vue
html
复制代码
<template>
<div class="dialog">
<div class="dialog-header">
<!-- <h3>友情提示</h3> -->
<slot name="head"></slot>
<span class="close">✖️</span>
</div>
<div class="dialog-content">
<!-- 我是文本内容 -->
<!--
一、插槽 - 默认插槽 → 让组件内部的一些 结构 支持 自定义
插槽基本语法:
1. 组件内需要定制的结构部分,改用<slot></slot>占位
2. 使用组件时, <MyDialog></MyDialog>标签内部, 传入结构替换slot
3. 给插槽传入内容时,可以传入纯文本、html标签、组件
-->
<!-- <slot></slot> -->
<!--
二、插槽 - 后备内容(默认值)
插槽后备内容:封装组件时,可以为预留的 `<slot>` 插槽提供后备内容(默认内容)。
在 <slot> 标签内,放置内容, 作为默认显示内容 → 外部使用组件时,不传东西,则slot会显示后备内容
-->
<!-- <slot>默认内容</slot> -->
<!--
三、插槽 - 具名插槽 → 一个组件内有多处结构,需要外部传入标签,进行定制
具名插槽语法:
1. 多个slot使用name属性区分名字
2. template配合v-slot:名字来分发对应标签
3. v-slot:插槽名 可以简化成 #插槽名
-->
<slot name="content"></slot>
</div>
<div class="dialog-footer">
<button>取消</button>
<button>确认</button>
</div>
</div>
</template>
<script>
export default {
data() {
return {};
},
};
</script>
<style scoped>
* {
margin: 0;
padding: 0;
}
.dialog {
width: 470px;
height: 230px;
padding: 0 25px;
background-color: #ffffff;
margin: 40px;
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>
3.4 作用域插槽
App.vue
html
复制代码
<template>
<!--
05-src-封装表格组件-作用域插槽
场景:封装表格组件
1. 父传子,动态渲染表格内容
2. 利用默认插槽,定制操作列
3. 删除或查看都需要用到 当前项的 id,属于组件内部的数据, 通过 作用域插槽 传值绑定,进而使用
-->
<div>
<MyTable :data="list">
<template #default="obj">
<button @click="del(obj.id)">删除</button>
</template>
</MyTable>
<MyTable :data="list2">
<template #default="{ id, msg }">
<button @click="fn(id, msg)">查看</button>
</template>
</MyTable>
</div>
</template>
<script>
import MyTable from "./components/MyTable.vue";
export default {
data() {
return {
list: [
{ id: 1, name: "张小花", age: 18 },
{ id: 2, name: "孙大明", age: 19 },
{ id: 3, name: "刘德忠", age: 17 },
],
list2: [
{ id: 1, name: "赵小云", age: 18 },
{ id: 2, name: "刘蓓蓓", age: 19 },
{ id: 3, name: "姜肖泰", age: 17 },
],
};
},
components: {
MyTable,
},
methods: {
del(id) {
this.list = this.list.filter((item) => item.id !== id);
},
fn(id, msg) {
console.log(id, msg);
},
},
};
</script>
MyTable.vue
html
复制代码
<template>
<table class="my-table">
<thead>
<tr>
<th>序号</th>
<th>姓名</th>
<th>年纪</th>
<th>操作</th>
</tr>
</thead>
<!--
四、插槽 - 作用域插槽
作用域插槽: 定义 slot 插槽的同时, 是可以传值的。给 插槽 上可以 绑定数据,将来 使用组件时可以用。
基本使用步骤:
1. 给 slot 标签, 以 添加属性的方式传值
2. 所有添加的属性, 都会被收集到一个对象中
3. 在template中, 通过 ` #插槽名= "obj" ` 接收,默认插槽名为 default
-->
<tbody>
<!-- <tr>
<td>1</td>
<td>小张</td>
<td>8</td>
<td>
<button>删除</button>
</td>
</tr> -->
<tr v-for="(item, index) in data" :key="item.id">
<td>{{ index + 1 }}</td>
<td>{{ item.name }}</td>
<td>{{ item.age }}</td>
<td>
<!-- <button>删除</button> -->
<slot :id="item.id" msg="普通数据"></slot>
</td>
</tr>
</tbody>
</table>
</template>
<script>
export default {
props: {
data: Array,
},
};
</script>
<style scoped>
.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>
4. 综合案例 - 商品列表
App.vue
html
复制代码
<template>
<!-- 06-src-商品列表 -->
<!--
需求说明:
1. my-tag 标签组件封装
(1) 双击显示输入框,输入框获取焦点
(2) 失去焦点,隐藏输入框
(3) 回显标签信息
(4) 内容修改,回车 → 修改标签信息
2. my-table 表格组件封装
(1) 动态传递表格数据渲染
(2) 表头支持用户自定义
(3) 主体支持用户自定义
-->
<div class="table-case">
<MyTable :data="goods">
<template #head>
<th>编号</th>
<th>图片</th>
<th>名称</th>
<th width="100px">标签</th>
</template>
<!-- 拿到插槽传入的数据 解构 -->
<template #con="{ item, index }">
<td>{{ index + 1 }}</td>
<td>
<img :src="item.picture" />
</td>
<td>{{ item.name }}</td>
<td>
<MyTag v-model="item.tag"></MyTag>
</td>
</template>
</MyTable>
</div>
</template>
<script>
import MyTable from "./components/MyTable.vue";
import MyTag from "./components/MyTag.vue";
export default {
name: "TableCase",
data() {
return {
goods: JSON.parse(localStorage.getItem("newGoods")) || [
{
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: "儿童服饰",
},
],
};
},
components: {
MyTable,
MyTag,
},
watch: {
goods: {
deep: true,
handler(newGoods) {
localStorage.setItem("newGoods", JSON.stringify(newGoods));
},
},
},
};
</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
html
复制代码
<template>
<table class="my-table">
<thead>
<tr>
<slot name="head"></slot>
</tr>
</thead>
<tbody>
<tr v-for="(item, index) in data" :key="item.id">
<slot name="con" :item="item" :index="index"></slot>
</tr>
</tbody>
</table>
</template>
<script>
// import MyTag from "./MyTag.vue";
export default {
components: {
// MyTag,
},
props: {
data: Array,
},
};
</script>
<style lang="less" 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 0.5s;
&.red {
color: red;
}
}
.none {
height: 100px;
line-height: 100px;
color: #999;
}
}
</style>
MyTag.vue
html
复制代码
<template>
<div class="my-tag">
<input
class="input"
type="text"
placeholder="输入标签"
v-if="isEdit"
v-focus
@blur="isEdit = false"
@keyup.enter="changeTag"
:value="value"
/>
<!-- :value 是html标签自带的属性 "value" 是props数据 -->
<!-- dbl - double, 双击 - dblclick -->
<div class="text" v-else @dblclick="isEdit = true">{{ value }}</div>
</div>
</template>
<script>
// 分类标签的功能:
// input和文字互斥 → 布尔数据 v-if
// 双击文字div 显示input标签 → 自动获得焦点
// input 要数据回显 - 父子通信
// v-model = value属性和input事件的缩写
// 用户输入后,回车 → 把新的内容渲染到页面 → 数据应该同步
export default {
props: {
value: String,
},
data() {
return {
isEdit: false,
};
},
methods: {
changeTag(e) {
// 通知父组件保存用户输入的数据 $emit(事件名, 用户输入的数据)
this.$emit("input", e.target.value);
// 显示div,隐藏输入框
this.isEdit = false;
},
},
};
</script>
<style lang="less" scoped>
// lang="less" 表示的是less语法
.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>