卡片式组件封装demo

avatar
作者
猴君
阅读量:0

效果视频:

卡片组件

样式还得细调~,时间有限,主要记录一下逻辑。

html结构:
在这里插入图片描述

目录

父组件

数据处理

在父页面进行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> 
点击三圆点在对应位置显示查看弹框
  1. 获取目标元素的位置信息:
    getBoundingClientRect() 是一个 DOM API 方法,返回一个 DOMRect 对象,提供了目标元素的大小和其相对于视口的位置信息。

  2. 计算鼠标相对于目标元素的偏移量:
    event.clientXevent.clientY 分别是鼠标指针相对于整个文档的坐标位置。
    dotboxRect.leftdotboxRect.top 分别是目标元素的左上角相对于视口的坐标位置。

  3. 设置popup弹框元素的位置信息:

  4. 显示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> 
  1. 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> 

广告一刻

为您即时展示最新活动产品广告消息,让您随时掌握产品活动新动态!