844 lines
29 KiB
Vue
844 lines
29 KiB
Vue
<template>
|
||
<u-scroll-list :indicator="false" :scroll="scrollX">
|
||
<view class="u-table" :class="[customClass, border ? 'is-border' : '']" :style="tableStyle">
|
||
<!-- 表头 -->
|
||
<view v-if="showHeader" class="u-table__header" ref="u-table__header">
|
||
<block v-for="(column, index) in childrenData" :key="index">
|
||
<view v-if="column.show" class="u-table__header__column" :style="[cellStyles(column)]"
|
||
@click="handleHeaderClick(column, index)">
|
||
<view class="u-table__cell" :class="[cellClasses(column, index)]"
|
||
:style="{ minHeight: $u.addUnit(rowHeight) }">
|
||
<!-- 多选表头复选框 -->
|
||
<template v-if="column.type === 'selection'">
|
||
<u-checkbox :checked="isAllSelected" :indeterminate="isIndeterminate"
|
||
@change="handleSelectAll"></u-checkbox>
|
||
</template>
|
||
<!-- 普通表头 -->
|
||
<template v-else>
|
||
<text class="u-table__value" :class="[{'is-ellipsis': column.headerEllipsis }]">{{ column.label }}</text>
|
||
<view v-if="column.sortable && column.type != 'index'" class="u-table__sort-icon">
|
||
<view class="u-table__sort-arrow u-table__sort-arrow--up"
|
||
:class="{ 'is-active': sortColumn === column.prop && sortOrder === 'asc' }">
|
||
</view>
|
||
<view class="u-table__sort-arrow u-table__sort-arrow--down"
|
||
:class="{ 'is-active': sortColumn === column.prop && sortOrder === 'desc' }">
|
||
</view>
|
||
</view>
|
||
</template>
|
||
</view>
|
||
</view>
|
||
</block>
|
||
</view>
|
||
|
||
<!-- 表体 -->
|
||
<scroll-view class="u-table__body" :style="bodyStyle" :scroll-y="true">
|
||
<!-- 数据行容器 -->
|
||
<view class="u-table__rows">
|
||
<slot></slot>
|
||
</view>
|
||
|
||
<!-- 空数据提示 -->
|
||
<view v-if="!data || data.length === 0" class="u-table__empty"
|
||
:style="{ height: $u.addUnit(emptyHeight) }">
|
||
<slot name="empty">
|
||
<view class="u-table__empty-content">
|
||
<text class="u-table__empty-text">{{ emptyText }}</text>
|
||
</view>
|
||
</slot>
|
||
</view>
|
||
</scroll-view>
|
||
|
||
<!-- 表尾合计行 -->
|
||
<view v-if="showSummary && data && data.length > 0" class="u-table__footer" ref="u-table__footer">
|
||
<block v-for="(column, index) in children" :key="index">
|
||
<view v-if="column.show" class="u-table__footer__column" :style="[cellStyles(column)]">
|
||
<view class="u-table__cell u-table__summary-cell" :class="[cellClasses(column, index)]"
|
||
:style="{ height: $u.addUnit(rowHeight) }">
|
||
<text class="u-table__value">{{ getSummaryValue(column, index) }}</text>
|
||
</view>
|
||
</view>
|
||
</block>
|
||
</view>
|
||
</view>
|
||
</u-scroll-list>
|
||
</template>
|
||
|
||
<script>
|
||
// #ifdef APP-NVUE
|
||
const dom = uni.requireNativePlugin('dom')
|
||
// #endif
|
||
import props from './props.js';
|
||
import mixin from '../../libs/mixin/mixin'
|
||
import mpMixin from '../../libs/mixin/mpMixin'
|
||
|
||
/**
|
||
* table 表格
|
||
* @description 用于展示多条结构类似的数据,可对数据进行排序等操作
|
||
* @tutorial https://www.uviewui.com/components/table.html
|
||
* @property {Array} data 显示的数据 (默认 [] )
|
||
* @property {Boolean} border 是否带有边框 (默认 true )
|
||
* @property {Boolean} scrollX 是否允许横向滚动 (默认 true )
|
||
* @property {String|Number} round 设置圆角值 (默认 0 )
|
||
* @property {Boolean} stripe 是否为斑马纹表 (默认 true )
|
||
* @property {String|Number} height Table 的高度 (默认 null )
|
||
* @property {String|Number} rowHeight 行高 (默认 50 )
|
||
* @property {Boolean} showHeader 是否显示表头 (默认 true )
|
||
* @property {Object} headerCellStyle 表头单元格样式 (默认 {} )
|
||
* @property {Boolean} ellipsis 是否超出2行隐藏 (默认 true )
|
||
* @property {String} emptyText 空数据时显示的文本 (默认 '暂无数据' )
|
||
* @property {String|Number} emptyHeight 空数据区域高度 (默认 200 )
|
||
* @property {String} rowKey 行数据的Key,用于优化Table的渲染 (默认 'id' )
|
||
* @property {Array} defaultSelection 默认选中的行 (默认 [] )
|
||
* @property {Boolean} showSummary 是否显示表尾合计行 (默认 false )
|
||
* @property {String} sumText 表尾合计行第一列的文本 (默认 '合计' )
|
||
* @property {Function} summaryMethod 自定义的合计计算方法 (默认 null )
|
||
* @property {Array} summaryColumns 需要合计的列(prop数组) (默认 [] )
|
||
* @property {Object} customStyle 定义需要用到的外部样式
|
||
* @event {Function} sort-change 排序变化事件
|
||
* @event {Function} row-click 行点击事件
|
||
* @event {Function} cell-click 单元格点击事件
|
||
* @event {Function} selection-change 选择项发生变化时会触发该事件
|
||
* @event {Function} select 用户手动勾选数据行的 Checkbox 时触发的事件
|
||
* @event {Function} select-all 用户手动勾选全选 Checkbox 时触发的事件
|
||
* @example <u-table :data="dataList" :columns="columns"></u-table>
|
||
*/
|
||
export default {
|
||
name: 'u-table',
|
||
mixins: [mpMixin, mixin, props],
|
||
data() {
|
||
return {
|
||
children: [],
|
||
childrenData: [],
|
||
sortColumn: null,
|
||
sortOrder: null,
|
||
headerList: [],
|
||
headerHeight: 0,
|
||
footerHeight: 0,
|
||
// 多选相关状态
|
||
selectedRows: [], // 当前选中的行数据
|
||
selectedRowKeys: [], // 当前选中的行Key
|
||
expandedRowKeys: [] // 当前展开的行Key
|
||
}
|
||
},
|
||
computed: {
|
||
parentData() {
|
||
return [
|
||
this.data,
|
||
this.border,
|
||
this.stripe,
|
||
this.rowHeight,
|
||
this.ellipsis,
|
||
this.cellStyle,
|
||
this.selectedRowKeys,
|
||
this.toggleRowSelection,
|
||
this.mergeInfo
|
||
]
|
||
},
|
||
// 排序后的数据
|
||
sortedData() {
|
||
if (!this.sortColumn || !this.sortOrder) {
|
||
return this.data || []
|
||
}
|
||
|
||
const dataToSort = [...(this.data || [])]
|
||
return dataToSort.sort((a, b) => {
|
||
const aVal = this.getValueByPath(a, this.sortColumn)
|
||
const bVal = this.getValueByPath(b, this.sortColumn)
|
||
|
||
// 处理不同数据类型的比较
|
||
let result = 0
|
||
|
||
// 数字比较
|
||
if (typeof aVal === 'number' && typeof bVal === 'number') {
|
||
result = aVal - bVal
|
||
}
|
||
// 字符串比较
|
||
else if (typeof aVal === 'string' && typeof bVal === 'string') {
|
||
result = aVal.localeCompare(bVal)
|
||
}
|
||
// 日期比较
|
||
else if (aVal instanceof Date && bVal instanceof Date) {
|
||
result = aVal.getTime() - bVal.getTime()
|
||
}
|
||
// 其他类型转换为字符串比较
|
||
else {
|
||
const aStr = String(aVal || '')
|
||
const bStr = String(bVal || '')
|
||
result = aStr.localeCompare(bStr)
|
||
}
|
||
|
||
return this.sortOrder === 'asc' ? result : -result
|
||
})
|
||
},
|
||
|
||
// 合并单元格信息
|
||
mergeInfo() {
|
||
if (!this.spanMethod && (!this.mergeConfig || this.mergeConfig.length === 0)) {
|
||
return {}
|
||
}
|
||
|
||
const info = {}
|
||
const data = this.sortedData
|
||
|
||
// 使用自定义合并方法
|
||
if (this.spanMethod && uni.$u.test.func(this.spanMethod)) {
|
||
data.forEach((row, rowIndex) => {
|
||
this.childrenData.forEach((column, columnIndex) => {
|
||
if (!column.prop) return
|
||
|
||
const spanResult = this.spanMethod({
|
||
row,
|
||
column: column,
|
||
rowIndex,
|
||
columnIndex
|
||
})
|
||
|
||
if (spanResult && (spanResult.rowspan !== 1 || spanResult.colspan !== 1)) {
|
||
const key = `${rowIndex}-${column.prop}`
|
||
info[key] = {
|
||
rowspan: spanResult.rowspan || 1,
|
||
colspan: spanResult.colspan || 1,
|
||
display: spanResult.rowspan === 0 || spanResult.colspan === 0 ? 'none' : 'block'
|
||
}
|
||
}
|
||
})
|
||
})
|
||
}
|
||
// 使用简化配置
|
||
else if (this.mergeConfig && this.mergeConfig.length > 0) {
|
||
this.mergeConfig.forEach(config => {
|
||
if (!config.prop || !config.ranges) return
|
||
|
||
config.ranges.forEach(range => {
|
||
if (range.startRow !== undefined && range.endRow !== undefined) {
|
||
const rowspan = range.endRow - range.startRow + 1
|
||
// 第一行显示合并的单元格
|
||
const firstRowKey = `${range.startRow}-${config.prop}`
|
||
info[firstRowKey] = {
|
||
rowspan: rowspan,
|
||
colspan: range.colspan || 1,
|
||
display: 'block'
|
||
}
|
||
|
||
// 其他行隐藏
|
||
for (let i = range.startRow + 1; i <= range.endRow; i++) {
|
||
const key = `${i}-${config.prop}`
|
||
info[key] = {
|
||
rowspan: 0,
|
||
colspan: 0,
|
||
display: 'none'
|
||
}
|
||
}
|
||
}
|
||
})
|
||
})
|
||
}
|
||
|
||
return info
|
||
},
|
||
// 是否全选
|
||
isAllSelected() {
|
||
return this.data.length > 0 && this.selectedRowKeys.length === this.data.length
|
||
},
|
||
// 是否半选状态
|
||
isIndeterminate() {
|
||
return this.selectedRowKeys.length > 0 && this.selectedRowKeys.length < this.data.length
|
||
},
|
||
tableStyle() {
|
||
const style = {}
|
||
if (this.height) {
|
||
style.height = this.$u.addUnit(this.height)
|
||
style.display = 'flex'
|
||
style.flexDirection = 'column'
|
||
}
|
||
|
||
if (this.round) {
|
||
style.borderRadius = this.$u.addUnit(this.round)
|
||
}
|
||
|
||
return uni.$u.deepMerge(style, uni.$u.addStyle(this.customStyle));
|
||
},
|
||
bodyStyle() {
|
||
const style = {}
|
||
if (this.height) {
|
||
if (this.showHeader || this.showSummary) {
|
||
// 表体高度 = 总高度 - 表头高度 - 表尾高度
|
||
const totalHeight = parseInt(this.height) || 0
|
||
let usedHeight = 0
|
||
if (this.showHeader) usedHeight += this.headerHeight
|
||
if (this.showSummary && this.data && this.data.length > 0) usedHeight += this.footerHeight
|
||
|
||
if (totalHeight > usedHeight) {
|
||
style.height = this.$u.addUnit(totalHeight - usedHeight)
|
||
}
|
||
} else {
|
||
style.height = this.$u.addUnit(this.height)
|
||
}
|
||
style.flex = 1
|
||
}
|
||
return this.$u.addStyle(style)
|
||
},
|
||
cellClasses(column, index) {
|
||
return (column, index) => {
|
||
let classes = [
|
||
`is-${column.headerAlign || 'left'}`,
|
||
];
|
||
|
||
if (this.border) {
|
||
classes.push('is-border');
|
||
}
|
||
|
||
// #ifdef MP-ALIPAY || MP-TOUTIAO
|
||
classes = classes.join(' ')
|
||
// #endif
|
||
|
||
return classes;
|
||
}
|
||
},
|
||
cellStyles(column) {
|
||
return (column) => {
|
||
const style = {}
|
||
if (column.width) {
|
||
style.width = this.$u.addUnit(column.width)
|
||
} else if (column.minWidth) {
|
||
style.minWidth = this.$u.addUnit(column.minWidth)
|
||
} else {
|
||
style.flex = 1;
|
||
}
|
||
|
||
return uni.$u.deepMerge(style, uni.$u.addStyle(this.headerCellStyle));
|
||
}
|
||
}
|
||
},
|
||
created() {
|
||
//this.children = []
|
||
// 初始化默认选中项
|
||
this.initDefaultSelection()
|
||
},
|
||
mounted() {
|
||
this.getHeaderHeight()
|
||
this.getFooterHeight()
|
||
},
|
||
watch: {
|
||
// 监听排序状态变化,更新子组件数据
|
||
sortedData: {
|
||
handler() {
|
||
this.updateChildrenData()
|
||
},
|
||
immediate: true
|
||
},
|
||
// 监听原始数据变化
|
||
data: {
|
||
handler() {
|
||
this.updateChildrenData()
|
||
// 数据变化时重新检查选中状态
|
||
this.validateSelection()
|
||
},
|
||
immediate: true
|
||
},
|
||
// 监听默认选中项变化
|
||
defaultSelection: {
|
||
handler() {
|
||
this.initDefaultSelection()
|
||
},
|
||
immediate: true
|
||
},
|
||
// 监听showSummary变化,重新计算布局
|
||
showSummary: {
|
||
handler() {
|
||
this.$nextTick(() => {
|
||
this.getFooterHeight()
|
||
})
|
||
}
|
||
},
|
||
// 监听parentData变化
|
||
parentData: {
|
||
handler() {
|
||
if (this.children.length) {
|
||
this.children.map(child => {
|
||
child.init()
|
||
})
|
||
}
|
||
}
|
||
},
|
||
// 监听children变化,兼容vue2微信小程序
|
||
children: {
|
||
// #ifdef VUE3
|
||
deep: true, // 兼容微信小程序vue3写法
|
||
// #endif
|
||
handler(newval) {
|
||
if (newval && newval.length > 0) {
|
||
this.childrenData = newval.map(item => {
|
||
return {
|
||
show: item.show,
|
||
label: item.label,
|
||
prop: item.prop,
|
||
headerAlign: item.headerAlign,
|
||
headerEllipsis: item.headerEllipsis,
|
||
align: item.align,
|
||
width: item.width,
|
||
minWidth: item.minWidth,
|
||
sortable: item.sortable,
|
||
type: item.type,
|
||
fixed: item.fixed,
|
||
columnKey: item.columnKey,
|
||
className: item.className,
|
||
customStyle: item.customStyle,
|
||
formatter: item.formatter,
|
||
}
|
||
})
|
||
}
|
||
}
|
||
}
|
||
},
|
||
// #ifdef VUE3
|
||
emits: ['cellClick', 'sortChange', 'selectionChange', 'select', 'selectAll'],
|
||
// #endif
|
||
methods: {
|
||
// 获取合计行的值
|
||
getSummaryValue(column, index) {
|
||
|
||
// 如果使用自定义合计方法
|
||
if (this.summaryMethod && uni.$u.test.func(this.summaryMethod)) {
|
||
const summaryData = this.summaryMethod({
|
||
columns: this.childrenData,
|
||
data: this.sortedData,
|
||
column,
|
||
index
|
||
})
|
||
|
||
if (Array.isArray(summaryData)) {
|
||
return summaryData[index] || ''
|
||
} else if (typeof summaryData === 'object' && summaryData[column.prop]) {
|
||
return summaryData[column.prop]
|
||
}
|
||
}
|
||
|
||
// 默认合计逻辑
|
||
if (index === 0) {
|
||
// 第一列显示合计文本
|
||
return this.sumText
|
||
}
|
||
|
||
// 检查该列是否需要合计
|
||
if (column.prop && this.summaryColumns.includes(column.prop)) {
|
||
return this.calculateColumnSum(column.prop)
|
||
}
|
||
|
||
// 如果summaryColumns为空数组,则自动合计数字列
|
||
if (this.summaryColumns.length === 0 && column.prop && column.type !== 'selection' && column.type !== 'index') {
|
||
const isNumericColumn = this.sortedData.every(row => {
|
||
const value = this.getValueByPath(row, column.prop)
|
||
return value === null || value === undefined || value === '' || !isNaN(Number(value))
|
||
})
|
||
|
||
if (isNumericColumn) {
|
||
return this.calculateColumnSum(column.prop)
|
||
}
|
||
}
|
||
|
||
return ''
|
||
},
|
||
|
||
// 计算列的合计值
|
||
calculateColumnSum(prop) {
|
||
if (!prop || !this.sortedData || this.sortedData.length === 0) {
|
||
return '0'
|
||
}
|
||
|
||
let sum = 0
|
||
let hasValidNumber = false
|
||
|
||
this.sortedData.forEach(row => {
|
||
const value = this.getValueByPath(row, prop)
|
||
const numValue = parseFloat(value)
|
||
|
||
if (!isNaN(numValue)) {
|
||
sum += numValue
|
||
hasValidNumber = true
|
||
}
|
||
})
|
||
|
||
if (!hasValidNumber) {
|
||
return ''
|
||
}
|
||
|
||
// 保留两位小数并去除多余的0
|
||
return parseFloat(sum.toFixed(2)).toString()
|
||
},
|
||
|
||
// 初始化默认选中项
|
||
initDefaultSelection() {
|
||
if (this.defaultSelection && this.defaultSelection.length > 0) {
|
||
this.selectedRows = [...this.defaultSelection]
|
||
this.selectedRowKeys = this.defaultSelection.map(row => this.getRowKey(row))
|
||
}
|
||
},
|
||
|
||
// 验证选中状态(数据变化时调用)
|
||
validateSelection() {
|
||
if (!this.sortedData || this.sortedData.length === 0) {
|
||
this.clearSelection()
|
||
return
|
||
}
|
||
|
||
// 过滤出仍然存在的选中项
|
||
const validSelectedRows = []
|
||
const validSelectedRowKeys = []
|
||
|
||
this.selectedRows.forEach(selectedRow => {
|
||
const rowKey = this.getRowKey(selectedRow)
|
||
const stillExists = this.sortedData.some(row => this.getRowKey(row) === rowKey)
|
||
if (stillExists) {
|
||
validSelectedRows.push(selectedRow)
|
||
validSelectedRowKeys.push(rowKey)
|
||
}
|
||
})
|
||
|
||
if (validSelectedRows.length !== this.selectedRows.length) {
|
||
this.selectedRows = validSelectedRows
|
||
this.selectedRowKeys = validSelectedRowKeys
|
||
this.$emit('selectionChange', this.selectedRows)
|
||
}
|
||
},
|
||
|
||
// 获取行的唯一标识
|
||
getRowKey(row) {
|
||
if (!row) return ''
|
||
return this.rowKey ? this.getValueByPath(row, this.rowKey) : JSON.stringify(row)
|
||
},
|
||
|
||
// 切换行选中状态
|
||
toggleRowSelection(row, selected) {
|
||
|
||
const rowKey = this.getRowKey(row)
|
||
const index = this.selectedRowKeys.indexOf(rowKey)
|
||
|
||
if (selected === undefined) {
|
||
selected = index === -1
|
||
}
|
||
|
||
if (selected && index === -1) {
|
||
// 选中
|
||
this.selectedRows.push(row)
|
||
this.selectedRowKeys.push(rowKey)
|
||
} else if (!selected && index !== -1) {
|
||
// 取消选中
|
||
this.selectedRows.splice(index, 1)
|
||
this.selectedRowKeys.splice(index, 1)
|
||
}
|
||
|
||
this.$emit('select', this.selectedRows, row)
|
||
this.$emit('selectionChange', this.selectedRows)
|
||
},
|
||
|
||
// 全选/取消全选
|
||
handleSelectAll(checked) {
|
||
if (checked) {
|
||
// 全选
|
||
this.selectedRows = [...this.sortedData]
|
||
this.selectedRowKeys = this.sortedData.map(row => this.getRowKey(row))
|
||
} else {
|
||
// 取消全选
|
||
this.selectedRows = []
|
||
this.selectedRowKeys = []
|
||
}
|
||
|
||
// 强制更新子组件数据,确保复选框状态同步
|
||
this.updateChildrenData()
|
||
|
||
this.$emit('selectAll', this.selectedRows)
|
||
this.$emit('selectionChange', this.selectedRows)
|
||
},
|
||
|
||
// 清空选择
|
||
clearSelection() {
|
||
this.selectedRows = []
|
||
this.selectedRowKeys = []
|
||
this.$emit('selectionChange', this.selectedRows)
|
||
},
|
||
|
||
// 获取当前选中的行
|
||
getSelectionRows() {
|
||
return this.selectedRows
|
||
},
|
||
|
||
// 获取嵌套属性值
|
||
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
|
||
},
|
||
|
||
// 更新子组件数据
|
||
updateChildrenData() {
|
||
if (this.children && this.children.length > 0) {
|
||
this.children.forEach(child => {
|
||
if (child.parentData) {
|
||
// 传递排序后的数据给子组件
|
||
child.parentData.data = this.sortedData
|
||
child.parentData.border = this.border
|
||
child.parentData.stripe = this.stripe
|
||
|
||
// 传递合并信息
|
||
child.parentData.mergeInfo = this.mergeInfo
|
||
|
||
// 传递选中状态
|
||
if (child.type === 'selection') {
|
||
child.parentData.selectedRowKeys = this.selectedRowKeys
|
||
child.parentData.toggleRowSelection = this.toggleRowSelection.bind(this)
|
||
}
|
||
}
|
||
})
|
||
}
|
||
},
|
||
|
||
// 获取下拉菜单内容的高度
|
||
async getHeaderHeight() {
|
||
await uni.$u.sleep(30);
|
||
// #ifndef APP-NVUE
|
||
this.$uGetRect('.u-table__header').then(res => {
|
||
this.headerHeight = res.height;
|
||
});
|
||
// #endif
|
||
|
||
// #ifdef APP-NVUE
|
||
const ref = this.$refs['u-table__header']
|
||
ref && dom.getComponentRect(ref, (res) => {
|
||
this.headerHeight = res.size.height
|
||
});
|
||
// #endif
|
||
},
|
||
|
||
// 获取表尾高度
|
||
async getFooterHeight() {
|
||
if (!this.showSummary) {
|
||
this.footerHeight = 0
|
||
return
|
||
}
|
||
|
||
await uni.$u.sleep(30);
|
||
// #ifndef APP-NVUE
|
||
this.$uGetRect('.u-table__footer').then(res => {
|
||
this.footerHeight = res.height || 0;
|
||
});
|
||
// #endif
|
||
|
||
// #ifdef APP-NVUE
|
||
const ref = this.$refs['u-table__footer']
|
||
ref && dom.getComponentRect(ref, (res) => {
|
||
this.footerHeight = res.size.height || 0
|
||
});
|
||
// #endif
|
||
},
|
||
|
||
// 表头点击事件处理
|
||
handleHeaderClick(column, index) {
|
||
if (column.type === 'selection') {
|
||
// 多选列不处理排序
|
||
return
|
||
}
|
||
this.handleSort(column, index)
|
||
},
|
||
|
||
handleSort(column, index) {
|
||
if (!column.sortable || column.type === 'index') return
|
||
|
||
let newOrder = 'asc'
|
||
|
||
// 如果点击的是当前排序列
|
||
if (this.sortColumn === column.prop) {
|
||
if (this.sortOrder === 'asc') {
|
||
// 当前是升序,切换到降序
|
||
newOrder = 'desc'
|
||
} else if (this.sortOrder === 'desc') {
|
||
// 当前是降序,取消排序
|
||
this.sortColumn = null
|
||
this.sortOrder = null
|
||
this.$emit('sortChange', {
|
||
prop: null,
|
||
order: null,
|
||
column: column,
|
||
index: index
|
||
})
|
||
return
|
||
}
|
||
}
|
||
|
||
// 设置新的排序状态
|
||
this.sortColumn = column.prop
|
||
this.sortOrder = newOrder
|
||
|
||
// 触发排序变化事件
|
||
this.$emit('sortChange', {
|
||
prop: this.sortColumn,
|
||
order: this.sortOrder,
|
||
column: column,
|
||
index: index
|
||
})
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
@import "../../libs/css/components.scss";
|
||
|
||
.u-table {
|
||
position: relative;
|
||
font-size: 14px;
|
||
overflow: hidden;
|
||
min-width: 100%;
|
||
|
||
&__header {
|
||
@include flex(row);
|
||
background-color: $u-bg-color;
|
||
flex-shrink: 0;
|
||
|
||
&__column {
|
||
@include flex(row);
|
||
flex-shrink: 0;
|
||
&:last-child .is-border {
|
||
border-right: none;
|
||
}
|
||
}
|
||
}
|
||
|
||
&__footer {
|
||
@include flex(row);
|
||
background-color: $u-bg-color;
|
||
flex-shrink: 0;
|
||
border-top: 1px solid $u-border-color;
|
||
|
||
&__column {
|
||
@include flex(row);
|
||
flex-shrink: 0;
|
||
|
||
&:last-child .is-border {
|
||
border-right: none;
|
||
}
|
||
}
|
||
}
|
||
|
||
&__body {
|
||
flex: 1;
|
||
width: 100%;
|
||
}
|
||
|
||
&__rows {
|
||
@include flex(row);
|
||
width: 100%;
|
||
}
|
||
|
||
&__cell {
|
||
@include flex(row);
|
||
flex: 1;
|
||
padding: 0px 10px;
|
||
align-items: center;
|
||
position: relative;
|
||
|
||
&.is-left {
|
||
justify-content: flex-start;
|
||
}
|
||
|
||
&.is-center {
|
||
justify-content: center;
|
||
}
|
||
|
||
&.is-right {
|
||
justify-content: flex-end;
|
||
}
|
||
|
||
&.is-border {
|
||
border-right: 1px solid $u-border-color;
|
||
border-bottom: 1px solid $u-border-color;
|
||
}
|
||
}
|
||
|
||
&__summary-cell {
|
||
font-weight: 500;
|
||
color: $u-main-color;
|
||
background-color: #fafafa;
|
||
|
||
&.is-border {
|
||
border-bottom: none;
|
||
}
|
||
}
|
||
|
||
&__value {
|
||
font-size: 13px;
|
||
line-height: 15px;
|
||
|
||
&.is-ellipsis {
|
||
word-break: break-all;
|
||
display: -webkit-box;
|
||
-webkit-box-orient: vertical;
|
||
-webkit-line-clamp: 1;
|
||
overflow: hidden;
|
||
}
|
||
}
|
||
|
||
&.is-border {
|
||
border: 1px solid $u-border-color;
|
||
}
|
||
}
|
||
|
||
.u-table__sort-icon {
|
||
@include flex(column);
|
||
margin-left: 4px;
|
||
gap: 2px;
|
||
}
|
||
|
||
.u-table__sort-arrow {
|
||
width: 0;
|
||
height: 0;
|
||
border-left: 4px solid transparent;
|
||
border-right: 4px solid transparent;
|
||
transition: border-color 0.3s ease;
|
||
|
||
&--up {
|
||
border-bottom: 5px solid #c0c4cc;
|
||
|
||
&.is-active {
|
||
border-bottom-color: $u-primary;
|
||
}
|
||
}
|
||
|
||
&--down {
|
||
border-top: 5px solid #c0c4cc;
|
||
|
||
&.is-active {
|
||
border-top-color: $u-primary;
|
||
}
|
||
}
|
||
}
|
||
|
||
.u-table__empty {
|
||
@include flex(column);
|
||
align-items: center;
|
||
justify-content: center;
|
||
color: #909399;
|
||
font-size: 14px;
|
||
width: 100%;
|
||
}
|
||
|
||
.u-table__empty-content {
|
||
text-align: center;
|
||
}
|
||
|
||
.u-table__empty-text {
|
||
color: #909399;
|
||
}
|
||
</style> |