Files
klp-mono/apps/hand-factory/components/klp-ui/k-table/k-table.vue

331 lines
7.8 KiB
Vue
Raw Normal View History

2025-10-27 13:21:43 +08:00
<!-- components/uni-table/uni-table.vue -->
<template>
<view class="uni-table-container">
<!-- 横向表格模式 -->
<scroll-view
v-if="mode === 'horizontal'"
scroll-x
class="horizontal-table"
:style="{ maxHeight: height }"
>
<view class="table-header">
<view
v-for="(col, index) in columns"
:key="index"
class="header-cell"
:style="getHeaderStyle(col)"
>
{{ col.title }}
</view>
</view>
<view
v-for="(row, rowIndex) in data"
:key="rowIndex"
class="table-row"
:class="{ 'row-hover': rowHover }"
@click="handleRowClick(row, rowIndex)"
>
<view
v-for="(col, colIndex) in columns"
:key="colIndex"
class="body-cell"
:style="getCellStyle(col)"
>
<slot
v-if="col.slot"
:name="col.slot"
:row="row"
:column="col"
></slot>
<text v-else>{{ formatCellValue(row[col.key], col) }}</text>
</view>
</view>
<view v-if="data.length === 0" class="empty-tip">
<u-empty mode="data" icon="/static/empty.png"/>
</view>
</scroll-view>
<!-- 纵向表格模式 -->
<scroll-view
v-else
scroll-y
class="vertical-table"
:style="{ maxHeight: height }"
>
<view
v-for="(row, rowIndex) in data"
:key="rowIndex"
class="vertical-row"
:class="{ 'row-hover': rowHover }"
@click="handleRowClick(row, rowIndex)"
>
<view
v-for="(col, colIndex) in columns"
:key="colIndex"
class="vertical-cell"
>
<view class="cell-label">{{ col.title }}</view>
<view class="cell-value">
<slot
v-if="col.slot"
:name="col.slot"
:row="row"
:column="col"
></slot>
<text v-else>{{ formatCellValue(row[col.key], col) }}</text>
</view>
</view>
</view>
<view v-if="data.length === 0" class="empty-tip">
<u-empty mode="data" icon="/static/empty.png"/>
</view>
</scroll-view>
</view>
</template>
<script>
export default {
name: 'UniTable',
props: {
// 表格模式horizontal横向/vertical纵向
mode: {
type: String,
default: 'horizontal',
validator: value => ['horizontal', 'vertical'].includes(value)
},
// 列配置
columns: {
type: Array,
required: true,
},
// 表格数据
data: {
type: Array,
default: () => []
},
// 固定高度支持px/rpx
height: {
type: String,
default: '1000rpx'
},
// 是否显示边框
border: {
type: Boolean,
default: true
},
// 是否显示行hover效果
rowHover: {
type: Boolean,
default: true
},
// 是否显示斑马纹
stripe: {
type: Boolean,
default: false
},
// 是否显示序号列
showIndex: {
type: Boolean,
default: false
},
// 序号列标题
indexLabel: {
type: String,
default: '序号'
}
},
computed: {
// 处理后的列配置
processedColumns() {
let cols = [...this.columns]
if (this.showIndex) {
cols.unshift({
title: this.indexLabel,
key: '_index',
width: '80rpx',
align: 'center'
})
}
return cols
}
},
methods: {
// 获取表头样式
getHeaderStyle(col) {
return {
width: col.width || '200rpx',
textAlign: col.align || 'left',
flex: col.flex ? `0 0 ${col.width}` : 'none',
backgroundColor: this.border ? '#f5f7fa' : 'transparent'
}
},
// 获取单元格样式
getCellStyle(col) {
return {
width: col.width || '200rpx',
textAlign: col.align || 'left',
flex: col.flex ? `0 0 ${col.width}` : 'none'
}
},
// 行点击事件
handleRowClick(row, index) {
this.$emit('row-click', row, index)
},
// 格式化单元格值
formatCellValue(value, col) {
if (value === undefined || value === null) {
return '--'
}
// 如果列定义了format函数使用format函数处理
if (col.format && typeof col.format === 'function') {
return col.format(value)
}
// 如果是日期类型
if (col.type === 'date') {
return this.formatDate(value, col.dateFormat)
}
// 如果是数字类型
if (col.type === 'number') {
return this.formatNumber(value, col.precision)
}
return value
},
// 格式化日期
formatDate(value, format = 'YYYY-MM-DD') {
if (!value) return '--'
// 这里可以使用日期格式化库如dayjs
return value
},
// 格式化数字
formatNumber(value, precision) {
if (typeof value !== 'number') return value
return precision !== undefined ? value.toFixed(precision) : value
}
}
}
</script>
<style lang="scss" scoped>
.uni-table-container {
background-color: #fff;
border-radius: 12rpx;
overflow: hidden;
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.1);
.horizontal-table {
.table-header {
display: flex;
background-color: #f5f7fa;
border-bottom: 2rpx solid #ebeef5;
position: sticky;
top: 0;
z-index: 1;
.header-cell {
padding: 24rpx;
font-weight: 500;
color: #303133;
white-space: nowrap;
transition: background-color 0.3s;
position: relative;
&::after {
content: '';
position: absolute;
right: 0;
top: 50%;
transform: translateY(-50%);
height: 50%;
width: 2rpx;
background-color: #ebeef5;
}
}
}
.table-row {
display: flex;
border-bottom: 1rpx solid #ebeef5;
transition: background-color 0.3s;
&:last-child {
border-bottom: none;
}
&.row-hover:active {
background-color: #f5f7fa;
}
.body-cell {
padding: 24rpx;
color: #606266;
word-break: break-all;
display: flex;
align-items: center;
}
}
}
.vertical-table {
.vertical-row {
padding: 24rpx;
border-bottom: 1rpx solid #ebeef5;
transition: background-color 0.3s;
&:last-child {
border-bottom: none;
}
&.row-hover:active {
background-color: #f5f7fa;
}
.vertical-cell {
display: flex;
margin-bottom: 20rpx;
align-items: flex-start;
&:last-child {
margin-bottom: 0;
}
.cell-label {
width: 200rpx;
color: #909399;
flex-shrink: 0;
font-size: 28rpx;
}
.cell-value {
flex: 1;
color: #303133;
word-break: break-all;
font-size: 28rpx;
padding-left: 20rpx;
}
}
}
}
.empty-tip {
padding: 80rpx 0;
display: flex;
justify-content: center;
align-items: center;
background-color: #fff;
}
}
</style>