123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396 |
- <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) {
- let rowHeight = parseInt(this.parentData.rowHeight);
- let pixelRatio = uni.$u.window().pixelRatio;
- // 基础高度:行高 × 合并行数
- let totalHeight = rowHeight * mergeData.rowspan;
-
- // 如果开启了边框,需要加上被合并行之间的边框高度
- if (this.parentData.border) {
- // 边框宽度需要考虑像素比例,避免出现小数像素
- const borderWidth = Math.round(1 * pixelRatio) / pixelRatio;
- //保留1位小数
- totalHeight += ((mergeData.rowspan - 1) * borderWidth);
- }
-
- style.height = this.$u.addUnit(totalHeight);
- }
- // 设置显示状态
- 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>
|