YingXingAI/node_modules/uview-plus/components/u-table2/u-table2.vue

518 lines
16 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<view scroll-x class="u-table2" :style="{ height: height ? height + 'px' : 'auto' }">
<!-- 表头 -->
<view v-if="showHeader" class="u-table-header" :class="{ 'u-table-sticky': fixedHeader }" :style="{minWidth: scrollWidth}">
<view class="u-table-row">
<view v-for="(col, colIndex) in columns" :key="col.key" class="u-table-cell"
:style="headerColStyle(col)"
:class="[
col.align ? 'u-text-' + col.align : '',
headerCellClassName ? headerCellClassName(col) : '',
col.fixed === 'left' ? 'u-table-fixed-left' : '',
col.fixed === 'right' ? 'u-table-fixed-right' : ''
]" @click="handleHeaderClick(col)">
<slot name="header" :column="col" :columnIndex="colIndex" :level="1">
{{ col.title }}
</slot>
<view v-if="col.sortable">
{{ getSortIcon(col.key) }}
</view>
</view>
</view>
</view>
<!-- 表体 -->
<view class="u-table-body" :style="{ minWidth: scrollWidth, maxHeight: maxHeight ? maxHeight + 'px' : 'none' }">
<template v-if="data && data.length > 0">
<template v-for="(row, index) in sortedData" :key="row[rowKey] || index">
<view class="u-table-row" :class="[
highlightCurrentRow && currentRow === row ? 'u-table-row-highlight' : '',
rowClassName ? rowClassName(row, index) : '',
stripe && index % 2 === 1 ? 'u-table-row-zebra' : ''
]" @click="handleRowClick(row)">
<view v-for="(col, colIndex) in columns" :key="col.key"
class="u-table-cell" :class="[
col.align ? 'u-text-' + col.align : '',
cellClassName ? cellClassName(row, col) : '',
col.fixed === 'left' ? 'u-table-fixed-left' : '',
col.fixed === 'right' ? 'u-table-fixed-right' : ''
]" :style="cellStyleInner({row: row, column: col,
rowIndex: index, columnIndex: colIndex, level: 0})">
<!-- 复选框列 -->
<view v-if="col.type === 'selection'">
<checkbox :checked="isSelected(row)"
@click.stop="toggleSelect(row)" />
</view>
<!-- 树形结构展开图标 -->
<view v-else-if="col.type === 'expand'"
@click.stop="toggleExpand(row)">
{{ isExpanded(row) ? '▼' : '▶' }}
</view>
<!-- 默认插槽或文本 -->
<slot name="cell" :row="row" :column="col"
:rowIndex="index" :columnIndex="colIndex">
<view class="u-table-cell_content">
{{ row[col.key] }}
</view>
</slot>
</view>
</view>
<!-- 子级渲染 -->
<template v-if="isExpanded(row) && row[treeProps.children] && row[treeProps.children].length">
<view v-for="childRow in row[treeProps.children]" :key="childRow[rowKey]"
class="u-table-row u-table-row-child">
<view v-for="(col2, col2Index) in columns" :key="col2.key" class="u-table-cell"
:style="cellStyleInner({row: childRow, column: col2,
rowIndex: index, columnIndex: col2Index, level: 1})">
<slot name="cell" :row="childRow" :column="col2" :prow="row"
:rowIndex="index" :columnIndex="col2Index" :level="1">
<view class="u-table-cell_content">
{{ childRow[col2.key] }}
</view>
</slot>
</view>
</view>
</template>
</template>
</template>
<template v-else>
<slot name="empty">
<view class="u-table-empty">{{ emptyText }}</view>
</slot>
</template>
</view>
</view>
</template>
<script>
import { ref, watch, computed } from 'vue'
import { addUnit, sleep } from '../../libs/function/index';
export default {
name: 'u-table2',
props: {
data: {
type: Array,
required: true,
default: () => {
return []
}
},
columns: {
type: Array,
required: true,
default: () => {
return []
},
validator: cols =>
cols.every(col =>
['default', 'selection', 'expand'].includes(col.type || 'default')
)
},
stripe: {
type: Boolean,
default: false
},
border: {
type: Boolean,
default: false
},
height: {
type: [String, Number],
default: null
},
maxHeight: {
type: [String, Number],
default: null
},
showHeader: {
type: Boolean,
default: true
},
highlightCurrentRow: {
type: Boolean,
default: false
},
rowKey: {
type: String,
default: 'id'
},
currentRowKey: {
type: [String, Number],
default: null
},
rowStyle: {
type: Object,
default: () => ({})
},
cellClassName: {
type: Function,
default: null
},
cellStyle: {
type: Function,
default: null
},
headerCellClassName: {
type: Function,
default: null
},
rowClassName: {
type: Function,
default: null
},
context: {
type: Object,
default: null
},
showOverflowTooltip: {
type: Boolean,
default: false
},
lazy: {
type: Boolean,
default: false
},
load: {
type: Function,
default: null
},
treeProps: {
type: Object,
default: () => ({
children: 'children',
hasChildren: 'hasChildren'
})
},
defaultExpandAll: {
type: Boolean,
default: false
},
expandRowKeys: {
type: Array,
default: () => []
},
sortOrders: {
type: Array,
default: () => ['ascending', 'descending']
},
sortable: {
type: [Boolean, String],
default: false
},
multiSort: {
type: Boolean,
default: false
},
sortBy: {
type: String,
default: null
},
sortMethod: {
type: Function,
default: null
},
filters: {
type: Object,
default: () => ({})
},
fixedHeader: {
type: Boolean,
default: true
},
emptyText: {
type: String,
default: '暂无数据'
},
},
emits: [
'select', 'select-all', 'selection-change',
'cell-click', 'row-click', 'row-dblclick',
'header-click', 'sort-change', 'filter-change',
'current-change', 'expand-change'
],
data() {
return {
scrollWidth: 'auto'
}
},
mounted() {
this.getComponentWidth()
},
computed: {
},
methods: {
addUnit,
headerColStyle(col) {
let style = {
width: col.width ? addUnit(col.width) : 'auto',
flex: col.width ? 'none' : 1
};
if (col?.style) {
style = {...style, ...col?.style};
}
return style;
},
setCellStyle(e) {
this.cellStyle = e
},
cellStyleInner(scope) {
let style = {
width: scope.column?.width ? addUnit(scope.column.width) : 'auto',
flex: scope.column?.width ? 'none' : 1,
paddingLeft: (24 * scope.level) + 'px'
};
if (this.cellStyle != null) {
let styleCalc = this.cellStyle(scope)
if (styleCalc != null) {
style = {...style, ...styleCalc}
}
}
return style;
},
// 获取组件的宽度
async getComponentWidth() {
// 延时一定时间以获取dom尺寸
await sleep(30)
this.$uGetRect('.u-table-row').then(size => {
this.scrollWidth = size.width + 'px'
})
},
},
setup(props, { emit }) {
const expandedKeys = ref([...props.expandRowKeys]);
const selectedRows = ref([]);
const sortConditions = ref([]);
// 当前高亮行
const currentRow = ref(null);
watch(
() => props.expandRowKeys,
newVal => {
expandedKeys.value = [...newVal];
}
);
watch(
() => props.currentRowKey,
newVal => {
const found = props.data.find(item => item[props.rowKey] === newVal);
if (found) {
currentRow.value = found;
}
}
);
// 过滤后的数据
const filteredData = computed(() => {
return props.data.filter(row => {
return Object.keys(props.filters).every(key => {
const filter = props.filters[key];
if (!filter) return true;
return row[key]?.toString().includes(filter.toString());
});
});
});
// 排序后的数据
const sortedData = computed(() => {
if (!sortConditions.value.length) return filteredData.value;
const data = [...filteredData.value];
return data.sort((a, b) => {
for (const condition of sortConditions.value) {
const { field, order } = condition;
let valA = a[field];
let valB = b[field];
if (props.sortMethod) {
const result = props.sortMethod(a, b, field);
if (result !== 0) return result * (order === 'ascending' ? 1 : -1);
}
if (valA < valB) return order === 'ascending' ? -1 : 1;
if (valA > valB) return order === 'ascending' ? 1 : -1;
}
return 0;
});
});
function handleRowClick(row) {
if (props.highlightCurrentRow) {
const oldRow = currentRow.value;
currentRow.value = row;
emit('current-change', row, oldRow);
}
emit('row-click', row);
}
function handleHeaderClick(column) {
if (!column.sortable) return;
const index = sortConditions.value.findIndex(c => c.field === column.key);
let newOrder = 'ascending';
if (index >= 0) {
if (sortConditions.value[index].order === 'ascending') {
newOrder = 'descending';
} else {
sortConditions.value.splice(index, 1);
emit('sort-change', sortConditions.value);
return;
}
}
if (!props.multiSort) {
sortConditions.value = [{ field: column.key, order: newOrder }];
} else {
if (index >= 0) {
sortConditions.value[index].order = newOrder;
} else {
sortConditions.value.push({ field: column.key, order: newOrder });
}
}
emit('sort-change', sortConditions.value);
}
function getSortIcon(field) {
const cond = sortConditions.value.find(c => c.field === field);
if (!cond) return '';
return cond.order === 'ascending' ? '↑' : '↓';
}
function toggleSelect(row) {
const index = selectedRows.value.findIndex(r => r[props.rowKey] === row[props.rowKey]);
if (index >= 0) {
selectedRows.value.splice(index, 1);
} else {
selectedRows.value.push(row);
}
emit('selection-change', selectedRows.value);
emit('select', row);
}
function isSelected(row) {
return selectedRows.value.some(r => r[props.rowKey] === row[props.rowKey]);
}
function toggleExpand(row) {
const key = row[props.rowKey];
const index = expandedKeys.value.indexOf(key);
if (index === -1) {
expandedKeys.value.push(key);
} else {
expandedKeys.value.splice(index, 1);
}
emit('expand-change', expandedKeys.value);
}
function isExpanded(row) {
return expandedKeys.value.includes(row[props.rowKey]);
}
return {
currentRow,
sortedData,
expandedKeys,
selectedRows,
sortConditions,
handleRowClick,
handleHeaderClick,
getSortIcon,
toggleSelect,
isSelected,
toggleExpand,
isExpanded
};
}
};
</script>
<style lang="scss" scoped>
.u-table2 {
width: auto;
overflow: auto;
white-space: nowrap;
.u-table-header {
min-width: 100% !important;
width: fit-content;
background-color: #f5f7fa;
}
.u-table-body {
min-width: 100% !important;
width: fit-content;
}
.u-table-sticky {
position: sticky;
top: 0;
z-index: 10;
}
.u-table-row {
display: flex;
flex-direction: row;
align-items: center;
border-bottom: 1rpx solid #ebeef5;
overflow: hidden;
}
.u-table-cell {
flex: 1;
display: flex;
flex-direction: row;
padding: 5px 4px;
font-size: 14px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.u-table-fixed-left {
position: sticky;
left: 0;
z-index: 9;
}
.u-table-fixed-right {
position: sticky;
right: 0;
z-index: 9;
}
.u-table-row-zebra {
background-color: #fafafa;
}
.u-table-row-highlight {
background-color: #f5f7fa;
}
.u-table-empty {
text-align: center;
padding: 20px;
color: #999;
}
.u-text-left {
text-align: left;
}
.u-text-center {
text-align: center;
}
.u-text-right {
text-align: right;
}
}
</style>