1.先看效果
使用
<self-table
:option="indexOption"
:data="list"
@selection-change="handleSelectionChange"
>
</self-table>
option配置
export const indexOption = {
column: [
{ label: '出库单号', prop: 'deliveryOrderNo' },
{ label: '相关单号', prop: 'relevantDocNo' },
{ label: '计划日期', prop: 'plannedDate', type: 'date', timeformat: '{y}-{m}-{d}' },
{ label: '计划类型', prop: 'planType', type: 'dic', dic: 'sys_plan_type' },
{ label: '货主名称', prop: 'companyName' },
{ label: '货品类型数量', prop: 'itemTypeQuantity' },
{ label: '创建人', prop: 'creatorName' },
{ label: '创建日期', prop: 'createTime', type: 'date', timeformat: '{y}-{m}-{d}' },
{ label: '状态', prop: 'state', type: 'status', dic: 'sys_instore_status' },
],
optionWidth: 300,
selection: true,
defaluetOp: true,
};
这样就可以直接配置出个table了,如果你想要这样的效果就往下看
2.实现
定义selfTable组件(配置了unplugin-auto-import就不用到处import vue的东西了)
<template>
<el-row >
<el-table
:height="option.height"
:data="data"
:default-expand-all="option.defaultExpandAll"
:tree-props="{ children: option.childrenProp, hasChildren: option.hasChildrenProp }"
@selection-change="handleSelectionChange"
@sort-change="handleSortChange"
>
<el-table-column v-if="option.selection" type="selection" width="55"> </el-table-column>
<el-table-column v-if="option.expand" style="padding: 0 40px" type="expand">
<template #default="scope">
<el-table class="inner-table" :data="scope.row[option.inData]">
<el-table-column
:width="itm.width"
v-for="itm in option.inColumn"
:align="itm.align"
:label="itm.label"
:prop="itm.prop"
:fixed="itm.fixed"
>
<template #default="sp">
<template v-if="itm.type != 'slot'">
<span v-if="itm.type == 'dic'">{{ selectDictLabel(dictData[itm.dic], sp.row[itm.prop]) }}</span>
<span v-else-if="itm.type == 'date'">
{{ itm.type == 'date' ? parseTime(sp.row[itm.prop], itm.timeformat) : '' }}</span
>
<span v-else>
<span v-if="itm.prefix">{{ itm.prefix }}</span>
<span> {{ sp.row[itm.prop] }}</span>
<span v-if="itm.suffix">{{ itm.suffix }}</span>
</span>
</template>
<template v-else>
<slot :name="'in-' + itm.prop" :row="sp.row" :out="scope.row" :$index="sp.$index"></slot>
</template>
</template>
<template v-if="itm.tip" #header="sp2">
<span>{{ itm.label }}</span>
<el-tooltip class="item" :content="itm.tipcontent" effect="dark" placement="top">
<el-icon color="#409eff"><question-filled /></el-icon>
</el-tooltip>
</template>
</el-table-column>
<el-table-column v-if="option.inOption" label="操作" align="center" class-name="small-padding fixed-width">
<template #default="sp">
<slot name="in-option" :outIndex="scope.$index" :row="sp.row" :$index="sp.$index"></slot>
</template>
</el-table-column>
</el-table>
</template>
</el-table-column>
<el-table-column
v-for="item in option.column"
:width="item.width"
:align="item.align"
:label="item.label"
:prop="item.prop"
:fixed="item.fixed"
>
<template #default="scope">
<template v-if="item.type == 'dic'">
<span>{{ selectDictLabel(dictData[item.dic], scope.row[item.prop]) }}</span>
</template>
<template v-else-if="item.type == 'date'">
<span>{{ parseTime(scope.row[item.prop], item.timeformat) }}</span>
</template>
<template v-else-if="item.type == 'slot'">
<slot :name="item.prop" :row="scope.row" :$index="scope.$index"></slot>
</template>
<template v-else-if="item.type == 'status'">
<p class="status-dot" :style="{ color: getDictProp(dictData[item.dic], scope.row[item.prop], 'color') }">
<span :style="{ background: getDictProp(dictData[item.dic], scope.row[item.prop], 'color') }"></span>
{{ selectDictLabel(dictData[item.dic], scope.row[item.prop]) }}
</p>
</template>
<template v-else>
<span v-if="item.prefix">{{ item.prefix }}</span>
<span>{{ scope.row[item.prop] }}</span>
<span v-if="item.suffix">{{ item.suffix }}</span>
</template>
</template>
<template v-if="item.tip" #header="sp">
<span>{{ item.label }}</span>
<el-tooltip class="item" :content="item.tipcontent" effect="dark" placement="top">
<el-icon color="#409eff"><question-filled /></el-icon>
</el-tooltip>
</template>
</el-table-column>
<el-table-column
:width="option.optionWidth"
v-if="option.defaluetOp"
label="操作"
align="center"
class-name="small-padding fixed-width"
>
<template #default="scope">
<slot name="defaluetOp" :row="scope.row" :$index="scope.$index"></slot>
</template>
</el-table-column>
<slot></slot>
</el-table>
</el-row>
</template>
<script setup>
import * as dictData from '@/utils/dictData';
const props = defineProps({
option: {
selection: {
type: Boolean,
default: false,
},
expand: {
type: Boolean,
default: false,
},
defaluetOp: {
type: Boolean,
default: false,
},
defaultExpandAll: {
type: Boolean,
default: false,
},
childrenProp: {
type: String,
default: 'children',
},
hasChildrenProp: {
type: String,
default: 'hasChildren',
},
inData: {
type: String,
default: 'list',
},
inOption: {
type: Boolean,
default: false,
},
optionWidth: {
type: String,
default: '200',
},
column: [],
},
data: {
type: Array,
default: [],
},
});
function handleSelectionChange(selection) {
emit('selectionChange', selection);
}
function refresh() {
refreshTable.value = false;
nextTick(() => {
refreshTable.value = true;
});
}
const emit = defineEmits(['selectionChange']);
defineExpose({
refresh,
});
</script>
<style scoped></style>
用到的字典转换和时间转换方法
// 回显数据字典
export function selectDictLabel(dicts, value) {
if (value === undefined) {
return '';
}
if (dicts === undefined) {
return '';
}
const actions = [];
Object.keys(dicts).some((key) => {
if (dicts[key].value === `${value}`) {
actions.push(dicts[key].label);
return true;
}
});
if (actions.length === 0) {
actions.push(value);
}
return actions.join('');
}
//返回字典其他栏位
export function getDictProp(dicts, value, prop) {
if (value === undefined) {
return '';
}
if (dicts === undefined) {
return '';
}
let res = '';
Object.keys(dicts).some((key) => {
if (dicts[key].value === `${value}`) {
res = dicts[key][prop];
return true;
}
});
return res;
}
// 日期格式化
export function parseTime(time, pattern) {
if (arguments.length === 0 || !time) {
return null;
}
const format = pattern || '{y}-{m}-{d} {h}:{i}:{s}';
let date;
if (typeof time === 'object') {
date = time;
} else {
if (typeof time === 'string' && /^[0-9]+$/.test(time)) {
time = parseInt(time);
} else if (typeof time === 'string') {
time = time
.replace(new RegExp(/-/gm), '/')
.replace('T', ' ')
.replace(new RegExp(/\.[\d]{3}/gm), '');
}
if (typeof time === 'number' && time.toString().length === 10) {
time *= 1000;
}
date = new Date(time);
}
const formatObj = {
y: date.getFullYear(),
m: date.getMonth() + 1,
d: date.getDate(),
h: date.getHours(),
i: date.getMinutes(),
s: date.getSeconds(),
a: date.getDay(),
};
const timeStr = format.replace(/{(y|m|d|h|i|s|a)+}/g, (result, key) => {
let value = formatObj[key];
// Note: getDay() returns 0 on Sunday
if (key === 'a') {
return ['日', '一', '二', '三', '四', '五', '六'][value];
}
if (result.length > 0 && value < 10) {
value = `0${value}`;
}
return value || 0;
});
return timeStr;
}
然后就是注册全局组件,全局的方法 ,在main.js中
import { selectDictLabel, getDictProp } from '@/utils/dict';
import { parseTime } from '@/utils/dateUtil';
import SelfTable from '@/components/selfPack/selfTable';
app.config.globalProperties.parseTime = parseTime;
app.component('SelfTable', SelfTable);
至此就可以在项目中直接使用了
3.复杂一点的使用
在实际项目中会有比如嵌套表格,按钮,或者树形结构的情况,设置了一些slot的位置
比如组件中
<slot :name="'in-' + itm.prop" :row="sp.row" :out="scope.row" :$index="sp.$index"></slot>
后续在使用中就可以用in-栏位名指定到内部嵌套表单的slot, sp.out取到外层数据
<self-table
v-loading="loading"
:option="detailOption"
style="width: 100%"
:data="params.inStockMaterialDTOList"
@selection-change="handleSelectionChange"
>
<template #in-defaultStorageAreaName="sp">
{{ sp.out.defaultStorageAreaName }}
</template>
</self-table>
下面是嵌套表单的范例
export const detailOption = {
selection: true,
expand: true,
defaultExpandAll: true,
column: [
{ label: 'xxx', prop: 'materialName' },
{ label: 'xxx', prop: 'materialNo' },
{ label: 'xxx', prop: 'companyName', type: 'slot' },
{ label: 'xxx', prop: 'materialType', type: 'slot' },
{ label: 'xxx', prop: 'defaultStorageAreaName' },
{ label: 'xxx', prop: 'alertDays', suffix: '天' },
{ label: 'xxx', prop: 'color' },
{ label: 'xxx', prop: 'decorativePattern' },
{ label: 'xxx', prop: 'gramWeight' },
{ label: 'xxx', prop: 'unit' },
{ label: 'xxx', prop: 'measurement' },
{ label: 'xxx', prop: 'num', type: 'slot' },
],
inData: 'inStockMaterialRollEntityList',
inColumn: [
{ label: 'xxx', prop: 'serialNumber', width: '200' },
{ label: 'xxx', prop: 'companyName', type: 'slot' },
{ label: 'xxx', prop: 'materialType', type: 'slot' },
{ label: 'xxx', prop: 'createTime', type: 'date', timeformat: '{y}-{m}-{d}' },
{ label: 'xxx', prop: 'batchNumber' },
{ label: 'xxx', prop: 'materialStatus' },
{ label: 'xxx', prop: 'defaultStorageAreaName', type: 'slot' },
{ label: 'xxx', prop: 'storageQuantity' },
{ label: 'xxx', prop: 'unit' },
],
defaluetOp: true,
inOption: true,
};
4. 栏位说明
option说明
selection: {
type: Boolean,
default: false, //是否可选
},
expand: {
type: Boolean,
default: false,//是否能展开
},
defaluetOp: {
type: Boolean,
default: false,//是否有操作栏位
},
defaultExpandAll: {
type: Boolean,
default: false,//是否默认展开所有
},
inData: {
type: String,
default: 'list',//嵌套表单栏位名
},
inOption: {
type: Boolean,//嵌套表单是否可选
default: false,
},
childrenProp: {
type: String, //树形表单children栏位
default: 'children',
},
hasChildrenProp: {
type: String,//树形表单hasChildren栏位 参照element
default: 'hasChildren',
},
optionWidth: {
type: String,//操作栏宽度
default: '200',
},
column: {
type: Array,
default: [],//列栏位数组
},
inColumn: {
type: Array,
default: [],//嵌套表单列栏位数组
},
column/inColumn说明
label:{
type: String,//表单栏位名称
},
prop:{
type: String,//表单栏位名
},
type:{
type: String,//表单栏位类型
// dic 配合 dic栏位指定数据字典
// date 配合 timeFormat栏位格式化时间
// slot 自定义插槽
},
prefix:{
type: String,//表单栏位前缀
},
suffix:{
type: String,//表单栏位后缀
},
tip:{
type:Boolean, //表单栏位列名称是否显示提示
defalut:false //配合tipcontent 栏位 显示具体内容
}
5 .todo
数据字典暂时是本地的配置文件,后续会修改到远程接口的,额外的功能大家可以根据自己的项目要求再修改一下