<template>
|
<div>
|
<!-- 表格 -->
|
<el-table
|
ref="multipleTable"
|
v-loading="tableLoading"
|
:border="border"
|
:data="tableData"
|
:header-cell-style="headerCellStyleResolved"
|
:height="height"
|
:highlight-current-row="highlightCurrentRow"
|
:row-class-name="rowClassName"
|
:row-style="rowStyle"
|
:row-key="rowKey"
|
stripe
|
style="width: 100%"
|
tooltip-effect="dark"
|
class="lims-table"
|
@row-click="rowClick"
|
@current-change="currentChange"
|
@selection-change="handleSelectionChange"
|
>
|
<el-table-column v-if="isSelection" align="center" type="selection" width="55" />
|
<el-table-column align="center" label="序号" type="index" width="60" :index="indexMethod" />
|
|
<el-table-column
|
v-for="(item, index) in column"
|
:key="index"
|
:column-key="item.columnKey"
|
:filter-method="item.filterHandler"
|
:filter-multiple="item.filterMultiple"
|
:filtered-value="item.filteredValue"
|
:filters="item.filters"
|
:fixed="item.fixed"
|
:label="item.label"
|
:prop="item.prop"
|
:show-overflow-tooltip="item.dataType === 'action' || item.dataType === 'slot' ? false : true"
|
:min-width="columnWidth(item)"
|
:sortable="item.sortable ? true : false"
|
:type="item.type"
|
:width="columnWidth(item)"
|
align="center"
|
>
|
<template #default="scope">
|
<!-- 插槽 -->
|
<div v-if="item.dataType == 'slot'">
|
<slot :name="item.slot" :row="scope.row" :index="scope.$index" />
|
</div>
|
<!-- 图片 -->
|
<div v-else-if="item.dataType == 'image'">
|
<img
|
:src="javaApi + '/img/' + scope.row[item.prop]"
|
alt=""
|
style="width: 40px; height: 40px; margin-top: 10px"
|
/>
|
</div>
|
<!-- tag -->
|
<LimsTagCell
|
v-else-if="item.dataType == 'tag'"
|
:value="scope.row[item.prop]"
|
:format-data="item.formatData"
|
:format-type="item.formatType"
|
/>
|
<!-- 按钮 -->
|
<LimsActionCell v-else-if="item.dataType == 'action'" :operations="item.operation || []" :row="scope.row" />
|
<!-- 可点击的文字 -->
|
<div
|
v-else-if="item.dataType == 'link'"
|
class="cell link"
|
style="width: 100%"
|
@click="goLink(scope.row, item.linkMethod)"
|
>
|
<span v-if="!item.formatData">{{ scope.row[item.prop] }}</span>
|
</div>
|
<!-- 默认纯展示数据 -->
|
<div v-else class="cell" style="width: 100%">
|
<span v-if="!item.formatData">{{ scope.row[item.prop] }}</span>
|
<span v-else>{{ fmt(scope.row[item.prop], item.formatData) }}</span>
|
</div>
|
</template>
|
</el-table-column>
|
</el-table>
|
|
<pagination
|
v-show="page.total > 0"
|
:total="page.total"
|
:layout="page.layout"
|
v-model:page="page.current"
|
v-model:limit="page.size"
|
@pagination="onPagination"
|
/>
|
</div>
|
</template>
|
|
<script setup>
|
import { ElMessage } from 'element-plus'
|
import LimsTagCell from './LimsTagCell'
|
import LimsActionCell from './LimsActionCell'
|
import { actionWidth, DEFAULT_ACTION_COLUMN_WIDTH } from './action-width'
|
|
defineOptions({ name: 'LimsTable' })
|
|
// 注:以下是所有配置项,其中最常使用的就是 label、width、prop、dataType、slot
|
// label :列名,就是表头上的标签叫什么些什么,类型 string
|
// width:该列宽度, string
|
// prop: table绑定数据字段 string,这一列要展示哪个tableData里面的字段就写哪个字段
|
// dataType: 内置多个基本的element组件可供直接使用 string
|
// slot 当`dataType`为`slot`时必带参数,参数值为插槽的 `slot` 值 string
|
// fixed:列是否固定在左侧或者右侧,true 表示固定在左侧 string, boolean
|
// sortable 对应列是否可以排序 boolean, string
|
// filters 数据过滤的选项,数组格式,数组中的元素需要有 text 和 value 属性。Array
|
// columnKey :column 的 key,如果需要使用 filter-change 事件,则需要此属性标识是哪个 column 的筛选条件
|
// filteredValue 选中的数据过滤项,如果需要自定义表头过滤的渲染方式,可能会需要此属性。
|
// filterMultiple 数据过滤的选项是否多选
|
// formatData 对数据进行数据处理,接受一个回调函数 (value) => any
|
// formatType 当 `dataType`为`tag`时,对标签颜色设置 (value) => 'danger'|'success'|...
|
// operation 当 `dataType` 为 `action` 时,对按钮的配置
|
// linkMethod 当 `dataType` 为 `link` 时,点击后调用的父组件方法名
|
// 表头配色交回全局 CSS(assets/styles/theme/element.scss 读 --lims-table-head-bg)。
|
// 这里保持空对象:header-cell-style 是内联样式,会盖掉全局皮肤,所以不能留默认值。
|
const DEFAULT_HEADER_CELL_STYLE = {}
|
|
const props = defineProps({
|
isSelection: {
|
type: Boolean,
|
default: false
|
},
|
height: {
|
type: String,
|
default: null
|
},
|
tableLoading: {
|
type: Boolean,
|
default: false
|
},
|
handleSelectionChange: {
|
type: Function,
|
default: () => {}
|
},
|
rowClick: {
|
type: Function,
|
default: () => {}
|
},
|
currentChange: {
|
type: Function,
|
default: () => {}
|
},
|
border: {
|
type: Boolean,
|
default: true
|
},
|
highlightCurrentRow: {
|
type: Boolean,
|
default: false
|
},
|
headerCellStyle: {
|
type: Object,
|
default: () => ({})
|
},
|
column: {
|
type: Array,
|
default: () => []
|
},
|
rowClassName: {
|
type: Function,
|
default: () => {}
|
},
|
rowStyle: {
|
type: [Object, Function],
|
default: () => null
|
},
|
tableData: {
|
type: Array,
|
default: () => []
|
},
|
rowKey: {
|
type: String,
|
default: undefined
|
},
|
page: {
|
type: Object,
|
default: () => ({
|
total: 0,
|
current: 0,
|
size: 10,
|
layout: 'total, sizes, prev, pager, next, jumper'
|
})
|
}
|
})
|
|
const emit = defineEmits(['pagination'])
|
|
const multipleTable = ref()
|
const instance = getCurrentInstance()
|
|
const headerCellStyleResolved = computed(() => {
|
const style = props.headerCellStyle || {}
|
return Object.keys(style).length ? style : DEFAULT_HEADER_CELL_STYLE
|
})
|
|
// 操作列宽度:原实现渲染期改写共享的 btnWidth,列宽取决于最后渲染的行;
|
// 这里取所有行的最大值,保证按钮不会被截断。
|
const actionColumnWidth = computed(() => {
|
let max = 0
|
props.column.forEach(col => {
|
if (col.dataType !== 'action') return
|
const ops = col.operation || []
|
props.tableData.forEach(row => {
|
const w = actionWidth(ops, row)
|
if (w > max) max = w
|
})
|
})
|
return (max || DEFAULT_ACTION_COLUMN_WIDTH) + 'px'
|
})
|
|
function columnWidth(item) {
|
return item.dataType === 'action' ? actionColumnWidth.value : item.width
|
}
|
|
// formatData 为函数时做一次转换,否则原样展示
|
function fmt(val, format) {
|
return typeof format === 'function' ? format(val) : val
|
}
|
|
function indexMethod(index) {
|
return (props.page.current - 1) * props.page.size + index + 1
|
}
|
|
function onPagination({ page, limit }) {
|
emit('pagination', { page: page, limit: limit })
|
}
|
|
// 点击单元格 link 事件:沿 $parent 链向上找同名方法,兼容组件被包在弹窗/子组件里的场景
|
function goLink(row, linkMethod) {
|
if (!linkMethod) {
|
ElMessage.warning('请配置lingk事件')
|
return
|
}
|
let parent = instance.proxy.$parent
|
while (parent) {
|
if (typeof parent[linkMethod] === 'function') {
|
parent[linkMethod](row)
|
return
|
}
|
parent = parent.$parent
|
}
|
console.warn(`[lims-table] 未找到 link 处理函数:${linkMethod}`)
|
}
|
|
function setCurrent(row) {
|
multipleTable.value?.setCurrentRow(row)
|
}
|
|
onMounted(() => {
|
nextTick(() => {
|
multipleTable.value?.doLayout()
|
})
|
})
|
|
defineExpose({ setCurrent })
|
</script>
|
|
<style scoped>
|
.el-table :deep(.el-table__empty-text) {
|
text-align: center;
|
}
|
|
.link {
|
color: var(--el-color-primary);
|
cursor: pointer;
|
}
|
|
:deep(.cell) {
|
padding: 0 !important;
|
}
|
|
.cell {
|
white-space: nowrap;
|
overflow: hidden;
|
text-overflow: ellipsis;
|
padding-right: 4px !important;
|
padding-left: 10px !important;
|
}
|
|
.lims-table :deep(.highlight-warning-row-border td:first-child) {
|
border-left: 4px solid #ffcd29;
|
}
|
|
.lims-table :deep(.highlight-warning-row-border td:last-child) {
|
border-right: 4px solid #ffcd29;
|
}
|
|
.lims-table :deep(.highlight-danger-row-border td:first-child) {
|
border-left: 4px solid var(--el-color-danger);
|
}
|
|
.lims-table :deep(.highlight-danger-row-border td:last-child) {
|
border-right: 4px solid var(--el-color-danger);
|
}
|
|
:deep(.red-row td) {
|
background: #ffcccc !important;
|
}
|
</style>
|