前言
对 UI 框架进行扩展,是进行组件化开发的第一步。即使现在有许多优秀的 UI 框架为我们提供了便利,为了满足项目的整体风格,基于 UI 框架所提供的组件进行二次扩展也是必不可少的一步。这样做不仅可以满足系统整体 UI 风格保持统一,也提供了统一的修改入口,避免以后需求变更导致需要修改多处地方。
扩展 UI 组件
第一种
一种是直接使用 mixins 混入新配置。其优点是,在现有 UI 组件上直接修改默认配置,其余功能都会得到保留。
<script>
import { Form } from 'element-ui';
export default {
name: 'vForm',
mixins: [Form],
props: {
'label-position': {
default: 'right',
},
},
};
</script>
第二种
这一种主要适用于单一组件功能的封装,缺点是属性、事件以及插槽等功能需要再次传递给 UI 组件。
<template>
<el-dialog
v-bind="$attrs"
v-on="$listeners"
:close-on-click-modal="false"
>
<span slot="title" v-if="!$attrs.title">
<slot name="title"></slot>
</span>
<slot></slot>
<span slot="footer">
<slot name="footer"></slot>
</span>
</el-dialog>
</template>
<script>
export default {
name: 'vDialogDemo',
};
</script>
<style lang="scss" scoped>
/deep/ .el-dialog__footer {
text-align: center;
}
</style>
表格组件
这里贴一个以前项目里封装的表格组件,主要是将分页组件和表格组件进行封装,以及统一表格样式。保证了切换分页时的逻辑统一,以及支持前端分页、根据 cols 参数渲染表格。
<template>
<div>
<el-pagination
v-if="(showTopPage || pageable) && $attrs.data && $attrs.data.length"
@size-change="changeSize"
@current-change="changePage"
:current-page="pageParams.pageNumber"
:page-sizes="pageParams.pageSizes || [10, 20, 50, 100]"
:page-size="pageParams.pageSize"
:layout="layout"
:total="pageParams.totalCount"
style="text-align: right; margin-bottom: 20px"
></el-pagination>
<div style="height: 100%">
<el-table
ref="vTableDemoRef"
v-if="!independentPageable"
:header-cell-style="headerCellStyle"
:cell-style="cellStyle"
:border="border"
style="width: 99.9%"
@select="selectInfo"
@select-all="selectAllInfo"
v-bind="$attrs"
v-on="$listeners"
@sort-change="handleSortChange"
>
<!-- 自定义列 -->
<slot></slot>
<!-- 根据参数渲染列 -->
<template v-for="(col, index) in cols">
<!-- 自定义组件。使用时在cols参数中增加component字段 -->
<component
v-if="col.component"
:key="index"
:is="col.component"
:colConfig="col"
></component>
<el-table-column
v-else
v-bind="col"
:key="index"
:align="col.align || 'center'"
></el-table-column>
</template>
<slot name="after"></slot>
</el-table>
</div>
<div style="height: 100%">
<el-table
ref="vTableDemoRef"
v-if="independentPageable"
:header-cell-style="headerCellStyle"
:cell-style="cellStyle"
:border="border"
:data="independentTableData"
style="height: 100%; width: 99.9%"
v-bind="$attrs"
v-on="$listeners"
@sort-change="handleSortChange"
>
<slot></slot>
</el-table>
</div>
<el-pagination
v-if="showBottomPage || pageable"
background
@size-change="changeSize"
@current-change="changePage"
:current-page="pageParams.pageNumber"
:page-sizes="pageParams.pageSizes || [10, 20, 50, 100]"
:page-size="pageParams.pageSize"
:layout="layout"
:total="pageParams.totalCount"
style="text-align: right; margin-top: 1.4vh"
></el-pagination>
</div>
</template>
<script>
export default {
props: {
sortCustom: {
type: Boolean,
default: false,
},
// 表格头样式
headerCellStyle: {
default: () => ({
background: '#F6F6F6',
color: '#333333',
}),
},
// 单元格样式
cellStyle: {
default: () => ({
'font-weight': 'blod',
color: '#333333',
'font-size': '14px',
}),
},
border: {
default: () => true,
},
// 表格列参数
cols: {
default: () => [],
},
// 是否显示分页
pageable: {
type: Boolean,
default: false,
},
// 是否前端自己分页
independentPageable: {
type: Boolean,
default: false,
},
tableData: {
type: Array,
default: () => [],
},
// 是否显示表格上方的分页
showTopPage: {
type: Boolean,
default: false,
},
// 是否显示表格下方的分页
showBottomPage: {
type: Boolean,
default: false,
},
// 更新分页参数后的回调方法,用于重新请求数据
getData: {
type: Function,
default: () => { },
},
// 用于自定义分页参数
pageData: {
type: Object,
default: () => ({}),
},
layout: {
type: String,
default: 'prev, pager, next, total, sizes, jumper',
},
appendData: {
type: Function,
default: () => { },
},
},
computed: {
independentTableData() {
let startIndex = (this.pageParams.pageNumber - 1) * this.pageParams.pageSize;
let endIndex = this.pageParams.pageNumber * this.pageParams.pageSize;
// 如果数据源减少,可能需要获取前页数据
if (startIndex >= this.tableData.length) {
const pageNumber = Math.ceil(this.tableData.length / this.pageParams.pageSize);
startIndex = (pageNumber - 1) * this.pageParams.pageSize;
endIndex = pageNumber * this.pageParams.pageSize;
}
const slicedTableData = this.tableData.slice(startIndex, endIndex);
this.appendData(slicedTableData);
return slicedTableData;
},
},
data() {
return {
pageParams: {
// 默认分页参数
pageNumber: 1,
pageSize: 10,
totalCount: 0,
totalPage: 0,
searchCount: true,
optimizeCountSql: true,
// 自定义分页参数,可覆盖上面的值
...this.pageData,
},
sort: {
ascs: '',
descs: '',
},
};
},
methods: {
expandAll() {
const els = this.$el.getElementsByClassName('el-table__expand-icon');
for (let i = 0; i < els.length; i++) {
els[i].click();
}
},
changeSize(val) {
this.pageParams.pageSize = val;
this.pageParams.pageNumber = 1;
if (!this.independentPageable) {
this.getData();
}
this.$emit('changePageSize');
},
changePage(val) {
this.pageParams.pageNumber = val;
if (!this.independentPageable) {
this.getData();
}
this.$emit('changePageSize');
},
// 用于获取当前的分页参数
getPageData(needSortParams = false, { ascs = '', descs = '' } = { ascs: '', descs: '' }) {
if (needSortParams) {
const ascsArr = [];
const descsArr = [];
if (this.sort.ascs) ascsArr.push(this.sort.ascs);
if (this.sort.descs) descsArr.push(this.sort.descs);
if (ascs) ascsArr.push(ascs);
if (descs) descsArr.push(descs);
return {
ascs: ascsArr.join(','),
descs: descsArr.join(','),
...this.pageParams,
};
}
return this.pageParams;
},
search(url, params, callback) {
const cb = params ? callback : params;
this.$axios.get(url, {
params: {
...this.pageParams,
...params,
},
}).then((res) => {
this.updateByResponse(res);
cb(res);
});
},
// 用于清空分页信息
resetSearchParams() {
this.pageParams.pageNumber = 1;
this.pageParams.pageSize = 10;
this.pageParams.totalCount = 0;
this.pageParams.totalPage = 0;
return this.pageParams;
},
// 用于搜索时,设置分页为第一页
onSearch() {
this.pageParams.pageNumber = 1;
return this.pageParams;
},
resetIndependentPageable() {
this.pageParams.pageNumber = 1;
this.pageParams.pageSize = 10;
return this.pageParams;
},
// 用于查询后,更新分页参数中的总数
updateByResponse(res) {
this.$set(this.pageParams, 'totalCount', res.totalCount);
this.$set(this.pageParams, 'pageSize', res.pageSize);
this.$set(this.pageParams, 'pageNumber', res.pageNumber);
},
updateByLength(num) {
this.$set(this.pageParams, 'totalCount', num);
},
// 单个选择
selectInfo(selectInfo) {
this.$emit('selectInfo', selectInfo);
},
// 选择全部
selectAllInfo(selectAllInfo) {
this.$emit('selectAllInfo', selectAllInfo);
},
// 变更选中状态
toggleRowSelection(row, status) {
if (this.$refs.vTableDemoRef) {
this.$refs.vTableDemoRef.toggleRowSelection(row, status);
}
},
toggleAllSelection() {
if (this.$refs.vTableDemoRef) {
this.$refs.vTableDemoRef.toggleAllSelection();
}
},
clearSelection() {
this.$refs.vTableDemoRef.clearSelection();
},
/**
* 根据关键字切换行状态
* @param {Array} data 表格数据
* @param {String} key 关键字code
* @param {Array} selectedKeys 指定选中关键字
* @example
*
* this.$refs.vTableDemoRef.selectRows(tableData, 'id', ['12222','1555677']);
*/
toggleRowsSelectionByKey(data, key, selectedKeys, status = true) {
if (!data || data.length === 0) return;
if (!selectedKeys || selectedKeys.length === 0) return;
// 筛选需要选中行
const selectedRows = data.filter(row => selectedKeys.findIndex(selectedKey => selectedKey === row[key]) > -1);
// 选中
if (selectedRows.length) {
selectedRows.forEach((row) => {
this.toggleRowSelection(row, status);
});
}
},
handleSortChange({ prop, order }) {
if (!order) {
this.sort = {
ascs: '',
descs: '',
};
} else if (order === 'ascending') {
this.sort = {
ascs: prop,
descs: '',
};
} else {
this.sort = {
ascs: '',
descs: prop,
};
}
// TODO: better
// const isAsc = order === 'ascending';
// const isDesc = !isAsc;
// this.sort = {
// ascs: order && isAsc ? prop : '',
// descs: order && !isDesc ? prop : '',
// };
if (!this.sortCustom) {
this.getData();
}
},
},
};
</script>
<style lang="scss" scoped>
.el-input-number {
width: 100%;
/deep/ input {
text-align: left;
}
}
/deep/ .el-table {
font-size: 14px;
color: #333333;
th {
padding: 0;
}
thead {
tr {
height: 30px;
}
}
tbody {
tr {
height: 32px;
td {
padding: 0;
}
}
}
}
</style>