效果图
写在前面
写后台管理经常从列表点击查看详情,展示数据信息,Element UI虽然有表格组件,但是描述组件并没有,之前团队的成员遇到这种情况都自己去写样式,写起来也麻烦,而且每个人写出来的样式也不统一,破坏了项目的整体风格。
像是Ant Design UI就有描述组件,用起来特别舒服,所以索性自己结合Element UI的el-row和el-col自己写了一个。
实现哪些功能
1、每行的高度根据改行中某一列的最大高度自动撑开
2、列宽度自动补全,避免最后一列出现残缺的情况
3、支持纯文本与HTML插槽
4、支持每行几列的设置
5、支持每列宽度自定义
6、支持动态数据重绘
组件设计
1、使用父子组件嵌套实现,父组件为 e-desc, 子组件为 e-desc-item 。
2、e-desc-item传递props的label 和 插槽的value
3、利用 el-row 和 el-col 来实现整体组件布局
封装e-desc组件
<template>
<div class="desc" :style="{margin}">
<!-- 标题 -->
<h1 v-if="title" class="desc-title" v-html="title"></h1>
<el-row class="desc-row" ref='elRow'>
<slot/>
</el-row>
</div>
</template>
<script>
export default {
name: 'EDesc',
// 通过provide提供给子组件
provide () {
return {
labelWidth: this.labelWidth,
column: this.column,
size: this.size
}
},
props: {
// 标题
title: {
type: String,
default: ''
},
// 边距
margin: {
type: String,
default: '0'
},
// label宽度
labelWidth: {
type: String,
default: '120px'
},
column: {
// 每行显示的项目个数
type: [Number, String],
default: 3
},
size: {
// 大小
type: String,
default: ''
}
},
data () {
return {
// 监听插槽变化
observe: new MutationObserver(this.computedSpan)
}
},
mounted () {
this.$nextTick(() => {
this.computedSpan()
this.observe.observe(this.$refs.elRow.$el, { childList: true })
})
},
methods: {
computedSpan () {
// 筛选出子组件e-desc-item
const dataSource = this.$slots.default
const dataList = []
dataSource.forEach(item => {
if (item.componentOptions && item.componentOptions.tag === 'e-desc-item') {
dataList.push(item.componentInstance)
}
})
// 剩余span
let leftSpan = this.column
const len = dataList.length
dataList.forEach((item, index) => {
// 处理column与span之间的关系
// 剩余的列数小于设置的span数
const hasLeft = leftSpan <= (item.span || 1)
// 当前列的下一列大于了剩余span
const nextColumnSpan = (index < (len - 1)) && (dataList[index + 1].span >= leftSpan)
// 是最后一行的最后一列
const isLast = index === (len - 1)
if (hasLeft || nextColumnSpan || isLast) {
// 满足以上条件,需要自动补全span,避免最后一列出现残缺的情况
item.selfSpan = leftSpan
leftSpan = this.column
} else {
leftSpan -= item.span || 1
}
})
}
},
beforeDestroy () {
this.observer.disconnect()
}
}
</script>
<style scoped lang="scss">
.desc{
.desc-title {
margin-bottom: 10px;
color: #333;
font-weight: 700;
font-size: 16px;
line-height: 1.5715;
}
.desc-row{
display: flex;
flex-wrap: wrap;
border-radius: 2px;
border: 1px solid #EBEEF5;
border-bottom: 0;
border-right: 0;
width: 100%;
}
}
</style>
封装e-desc-item组件
<template>
<el-col :span="computedSpan" class="desc-item">
<div class="desc-item-content" :class="size">
<label class="desc-item-label" :style="{width: labelWidth}" v-html="label"></label>
<div class="desc-item-value" v-if="$slots">
<slot/>
</div>
</div>
</el-col>
</template>
<script>
export default {
name: 'EDescItem',
inject: ['labelWidth', 'column', 'size'],
props: {
span: {
type: [Number, String],
required: false,
default: 0
},
label: {
type: String,
required: false,
default: ''
}
},
data () {
return {
// 子组件自己的span
selfSpan: 0
}
},
computed: {
computedSpan () {
// 子组件自己的span,用于父组件计算修改span
if (this.selfSpan) {
return 24 / this.column * this.selfSpan
} else if (this.span) {
// props传递的span
return 24 / this.column * this.span
} else {
// 未传递span时,取column
return 24 / this.column
}
}
}
}
</script>
<style scoped lang="scss">
.desc-item {
border-right: 1px solid #EBEEF5;
border-bottom: 1px solid #EBEEF5;
.desc-item-content {
display: flex;
justify-content: flex-start;
align-items: center;
color: rgba(0,0,0,.65);
font-size: 14px;
line-height: 1.5;
width: 100%;
background-color: #fafafa;
height: 100%;
.desc-item-label{
border-right: 1px solid #EBEEF5;
display: inline-block;
padding: 12px 16px;
flex-grow: 0;
flex-shrink: 0;
color: rgba(0, 0, 0, 0.6);
font-weight: 400;
font-size: 14px;
line-height: 1.5;
height: 100%;
display: flex;
align-items: center;
}
.desc-item-value{
background: #fff;
padding: 12px 16px;
flex-grow: 1;
overflow: hidden;
word-break: break-all;
height: 100%;
display: flex;
align-items: center;
color: #444;
span{
color: #aaa;
}
// 空数据时展示的内容
&:empty::after {
content: '--';
}
}
&.small {
.desc-item-label,
.desc-item-value {
padding: 10px 14px;
}
}
}
}
</style>
使用方式
<template>
<e-desc margin='0 12px' label-width='100px'>
<e-desc-item label="姓名">{{info.name}}</e-desc-item>
<e-desc-item label="年龄">{{ info.age }}岁</e-desc-item>
<e-desc-item label="性别">{{ info.sex }}</e-desc-item>
<e-desc-item label="学校">{{ info.school }}</e-desc-item>
<e-desc-item label="专业">{{ info.major }}</e-desc-item>
<e-desc-item label="爱好">{{ info.hobby }}</e-desc-item>
<e-desc-item label="手机号">{{ info.phone }}</e-desc-item>
<e-desc-item label="微信">{{ info.wx }}</e-desc-item>
<e-desc-item label="QQ">{{ info.qq }}</e-desc-item>
<e-desc-item label="住址">{{ info.address }}</e-desc-item>
<e-desc-item label="自我描述" :span='2'>{{ info.intro }}</e-desc-item>
<e-desc-item label="操作" :span='3'>
<template>
<el-button size="small" type="primary">修改</el-button>
<el-button size="small" type="danger">删除</el-button>
</template>
</e-desc-item>
</e-desc>
</template>
<script>
import EDesc from './e-desc'
import EDescItem from './e-desc-item'
export default {
components: {
EDesc, EDescItem
},
data () {
return {
info: {
name: 'Jerry',
age: 26,
sex: '男',
school: '四川大学',
major: '码农专业',
address: '四川省成都市',
hobby: '搬砖、前端、赚钱',
phone: 18888888888,
wx: 'Nice2cu_Hu',
qq: 332983810,
intro: '我是一个粉刷匠,粉刷本领强。我要把那新房子,刷得更漂亮。刷了房顶又刷墙,刷子飞舞忙。哎呀我的小鼻子,变呀变了样。我是一个粉刷匠,粉刷本领强。我要把那新房子,刷得更漂亮。刷了房顶又刷墙,刷子飞舞忙。哎呀我的小鼻子,变呀变了样。'
}
}
}
}
</script>
参数说明
至此,代码就写完啦,考虑不周或者有bug的地方,还望多多留言告知我哟