效果图
写在前面
由于vue使用的版本太低,vue element UI 的描述列表不生效,但是有时候又不想换版本的可以自定义一个描述列表。
实现哪些功能
1、每行的高度根据改行中某一列的最大高度自动撑开
2、列宽度自动补全,避免最后一列出现残缺的情况
3、支持纯文本与HTML插槽
4、支持每行几列的设置
5、支持每列宽度自定义
6、支持动态数据重绘
组件设计
1、使用父子组件嵌套实现,父组件为 el-descriptions, 子组件为 el-descriptions-item。
2、e-desc-item传递props的label 和 插槽的value
3、利用 el-row 和 el-col 来实现整体组件布局
封装el-descriptions组件
<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: 'ElDescriptions', // 通过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: 4 }, size: { // 大小 type: String, default: '0' } }, 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: 'ElDescriptionsItem', 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> <div class="mod-contracts"> <el-descriptions margin="0 12px" label-width="100px" v-loading="dataListLoading" v-model="userInfo" v-if="userInfo" > <el-descriptions-item label="合同编号" :span="2">{{ userInfo.num }}</el-descriptions-item> <el-descriptions-item label="合同名称" :span="2"> {{ userInfo.title }}</el-descriptions-item > <el-descriptions-item label="姓名" :span="2"> {{ userInfo.userName }}</el-descriptions-item > <el-descriptions-item label="公司名称" :span="2"> {{ userInfo.companyName }}</el-descriptions-item > <el-descriptions-item label="合同类型" :span="2"> <span v-if="userInfo.type == 0">试用期合同</span> <span v-if="userInfo.type == 1">正式员工合同</span> <span v-if="userInfo.type == 2">外包合同</span> </el-descriptions-item> <el-descriptions-item label="薪资" :span="2">{{ userInfo.salary }}</el-descriptions-item> <el-descriptions-item label="岗位" :span="2">{{ userInfo.post }}</el-descriptions-item> <el-descriptions-item label="合同状态" :span="2"> <template> <el-tag v-if="userInfo.state === 0 && !isCurrentTimeLater" size="small" type="info" >未确认</el-tag > <el-tag v-if="userInfo.state === 1" size="small">生效</el-tag> <el-tag v-if="userInfo.state === 2 || isCurrentTimeLater" size="small" type="danger" >作废</el-tag > </template> </el-descriptions-item> <el-descriptions-item label="身份证号码" :span="2">{{ userInfo.idNum }}</el-descriptions-item> <el-descriptions-item label="家庭住址" :span="2">{{ userInfo.address }}</el-descriptions-item> <el-descriptions-item label="联系电话" :span="2">{{ userInfo.mobile }}</el-descriptions-item> <el-descriptions-item label="合同生效时间" :span="2">{{ userInfo.effectTime }}</el-descriptions-item> <el-descriptions-item label="合同失效时间" :span="2">{{ userInfo.lostEffectTime }}</el-descriptions-item> <el-descriptions-item label="合同人所属部门" :span="2"> <el-row v-for="item in deptList" :key="item.id"> <span v-if="item.id == userInfo.deptId">{{ item.label }}</span> </el-row> </el-descriptions-item> <el-descriptions-item label="合同结束时间" :span="4">{{ userInfo.endTime }}</el-descriptions-item> <el-descriptions-item label="详情" :span="4" style="height: auto"> 甲乙双方经平等协商。共同決定建立劳务关系。本服务协议属于劳务协议,不在《中华人民共和国芳动法》调整范围内,而在《中华人民共和国民法通则》、 《中华人民共和国合同法》的调整之中。甲乙双方在充分明确这一法律关系的基础上,根据《民法通则》、《合同法》和其他相关法律、法规,自愿签订本协议,共同道守协议所列条款。 </el-descriptions-item> <el-descriptions-item label="操作" :span="4" v-if="userInfo.state == 0 && !isCurrentTimeLater" > <template> <el-button size="small" type="primary" @click="update(userInfo.id)" >确认合同</el-button > </template> </el-descriptions-item> </el-descriptions> <el-descriptions v-else> <template>暂无合同,请联系管理员</template> </el-descriptions> <el-button v-if="userInfo" style="margin-top: 10px;margin-left: 10px;" type="danger" @click="downloadExcel(userInfo.id)">导出合同</el-button> <update v-if="UpdateVisible" ref="updateView" @refreshData="getContractsInfo" ></update> </div> </template> <script> import ElDescriptions from "../../common/e-desc.vue"; import ElDescriptionsItem from "../../common/e-desc-item.vue"; import Update from "./contracts-update.vue"; export default { components: { ElDescriptions, ElDescriptionsItem, Update, }, data() { return { userInfo: { id: 0, userId: 0, roleName: "", username: "", deptId: "", lostEffectTime: "", }, currentTime: new Date(), deptList: [], dataList:[], dataListLoading: false, UpdateVisible: false, }; }, mounted() { this.getDeptDataList(); this.getContractsInfo(); setInterval(() => { this.currentTime = new Date(); }, 1000); }, computed: { isCurrentTimeLater() { this.currentTime.setHours(0, 0, 0, 0); this.lostEffectTime = new Date(this.userInfo.lostEffectTime); return this.currentTime > this.lostEffectTime; }, }, methods: { getContractsInfo() { this.dataListLoading = true; this.$http({ url: this.$http.adornUrl(`/contracts/info`), method: "get", params: this.$http.adornParams(), }).then(({ data }) => { if (data && data.code === 0) { this.userInfo = data.contracts; this.dataList.push(data.contracts); } else { this.userInfo = []; this.totalPage = 0; } this.dataListLoading = false; }); }, // 获取部门数据列表 getDeptDataList() { this.dataListLoading = true; this.$http({ url: this.$http.adornUrl("/sys/dept/tree"), method: "get", params: this.$http.adornParams(), }).then(({ data }) => { this.deptList = data; }); }, //完善合同 update(id) { this.UpdateVisible = true; this.$nextTick(() => { this.$refs.updateView.init(id); }); }, //导出全部 downloadExcel(id) { var ids=this.dataList.map(item => item.id); this.$confirm(`确定对[id=${ids.join(",")}]进行导出操作?`, "提示", { confirmButtonText: "确定", cancelButtonText: "取消", type: "warning", }).then(() => { // 发送 HTTP 请求以下载 Excel 文件 this.$http({ url: this.$http.adornUrl("/contracts/download"), method: "post", data: this.$http.adornData(ids, false), responseType: "blob", // 设置响应类型为二进制流 }) .then((response) => { // 创建一个 URL 对象,指向返回的二进制数据 const url = window.URL.createObjectURL(new Blob([response.data])); // 创建一个 <a> 元素,设置其属性,模拟点击下载 const link = document.createElement("a"); link.href = url; link.setAttribute("download", "data.xlsx"); // 设置下载文件的默认名称 document.body.appendChild(link); link.click(); // 清理创建的 URL 对象 window.URL.revokeObjectURL(url); }) .catch((error) => { console.error("下载失败", error); }); }); }, }, }; </script>
复制
参数说明
至此,代码就写完啦,描述列表不生效的问题应该就解决了