这个功能很常见,但是在vue这种数据驱动视图层的项目中,实现这个功能也要操作dom,主要是通过获取dom节点的高度,来判断是否截掉多余行数。
我实现的功能是:vue项目异步从后端获取多条留言,文字长度超过两行,多余文字截掉,显示····+展开,点击展开后,显示全部文本内容,“展开”变“收起”,点击收起,又变成两行显示。
效果图如下:
直接附上代码,代码中有注释。
<template> <div class="div-full list-view" v-loading="isLoading" > <div class="show-view list-view-title" > <h3>留言列表</h3> <div class="view-base-oper"> <el-button v-if="userId=='50'" type="primary" v-has = "'leaveMessage'" size="mini" @click="openLeaveMessage">我要留言</el-button> </div> <div class="list-pager-top"> <el-pagination background layout="prev, slot, next" class="transparent-page-button" :page-size="pager.pageSize" :current-page="pager.currentPage" :total="total" @current-change="handleCurrentChange"> <span style="color:#656565;font-size:12px;font-weight:normal;text-align:center;"> <template v-if="total>0"><i style="font-style:normal;color:#ed8232;">{{pager.currentPage}}</i>/</template> <template v-else><i style="font-style:normal;color:#ed8232;"></i> </template> {{Math.ceil(total/pager.pageSize)}} </span> </el-pagination> </div> </div> <el-backtop target=".scroll-box .el-scrollbar__wrap" :bottom="64" :right="36"> <div class="iconfont backtop" id="backBtn"></div> </el-backtop> <el-scrollbar class="scroll-box" style="height:100%" > <div class="message-box"> <!-- 内容填充部分 --> <ul class="message-list" id="messBox"> <li v-for="(item,index) in showList" :key = "index"> <h2 class="message-title"> <span class="txt">{{item.messageTitle}}</span> <span class="type">{{item.messageType}}</span> <el-button v-if="userId != '50' && item.isReply == '0' " v-has = "'replyMessage'" type="primary" size="mini" @click="openReplyMessage(item.messageId)" style="float: right;">回复</el-button> </h2> <div class="message-content"> <label class="left">留言:</label> <div class="right"> <div class="content " > <p :id="'leaveMess'+index" >{{item.messageContent}}</p> <span :id="'leaveMessBtn'+index" class="show-all" @click="showAll($event,item,'leaveMess',index)">展开</span></span> </div> <p class="info"> <span>留言者:{{item.messageUserName}}</span> <span>留言时间:{{item.messageTime}}</span> </p> </div> </div> <div class="message-content message-reply" v-if="item.isReply == '1'"> <label class="left">回复:</label> <div class="right"> <div class="content "> <p :id="'replyMess'+index">{{item.replyContent}}</p> <span :id="'replyMessBtn'+index" class="show-all" @click="showAll($event,item,'replyMess',index)">展开</span> </div> <p class="info"> <span>回复者:{{item.replyUserName}}</span> <span>回复者单位:{{item.replyDeptName}}</span> <span>回复时间:{{item.replyTime}}</span> </p> </div> </div> </li> </ul> <div class="no-data" v-if="total<=0"> <h3 v-show="!isLoading">暂无数据</h3> </div> </div> <div class="list-pager" v-if="total>0" style="text-align:right; padding:0 20px;"> <el-pagination background layout="prev, pager, next, total, jumper" :page-size="pager.pageSize" :current-page="pager.currentPage" :total="total" @current-change="handleCurrentChange"> </el-pagination> </div> </el-scrollbar> <leaveMessage ref="leaveMessage" :messageTypeOptions = "messageTypeOptions" @reloadList = 'reloadData'/> <replyMessage ref="replyMessage" @reloadList = 'reloadData'/> </div> </template> <script> import ViewBase from '@/components/ViewBase' import ViewBaseMixins from '@/mixins/ViewBaseMixins' import { getMessageList} from '@/api/modules/message/message' import leaveMessage from '@/views/modules/message/subModules/leaveMessage/operMessageDialog/leaveMessage.vue' import replyMessage from '@/views/modules/message/subModules/leaveMessage/operMessageDialog/replyMessage' export default { props:{ messageTypeOptions:Array, }, components:{ leaveMessage, replyMessage, }, mixins: [ViewBaseMixins], data(){ return{ isLoading:false, filterData:{ messageStatus: "", messageType: "", messageUserId: "", replyUserId: "" }, pager: { currentPage: 1, orderBy: "updateTime", orderType: "desc", pageSize: 20 }, total:0, showList:[], textHeight: null, showContent:{ transition: "all 2s", height:"auto", whiteSpace:"normal", overflow:"auto", textOverflow:"normal", }, upContent:{ transition: "all 2s", height:"28px", whiteSpace:"nowrap", overflow:"hidden", textOverflow:"ellipsis", } } }, computed:{ userId () { if(this.$isCommonUser()){ //地方用户 return "50" } else if(this.$isAcceptUser()){ //81中心受理用户 return "81" }else if(this.$isReViewUser()){ //82中心复核用户 return "82" } } }, mounted() { this.loadData() // this.$EventBus.$off('EB_filterData'); // 记得销毁 // this.$EventBus.$on('EB_filterData',(filterData) => { // Object.keys(filterData).forEach(key=>{this.filterData[key]=filterData[key]}) // this.loadData() // }) }, methods: { /** * 获取用户列表 */ loadData(){ const _this = this _this.isLoading =true let filterData = { messageStatus: _this.filterData.messageStatus, messageType: _this.filterData.messageType, messageUserId: _this.filterData.messageUserId, replyUserId: _this.filterData.replyUserId, pager: _this.pager } this.showList = [] getMessageList(filterData).then(response => { _this.showList = response.list _this.total = response.total //判断留言和回复内容行高,确定是否显示展开收起按钮 this.$nextTick(() => { for(let i=0; i< response.total; i++){ // console.log(document.getElementById('replyMess'+i)) let leaveMess = document.getElementById('leaveMess'+i) let leaveMessBtn = document.getElementById('leaveMessBtn'+i) this.computeLineHeight(leaveMess,leaveMessBtn) let replyMess = document.getElementById('replyMess'+i) let replyMessBtn = document.getElementById('replyMessBtn'+i) this.computeLineHeight(replyMess,replyMessBtn) } }) }).catch(error => { console.log(`角色列表获取失败!${error}`) }).finally( error => { _this.isLoading = false }) }, reloadData(filterData){ if(filterData){ Object.keys(filterData).forEach(key=>{this.filterData[key]=filterData[key]}) } this.handleCurrentChange(1) }, openLeaveMessage(userId){ this.$refs.leaveMessage.show(); }, openReplyMessage(messageId){ this.$refs.replyMessage.show(messageId); }, showAll(e,item,type,index,lineCount=2,lineHeight = 28){ let btnDom = document.getElementById(type+index) let contentHeight = e.target.getAttribute('data-height') if(e.currentTarget.innerText == '展开'){ btnDom.style.height = contentHeight +"px" btnDom.style.transitionDuration = ".5s" e.currentTarget.innerText = '收起' e.currentTarget.classList.add("active") }else if(e.currentTarget.innerText == '收起'){ btnDom.style.height = lineCount * lineHeight +'px' btnDom.style.overflow = "hidden" btnDom.style.transitionDuration = ".5s" e.currentTarget.innerText = '展开' e.currentTarget.classList.remove("active") } }, /** * 点击分页触发方法 */ handleCurrentChange(value) { this.pager.currentPage = value this.loadData() // this.$emit('func-page-change',value) //翻页返回顶部 this.toTop() }, toTop(){ let btn = document.getElementById('backBtn') if(btn){ btn.click(); } }, //计算行高,显示收起展示 computeLineHeight(contentDom,btnDom,lineCount=2,lineHeight = 28){ // let leaveMess = document.getElementById('leaveMess'+i) if(contentDom){ let domHeight = contentDom.offsetHeight btnDom.setAttribute('data-height',domHeight) if(domHeight > lineHeight * lineCount){ contentDom.style.height = lineHeight * lineCount +'px' contentDom.style.overflow = "hidden" }else{ btnDom.style.display = 'none' } }else{ return false } } } } </script> <style lang="scss" scoped> .message-box{ margin:20px; } .message-list{ li{ padding:0 20px; margin-bottom:20px; border-bottom:1px solid #ddd; .message-title{ height:30px; .txt{ line-height:30px; font-size:14px; } .type{ padding:0 10px; line-height:30px; font-size:12px; color:#306abb; font-weight:normal; } } .message-content{ overflow:hidden; .left{ width:50px; white-space:nowrap; float:left; line-height: 28px; font-size: 14px; color:#306abb ; } .right{ margin-left:50px; .content{ position: relative; padding:0 10px; min-height:28px; line-height: 28px; font-size: 12px; background-color: #f4f4f5; p{ width:100%; line-height:28px; word-break: break-all; transition: height 2s; } .con{ transition: all 2s; height:28px; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; } .show-all{ cursor: pointer; position: absolute; bottom:0; right:10px; padding-left:5px; line-height:28px; font-size:12px; color:#306abb ; background-color: #f4f4f5; &:before{ content: '...'; position: absolute; bottom: 0; left: -50px; width: 50px; text-align:right; background: linear-gradient(to right, transparent, #f4f4f5 ); } i{ padding:0 3px; line-height:28px; font-size:12px; } &.active{ position: static; display:block; text-align:right; &:before{ display:none; } } } } .info{ text-align: right; span{ line-height:28px; padding:0 10px; } } } } } } .text { position: relative; padding:0 10px 30px 10px; min-height:28px; line-height: 28px; font-size: 12px; background-color: #f4f4f5; } .retract { position: relative; overflow: hidden; } .retract:after { content: '...'; position: absolute; bottom: 0; right: 0; width: 25px; padding-left: 30px; background: linear-gradient(to right, transparent, #f4f4f5 45%); } .btn { position: absolute; right: -2px; bottom: 0; font-size: 12px; line-height: 28px; letter-spacing: 2px; color:#306abb ; cursor: pointer; i{ font-size: 12px; } } .more { font-size: 14px; line-height: 20px; letter-spacing: 2px; color: #666666; visibility: hidden; } </style>
因为为没有搭建后台环境,所以用mock模拟的后台数据,引入的mock.js文件:
//引入mock模块
import Mock from 'mockjs';
Mock.mock('/messageList', { //输出数据
list:[
{
"id":'1',
'messageTitle': '随便起的名字1',
'messageUserName': '@name', //随机生成姓名
'messageContent': '1111111111111留言内容留言内容留言内容留言内容留言内容留言内容留言内容留言内容留言内容留言内容留言内容留言内容留言内容留言内容留言内容留言内容留言内容留言内容留言内容留言内容留言内容留言内容留言内容留言内容留言内容留言内容留言内容留言内容留言内容留言内容留言内容留言内容留言内容留言内容留言内容留言内容留言内容留言内容留言内容留言内容留言内容留言内容留言内容留言内容留言内容留言内容留言内容留言内容留言内容留言内容留言内容留言内容留言内容',
'messageTime|10-20': 10
},
{
"id":'2',
'messageTitle': '随便起的名字2',
'messageUserName': '@name', //随机生成姓名
'messageContent': '2222222222留言内容留言内容留言内容留言内容留言内容留言内容留言内容留言内容留言内容留言内容留言内容留言内容留言内容留言内容留言内容留言内容留言内容留言内容留言内容留言内容留言内容留言内容留言内容留言内容留言内容留言内容留言内容留言内容留言内容留言内容留言内容留言内容留言内容留言内容留言内容留言内容留言内容留言内容留言内容留言内容留言内容留言内容留言内容留言内容留言内容留言内容留言内容留言内容留言内容留言内容留言内容留言内容留言内容',
'messageTime|10-20': 10
}
]
});