效果视频:
卡片组件
样式还得细调~,时间有限,主要记录一下逻辑。
html结构:
目录
- 父组件
- 数据处理
- 数据格式
- 父组件的全部代码
- 子组件
- 数据处理
- props参数
- 样式部分
- 三个圆点
- 点击三圆点在对应位置显示查看弹框
- 点击非内容部分隐藏查看弹框
- 遮罩层
- 子组件的全部代码
父组件
数据处理
在父页面进行v-for循环,灵活根据状态可赋值数组,再根据数组的长度调用卡片组件。
数据格式
- ASTATE:状态;整型,0表示进行中,1表示超期
- DESCRIPTION:描述;字符串类型;
- STARTTIME:开始时间;字符串类型,格式为年月日T时分秒
- PLANENDTIME:结束时间;字符串类型,格式为年月日T时分秒
以上字段在子组件中皆有用到
[
{
"ASTATE": 0,
"DESCRIPTION": "大名称大名称\n名称:21名称21名称21\n名称:21名称21名称21\n时间:2024-7-17",
"STARTTIME": "2024-01-03T01:02:03",
"PLANENDTIME": "2024-01-04T01:02:03",
"ID": 2
},
{
"ASTATE": 2,
"DESCRIPTION": "大名称大名称\n名称:21名称21名称21\n名称:21名称21名称21\n时间:2024-7-17",
"STARTTIME": "2024-01-07T01:02:03",
"PLANENDTIME": "2024-01-08T01:02:03",
"ID": 4
}
]
父组件的全部代码
<!-- 页面名称 -->
<template>
<div class="homebox-class">
<div class="mainbox">
<h2>任务管理</h2>
<!-- 超期 -->
<h4 v-if="taskOverdueData.length>0" class="overduetitle">超期 {{taskOverdueData.length}}</h4>
<div class="outtabsbox">
<tab-task-com-vue :width="18" :height="16" v-for="(task,index) in taskOverdueData" :taskData="task" :key="'overdue' + index"></tab-task-com-vue>
</div>
<!-- 进行中 -->
<h4 v-if="taskOngoingData.length>0" class="ongoingtitle">进行中 {{taskOngoingData.length}}</h4>
<div class="outtabsbox">
<tab-task-com-vue :width="18" :height="16" v-for="(task,index) in taskOngoingData" :taskData="task" :key="'ongoing' + index"></tab-task-com-vue>
</div>
</div>
</div>
</template>
<script>
import testdata from './test.json';
import tabTaskComVue from '@/views/hzevt/abnormal/tabTaskCom.vue';
export default {
components: { tabTaskComVue },
props: {},
data() {
return {
taskOverdueData: [],
taskOngoingData: [],
};
},
watch: {},
computed: {},
created() { },
mounted() {
console.log('testdata', testdata);
for (var i of testdata) {
if (i.ASTATE == 0) {
this.taskOngoingData.push(i);
} else if (i.ASTATE == 2) {
this.taskOverdueData.push(i);
}
}
},
methods: {},
};
</script>
<style scoped>
.homebox-class {
width: 100%;
height: 100%;
}
.mainbox {
width: 100%;
height: 100%;
overflow-y: auto;
}
.mainbox h2 {
letter-spacing: 2px;
font-size: 1.2vw;
}
.mainbox h4 {
margin: 2% 0;
font-size: 1vw;
}
.overduetitle {
color: #f26950;
}
.ongoingtitle {
color: #2cc09c;
}
.outtabsbox {
display: flex;
justify-content: flex-start;
flex-wrap: wrap;
font-weight: bold;
}
</style>
子组件
数据处理
props参数
宽高单位为百分比。
props: {
width: {//卡片的宽度
type: Number,
required: true
},
height: {//卡片的高度
type: Number,
required: true
},
taskData: {//父组件传过来对应的item对象
type: Object,
required: true
},
},
样式部分
三个圆点
圆点的实现是采用三个div并添加border-radius: 50%的样式。
dots: [1, 2, 3],表示有三个圆点。
<div class="dotbox" @click="showTooltip($event)">
<div class="dot" v-for="(dot, index) in dots" :key="index"></div>
<div v-if="showPopup" @click="toCheckBtn" class="popup" :style="{ top: popupTop + 'px', left: popupLeft + 'px' }">
查看
</div>
</div>
点击三圆点在对应位置显示查看弹框
-
获取目标元素的位置信息:
getBoundingClientRect()
是一个 DOM API 方法,返回一个 DOMRect 对象,提供了目标元素的大小和其相对于视口的位置信息。 -
计算鼠标相对于目标元素的偏移量:
event.clientX
和event.clientY
分别是鼠标指针相对于整个文档的坐标位置。
dotboxRect.left
和dotboxRect.top
分别是目标元素的左上角相对于视口的坐标位置。 -
设置popup弹框元素的位置信息:
-
显示popup弹框元素:
showTooltip(event) {
const dotboxRect = event.currentTarget.getBoundingClientRect();
const offsetX = event.clientX - dotboxRect.left;
const offsetY = event.clientY - dotboxRect.top;
//设置popup弹框元素的位置信息
this.popupTop = offsetY + 'px';
this.popupLeft = offsetX + 'px';
this.showPopup = true;//显示popup弹框元素
},
点击非内容部分隐藏查看弹框
内容部分:
<div class="contentbox" ref="contentRef"></div>
- mounted添加监听事件
①. 获取点击目标信息:
②. 判断点击位置
handleClickOutside(event) {
const clickedInsideContentbox = this.$refs.contentRef.contains(event.target);//获取点击目标信息
if (!clickedInsideContentbox) {//判断点击位置
this.showPopup = false;
}
},
遮罩层
根据showPopup动态控制遮罩层的显示和隐藏
<div v-if="showPopup" class="popup-overlay" @click="togglePopup()"></div>
子组件的全部代码
<!-- 首页使用到的tab框组件 -->
<template>
<div :style="{ width: `${width}%`, height: `${height}%` }" class="tabsbox">
<!-- 遮罩层 -->
<div v-if="showPopup" class="popup-overlay" @click="togglePopup()"></div>
<div class="tabstitle overduetitle" v-if="taskData.ASTATE==2">超期</div>
<div class="tabstitle ongoingtitle" v-if="taskData.ASTATE==0">进行中</div>
<div class="contentbox" ref="contentRef">
<div class="contenttitlebox">
<div class="content-title" :class="taskData.ASTATE === 0?'state-0':'state-2'">生产异常流程</div>
<div class="dotbox" @click="showTooltip($event)">
<div class="dot" v-for="(dot, index) in dots" :key="index"></div>
<div v-if="showPopup" @click="toCheckBtn" class="popup" :style="{ top: popupTop + 'px', left: popupLeft + 'px' }">
查看
</div>
</div>
</div>
<pre class="content-descbox">{{taskData.DESCRIPTION}}</pre>
<div class="tabsbottombox">
<div class="timebox">
<img src="@/views/system/index/components/d2-page-cover/image/time1.png" alt="">
<div v-if="taskData.STARTTIME.includes('T')">{{taskData.STARTTIME.replace("T", " ").slice(0, 16)}}</div>
<div v-else>{{taskData.STARTTIME}}</div>
</div>
<div class="timebox" style="justify-content: flex-end;">
<img src="@/views/system/index/components/d2-page-cover/image/time2.png" alt="">
<div v-if="taskData.PLANENDTIME.includes('T')">{{taskData.PLANENDTIME.replace("T", " ").slice(0, 16)}}</div>
<div v-else>{{taskData.PLANENDTIME.replace("T", " ").slice(0, 16)}}</div>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
components: {},
props: {
width: {
type: Number,
required: true
},
height: {
type: Number,
required: true
},
taskData: {
type: Object,
required: true
},
},
data() {
return {
dots: [1, 2, 3],
showPopup: false,
popupTop: 0,
popupLeft: 0,
};
},
watch: {},
computed: {},
created() { },
beforeDestroy() {
// 在组件销毁前,移除全局点击事件监听器
document.removeEventListener('click', this.handleClickOutside);
},
mounted() {
document.addEventListener('click', this.handleClickOutside);
},
methods: {
showTooltip(event) {
const dotboxRect = event.currentTarget.getBoundingClientRect();
const offsetX = event.clientX - dotboxRect.left;
const offsetY = event.clientY - dotboxRect.top;
this.popupTop = offsetY + 'px';
this.popupLeft = offsetX + 'px';
this.showPopup = true;
},
togglePopup() {
// 点击tabsbox时切换popup状态
this.showPopup = false;
},
// 全局点击事件处理函数,用于关闭弹窗
handleClickOutside(event) {
const clickedInsideContentbox = this.$refs.contentRef.contains(event.target);
if (!clickedInsideContentbox) {
this.showPopup = false;
}
},
closePopup() {
this.showPopup = false;
},
// 查看详情
toCheckBtn() {
//你的操作逻辑
},
},
watch: {
},
};
</script>
<style scoped>
.tabsbox {
opacity: 0.6;
border-radius: 15px;
background: rgba(255, 255, 255, 1);
border: 1px solid rgba(138, 127, 127, 0.3);
box-shadow: 0px 2px 4px rgba(0, 0, 0, 0.25);
margin: 0 3% 3% 1%;
padding: 1%;
position: relative;
}
.overduetitle {
color: #f26950;
}
.ongoingtitle {
color: #2cc09c;
}
.tabstitle {
font-size: 0.8vw;
height: 15%;
border-bottom: 1px solid #e6e8ed;
}
.contentbox {
height: 85%;
}
.content-title {
font-size: 1vw;
height: 10%;
padding: 2%;
font-weight: bold;
position: relative; /* 让元素变为相对定位,以便使用 top 属性 */
}
.content-title::before {
content: ""; /* 伪元素用于创建边框 */
position: absolute; /* 绝对定位在元素内部 */
top: 15px; /* 距离顶部偏移量,根据需要调整 */
left: -2px; /* 边框从左侧开始 */
height: 100%; /* 高度为元素高度减去偏移量 */
}
/* ASTATE 为 0 时的伪类元素样式 */
.content-title.state-0::before {
border-left: 3px solid #2cc09c;
}
/* ASTATE 为 2 时的伪类元素样式 */
.content-title.state-2::before {
border-left: 3px solid #f26950;
}
.content-descbox {
height: 66%;
margin: 0 3% 3%;
color: #4f545a;
font-family: none;
font-size: 0.7vw;
display: flex;
align-items: center;
}
.tabsbottombox {
height: 14%;
display: flex;
}
.timebox {
height: 100%;
width: 100%;
display: flex;
align-items: center;
}
.timebox img {
width: 18px;
height: 18px;
}
.timebox div {
font-size: 0.7vw;
margin-left: 4px;
}
.contenttitlebox {
display: flex;
flex-direction: row;
flex-wrap: nowrap;
justify-content: space-between;
}
.dotbox {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
position: relative;
}
.dotbox:hover {
cursor: pointer;
}
.dot {
width: 0.2vw;
height: 0.3vh;
background-color: #787b83;
border-radius: 50%;
margin: 1.5px;
cursor: pointer;
}
.popup {
position: absolute;
background-color: #fff;
padding: 5px 0;
z-index: 1000;
width: 55px;
right: 10px;
top: 10px;
text-align: center;
border-radius: 0.3rem;
color: #8e8f95;
font-size: 0.8vw;
border: 1px solid #ededed;
box-sizing: border-box;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
}
.popup:hover {
background: #409eff;
color: #fff;
box-shadow: 0 2px 12px 0 rgb(0 0 0 / 40%);
}
.popup-overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5); /* 遮罩层颜色 */
z-index: 1; /* 确保遮罩层在popup之下 */
border-radius: 15px;
}
.tabsbox {
/* 默认背景样式 */
background-color: #fff;
}
</style>