Files
pet/uni_modules/uview-next/components/u-table-column/u-table-column.vue
T
2025-08-12 15:04:01 +08:00

386 lines
12 KiB
Vue

<template>
<view v-if="show" class="u-table__column" :style="[columnStyle]">
<block v-for="(row, index) in tableData" :key="index">
<view class="u-table__cell" :style="[cellStyles(row, index)]" :class="[cellClasses(row, index)]"
@click="handleCellClick(row, index)" v-if="!isCellHidden(row, index)">
<view class="u-table__value" :class="{ 'is-ellipsis': isEllipsis }">
<slot :row="row" :column="columnConfig" :index="index" :value="row">
<template v-if="type === 'index'">
{{ index + 1 }}
</template>
<template v-else-if="type === 'selection'">
<u-checkbox :checked="isSelectionSelected(row)"
@change="(checked) => handleSelectionChange(row, checked)"></u-checkbox>
</template>
<template v-else>
{{ getCellValue(row) }}
</template>
</slot>
</view>
</view>
</block>
</view>
</template>
<script>
import props from './props.js';
import mixin from '../../libs/mixin/mixin'
import mpMixin from '../../libs/mixin/mpMixin';
/**
* table-col 表格列
* @description 表格列组件,需要与u-table组件配合使用
* @tutorial https://www.uviewui.com/components/table.html
* @property {String} prop 字段名称,对应列内容的字段名 (默认 '' )
* @property {String} label 显示的标题 (默认 '' )
* @property {String|Number} width 列宽度 (默认 '' )
* @property {String|Number} minWidth 列最小宽度 (默认 '' )
* @property {Boolean} sortable 是否可排序 (默认 false )
* @property {Boolean} fixed 是否固定列 (默认 false )
* @property {String} align 对齐方式 (默认 'left' )
* @property {String} type 列类型:selection/index (默认 '' )
* @property {Function} formatter 格式化函数 (默认 null )
* @property {Boolean} show 是否显示 (默认 true )
* @property {String} headerAlign 表头对齐方式 (默认 'left' )
* @property {Boolean} headerEllipsis 表头超出1行隐藏 (默认 false )
* @property {String} columnKey 列的key (默认 '' )
* @property {String} className 列的类名 (默认 '' )
* @property {Object} customStyle 定义需要用到的外部样式
* @example <u-table-column prop="name" label="姓名" width="100"></u-table-column>
*/
export default {
name: 'u-table-column',
mixins: [mpMixin, mixin, props],
data() {
return {
parentData: {
data: [],
border: false,
stripe: false,
rowHeight: null,
ellipsis: true,
cellStyle: {},
selectedRowKeys: [],
toggleRowSelection: null,
mergeInfo: {}
},
sortDirection: 0,
innerFormatter: (value) => value
};
},
computed: {
columnConfig() {
return {
prop: this.prop,
type: this.type,
label: this.label,
width: this.width,
minWidth: this.minWidth,
sortable: this.sortable,
align: this.align,
type: this.type,
formatter: this.formatter,
sortDirection: this.sortDirection
};
},
tableData() {
return this.parentData.data || [];
},
isEllipsis() {
return this.parentData.ellipsis;
},
columnStyle() {
const style = {};
if (this.width) {
style.width = this.$u.addUnit(this.width);
} else if (this.minWidth) {
style.minWidth = this.$u.addUnit(this.minWidth);
} else {
style.flex = 1;
}
return style;
},
// 响应式的行选中状态映射
isSelectionSelected() {
return (row) => {
if (this.parentData.selectedRowKeys && this.parentData.selectedRowKeys.length > 0) {
const rowKey = this.getRowKey(row);
return this.parentData.selectedRowKeys.includes(rowKey);
}
return false;
}
},
cellStyles(row, index) {
return (row, index) => {
const style = {};
if (this.parentData.rowHeight) {
style.height = this.$u.addUnit(this.parentData.rowHeight);
}
// 处理合并单元格样式
if (this.parentData.mergeInfo && this.prop) {
const mergeKey = `${index}-${this.prop}`;
const mergeData = this.parentData.mergeInfo[mergeKey];
if (mergeData) {
// 设置合并单元格的高度
if (mergeData.rowspan > 1) {
const rowHeight = parseInt(this.parentData.rowHeight);
style.height = this.$u.addUnit(rowHeight * mergeData.rowspan + 1);
}
// 设置显示状态
if (mergeData.display === 'none') {
style.display = 'none';
}
}
}
return uni.$u.deepMerge(style, uni.$u.addStyle(this.parentData.cellStyle));
}
},
cellClasses(row, index) {
return (row, index) => {
let classes = [
this.className,
`is-${this.align || 'left'}`,
];
if (this.parentData.border) {
classes.push('is-border');
}
if (this.parentData.fixed) {
classes.push('is-fixed');
}
if (this.parentData.stripe && index % 2 === 1) {
classes.push('is-stripe');
}
// 添加合并单元格的样式类
if (this.parentData.mergeInfo && this.prop) {
const mergeKey = `${index}-${this.prop}`;
const mergeData = this.parentData.mergeInfo[mergeKey];
if (mergeData && mergeData.rowspan > 1) {
classes.push('is-merged');
}
}
// #ifdef MP-ALIPAY || MP-TOUTIAO
classes = classes.join(' ')
// #endif
return classes;
}
},
},
mounted() {
this.init()
},
// #ifdef VUE3
emits: ['cellClick'],
// #endif
methods: {
init() {
this.updateParentData();
if (!this.parent) {
uni.$u.error('u-table-column必须搭配u-table组件使用');
return;
}
},
updateParentData() {
this.getParentData('u-table');
},
// 获取行的唯一标识(与父组件保持一致)
getRowKey(row) {
if (!row) return '';
// 这里需要与父组件的rowKey属性保持一致
const rowKeyProp = this.parent?.rowKey || 'id';
return this.getValueByPath(row, rowKeyProp) || JSON.stringify(row);
},
// 获取嵌套属性值
getValueByPath(obj, path) {
if (!path || !obj) return '';
const keys = path.split('.');
let value = obj;
for (const key of keys) {
if (value && typeof value === 'object') {
value = value[key];
} else {
value = '';
break;
}
}
return value;
},
// 处理选择状态变化
handleSelectionChange(row, checked) {
if (typeof this.parentData.toggleRowSelection === 'function') {
this.parentData.toggleRowSelection(row, checked);
}
},
// 处理展开点击
handleExpandClick(row) {
if (typeof this.parentData.toggleRowExpansion === 'function') {
this.parentData.toggleRowExpansion(row);
}
},
handleCellClick(row, rowIndex) {
// 如果是selection或expand类型,不触发cell-click事件
if (this.type === 'selection' || this.type === 'expand') {
return;
}
const cellData = {
row,
column: this.columnConfig,
rowIndex,
columnIndex: this.getColumnIndex(),
value: this.getCellValue(row)
};
this.$emit('cellClick', cellData);
if (this.parent) {
this.parent.$emit('cellClick', cellData);
}
},
getCellValue(row) {
if (!this.prop) return '';
const keys = this.prop.split('.');
let value = row;
for (const key of keys) {
if (value && typeof value === 'object') {
value = value[key];
} else {
value = '';
break;
}
}
const formatter = this.formatter || this.innerFormatter;
return formatter(value);
},
getColumnIndex() {
if (!this.parent || !this.parent.children) return 0;
return this.parent.children.findIndex(col => col.prop === this.prop);
},
isCellHidden(row, index) {
// 检查合并信息,如果该单元格被合并到其他单元格中,则隐藏
if (this.parentData.mergeInfo && this.prop) {
const mergeKey = `${index}-${this.prop}`;
const mergeData = this.parentData.mergeInfo[mergeKey];
if (mergeData && mergeData.display === 'none') {
return true;
}
}
return false;
}
}
};
</script>
<style lang="scss" scoped>
@import '../../libs/css/components.scss';
.u-table {
&__column {
@include flex(column);
flex-shrink: 0;
&:last-child .is-border {
border-right: none;
}
}
&__cell {
@include flex(column);
padding: 0px 10px;
align-items: center;
justify-content: center;
position: relative;
&.is-left {
align-items: flex-start;
}
&.is-center {
align-items: center;
}
&.is-right {
align-items: flex-end;
}
&.is-border {
border-right: 1px solid $u-border-color;
border-bottom: 1px solid $u-border-color;
&:last-child {
border-bottom: none;
}
}
&.is-stripe {
background-color: $u-bg-color;
}
&.is-merged {
position: relative;
z-index: 1;
background-color: inherit;
}
}
&__value {
font-size: 13px;
line-height: 15px;
&.is-ellipsis {
word-break: break-all;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
overflow: hidden;
}
}
&__expand-icon {
@include flex(row);
align-items: center;
justify-content: center;
width: 20px;
height: 20px;
cursor: pointer;
transition: transform 0.2s ease;
&.is-expanded {
transform: rotate(90deg);
}
&:hover {
background-color: rgba(0, 0, 0, 0.05);
border-radius: 2px;
}
}
}
</style>