在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
需求: 后台管理中常常有如下布局的数据展示需求: 像表格又不是表格,像表单又不是表单,实际上样子像表格,呈现的数据是一个对象,和 form 的绑定的值一样,我将其称为表单式表格。 样式深的列是标题,浅的列是标题对应的取值,数据往往是服务器返回的,标题往往是定宽的,取值可能各种各样,比如显示一张图片,值为 01,需要显示是与否,有时候需要添加一个修改按钮,让用户能修改某些值,还需要设置某一列跨越几列。 先来看看一个基于 不好的实现: 在接手的项目看到一个实现,先看使用方式 <FormTable :data="lessonPackageArr" :fleldsInfo="lessonPackageInfo" :maxColumn="3" label-width="120px"> <template #presentedHours="{ data }"> <div class="flex-box between"> <span> {{ data.presentedHours }} </span> <span class="column-btn" @click="editPresentedHours(data)">修改</span> </div> </template> <template #gifts="{ data }"> <div class="flex-box between"> <span> {{ data.gifts }} </span> <span class="column-btn" @click="editPresentedHours(data)">修改</span> </div> </template> </FormTable> lessonPackageInfo 对象如下结构: // 一个对象,用于配置标题列和标题列对应的字段 // type 指定值的类型,现在组件内部设置可能显示哪些类型的值了 // 对于服务其返回 1 0 需要显示 是否的数,提供一个 map_data 来映射 // column 属性设置跨列 // 需要自定义显示内容 提供 slot lessonPackageInfo: { orderType: { type: 'option', desc: '课时包类别', map_data: { 1: '首单', 2: '续费', 5: '赠课' } }, combo: { type: 'text', desc: '套餐名称' }, presentedHours: { type: 'text', desc: '赠送课时', slot: true }, price: { type: 'text', desc: '标准价格' }, gifts: { type: 'text', desc: '赠送礼物', column: 3, slot: true }, }
为何组件的配置项多不好? 对于这种需求很固定,组件的输入即 为何不是完全的数据驱动不好? 这个组件不是完全数据驱动的,需要自定义显示列是,需要编写模板。 如果需要自定义的列很多,就要写很多模板代码,想要再提取,只能再次封装组件,不提取,模板代码可能会膨胀,你可能经常看到动辄 500 行一行的 为何需要完全的数据驱动? 虽然有 slot 来扩展组件,但是我们在写业务组件时候应该少用,而是尽量使用数据驱动模板。因为数据是 js 代码,当组件代码膨胀时,很容易把 js 代码提取成单独的文件, 而想要提取 slot 的代码,只能再封装组件。
看了组件使用的问题,再看组件的代码: <template> <div v-if="tableData.length" class="form-table"> <div v-for="(data, _) in tableData" :key="_" class="table-border"> <el-row v-for="(row, index) in rows" :key="index"> <el-col v-for="(field, key) in row" :key="key" :span="getSpan(field.column)"> <div v-if="(field.disabled && data[key]) || !field.disabled" class="column-content flex-box between"> <div class="label" :style="'width:' + labelWidth"> <span v-if="field.required" class="required">*</span> {{ field.desc }} </div> <div class="text flex-item" :title="data[key]"> <template v-if="key === 'minAge'"> <span>{{ data[key] }}</span> - <span>{{ data['maxAge'] }}</span> </template> <template v-else-if="key === 'status'"> <template v-if="field.statusList"> <span v-if="data[key] == 0" :class="field.statusList[2]">{{ field.map_data[data[key]] }}</span> <span v-else-if="data[key] == 10 || data[key] == 34" :class="field.statusList[1]"> {{ field.map_data[data[key]] }} </span> <span v-else :class="field.statusList[0]">{{ field.map_data[data[key]] }}</span> </template> <span v-else>{{ field.map_data[data[key]] }}</span> </template> <slot v-else :name="key" v-bind:data="data"> <TableColContent :dataType="field.type" :metaData="data[key]" :mapData="field.map_data" :text="field.text" /> </slot> </div> </div> </el-col> </el-row> </div> </div> <div v-else class="form-table empty">暂无数据</div> </template> <script> import TableColContent from '@/components/TableColContent' export default { name: 'FormTable', components: { TableColContent, }, props: { // 数据 data: { required: true, type: [Object, Array, null], }, // 字段信息 fleldsInfo: { required: true, type: Object, // className: { type: "text", desc: "班级名称", column: 3 }, }, // 最多显示列数 maxColumn: { required: false, type: Number, default: 2, }, labelWidth: { required: false, type: String, default: '90px', }, }, data() { return {} }, computed: { tableData() { if (!this.data) { return [] } if (this.data instanceof Array) { return this.data } else { return [this.data] } }, rows() { const returnArray = [] let total = 0 let item = {} for (const key in this.fleldsInfo) { const nextTotal = total + this.fleldsInfo[key].column || 1 if (nextTotal > this.maxColumn) { returnArray.push(item) item = {} total = 0 } total += this.fleldsInfo[key].column || 1 item[key] = this.fleldsInfo[key] if (total === this.maxColumn) { returnArray.push(item) item = {} total = 0 } } if (total) { returnArray.push(item) } return returnArray }, }, methods: { getSpan(column) { if (!column) { column = 1 } return column * (24 / this.maxColumn) }, }, } </script> 有哪些问题?
部分代码: <span v-else-if="dataType === 'image' || dataType === 'cropper'" :class="className"> <el-popover placement="right" title="" trigger="hover"> <img :src="metaData" style="max-width: 600px;" /> <img slot="reference" :src="metaData" :alt="metaData" width="44" class="column-pic" /> </el-popover> </span> 分析完以上实现的问题,看看好的实现: 好的实现: 先看使用方式: <template> <ZmFormTable :titleList="titleList" :data="data" /> </template> <script> export default { name: 'Test', data() { return { data: {}, // 从服务器获取 titleList: [ { title: '姓名', prop: 'name', span: 3 }, { title: '课堂作品', prop: (h, data) => { const img = (data.workPic && ( <ElImage style='width: 100px; height: 100px;' src={data.workPic} preview-src-list={[data.workPic]} ></ElImage> )) || '' return img }, span: 3, }, { title: '作品点评', prop: 'workComment', span: 3 }, ], } }, } </script> 组件说明: prop 支持 参数 h 和 data 是如何传递进来的?或者 这函数在哪调用呢? h 是 当普通函数的第一个参数是 h 是,它就是一个 这种方式使用起来简单多了。 看看内部实现: <template> <div class="form-table"> <ul v-if="titleList.length"> <!-- titleInfo 是经过转化的titleList--> <li v-for="(item, index) in titleInfo" :key="index" :style="{ width: ((item.span || 1) / titleNumPreRow) * 100 + '%' }" > <div class="form-table-title" :style="`width: ${titleWidth}px;`"> <Container v-if="typeof item.title === 'function'" :renderContainer="item.title" :data="data" /> <span v-else> {{ item.title }} </span> </div> <div class="form-table-key" :style="`width:calc(100% - ${titleWidth}px);`"> <Container v-if="typeof item.prop === 'function'" :renderContainer="item.prop" :data="data" /> <span v-else> {{ ![null, void 0].includes(data[item.prop] && data[item.prop]) || '' }} </span> </div> </li> </ul> <div v-else class="form-table-no-data">暂无数据</div> </div> </template> <script> import Container from './container.js' export default { name: 'FormTable', components: { Container, }, props: { titleWidth: { type: Number, default: 120, }, titleNumPreRow: { type: Number, default: 3, validator: value => { const validate = [1, 2, 3, 4, 5, 6].includes(value) if (!validate) { console.error('titleNumPreRow 表示一行有标题字段对,只能时 1 -- 6 的偶数,默认 3') } return validate }, }, titleList: { type: Array, default: () => { return [] }, validator: value => { const validate = value.every(item => { const { title, prop } = item return title && prop }) if (!validate) { console.log('传入的 titleList 属性的元素必须包含 title 和 prop 属性') } return validate }, }, data: { type: Object, default: () => { return {} }, }, }, } </script> <!-- 样式不是关键,省略 --> 实现自定义显示的方式,没有使用动态插槽,而是用一个函数组件 export default { name: 'Container', functional: true, render(h, { props }) { return props.renderContainer(h, props.data) }, } 在 总结:
到此这篇关于使用 render 函数封装高扩展的组件的文章就介绍到这了,更多相关 render 函数封装高扩展组件内容请搜索极客世界以前的文章或继续浏览下面的相关文章希望大家以后多多支持极客世界! |
请发表评论