Vue 组件中的可排序表头实现

Vue 组件中的可排序表头实现

在数据展示中,排序功能是非常常见的需求。本文将通过一个 SortableHeader 组件,展示如何实现可排序的表头。该组件支持根据用户点击的列进行升序、降序和重置排序,同时通过自定义事件将排序信息传递给父组件。

组件实现概述

该组件主要包含以下功能:

  • 展示一个横向滚动的表头
  • 支持点击表头的列来进行排序
  • 根据当前的排序状态,高亮显示对应的排序图标(升序/降序)

代码分析

模板部分 (<template>)
<template>
    <scroll-view
        class="scroll-view"
        :style="{ overflowX: 'auto', whiteSpace: 'nowrap' }"
    >
        <view
            class="header"
            :style="{ display: 'flex', justifyContent: layoutStyle }"
        >
            <view
                v-for="header in headers"
                :key="header.key"
                class="header-item"
                :style="{ margin: `0 ${itemSpacing}px` }"
                @click="requestSort(header.key,header.sortable)"
            >
                <view class="item">
                    <span class="column">{{ header.label }}</span>
                    <view
                        v-show="header.sortable"
                        class="sort-icons"
                    >
                        <view
                            class="sort-icon-up"
                            :class="getIconClass('up', header.key)"
                        />
                        <view
                            class="sort-icon-down"
                            :class="getIconClass('down', header.key)"
                        />
                    </view>
                </view>
            </view>
        </view>
    </scroll-view>
</template>

主要逻辑:

  • v-for 循环生成表头,headers 是一个传入的属性,每个表头项有 keylabel 以及可选的 sortable 属性。
  • requestSort 函数在点击列头时调用,用于切换排序状态。
  • 排序图标(上下箭头)仅在 sortabletrue 时显示,箭头样式通过 getIconClass 进行动态控制。
脚本部分 (<script lang="ts">)
import { defineComponent, ref, watch } from 'vue';

interface Header {
    key: string;
    label: string;
    sortable?: boolean; // 添加 sortable 属性
}

export default defineComponent({
    name: 'SortableHeader',
    props: {
        headers: {
            type: Array as () => Header[],
            required: true,
        },
        layoutStyle: {
            type: String,
            default: 'space-between',
        },
        itemSpacing: {
            type: Number,
            required: true,
            default: 10,
        },
        defaultSort: {
            type: Object as () => { key: string; state: number },
            default: () => ({ key: '', state: 0 }), // 默认值
        },
    },
    emits: ['sort'],
    setup(props, { emit }) {
        const sortConfig = ref<{ key: string; state: number }>({
            key: props.defaultSort.key,
            state: props.defaultSort.state,
        });

        watch(
            () => props.defaultSort,
            (newSort) => {
                sortConfig.value = { key: newSort.key, state: newSort.state };
            },
            { immediate: true }
        );

        const requestSort = (key: string, sortable: boolean) => {
            if (!sortable) return;
            if (sortConfig.value.key === key) {
                sortConfig.value.state = sortConfig.value.state === 1 ? -1 : (sortConfig.value.state === -1 ? 0 : 1);
            } else {
                sortConfig.value.key = key;
                sortConfig.value.state = 1;
            }
            emitSortEvent();
        };

        const emitSortEvent = () => {
            if (sortConfig.value.state === 1) {
                emit('sort', { key: sortConfig.value.key, order: 'asc', state: sortConfig.value.state });
            } else if (sortConfig.value.state === -1) {
                emit('sort', { key: sortConfig.value.key, order: 'desc', state: sortConfig.value.state });
            }
        };

        const getIconClass = (direction: string, key: string) => {
            if (sortConfig.value.state === 0) return 'inactive';
            if (sortConfig.value.key !== key) return 'inactive';
            return direction === 'up' && sortConfig.value.state === 1
                ? 'active'
                : direction === 'down' && sortConfig.value.state === -1
                    ? 'active'
                    : 'inactive';
        };

        return {
            requestSort,
            getIconClass,
        };
    },
});

主要逻辑:

  • Props:

    • headers 是表头的配置数组。
    • layoutStyle 控制表头的对齐方式,默认为 space-between
    • itemSpacing 控制列之间的间距。
    • defaultSort 指定默认的排序方式,包括排序字段 key 和排序状态 state(0: 无排序,1: 升序,-1: 降序)。
  • 响应式状态

    • sortConfig 保存当前排序的字段和状态。
  • 排序逻辑

    • requestSort 根据传入的 keysortable 来切换排序。如果点击的是当前排序的字段,则循环切换状态(升序、降序、无排序),否则将新字段设为升序。
  • 事件发射

    • emitSortEvent 通过 emit 方法将排序信息传递给父组件,格式为 { key: '字段', order: 'asc/desc', state: 状态 }
  • 图标样式

    • getIconClass 根据当前排序状态返回不同的类名,以控制图标的高亮显示。
样式部分 (<style scoped lang="less">)
.header {
    display: flex;
    cursor: pointer;
    padding: 0 20px;
    height: 90px;
    background: var(--cr-bg);
    align-items: center;
}

.scroll-view {
    overflow-x: auto;
    white-space: nowrap;
}

.header-item {
    display: inline-flex;
    align-items: center;
}

.item {
    display: flex;
    align-items: center;
}

.column {
    font-weight: 400;
    font-size: var(--font-normal);
    color: var(--cr-font-light-grey);
    text-align: right;
    line-height: 28px;
}

.sort-icons {
    display: flex;
    flex-direction: column;
    align-items: center;
    margin-left: 6px;
}

.sort-icon-up,
.sort-icon-down {
    width: 0;
    height: 0;
    display: inline-block;
    margin-top: 0.125rem;
}

/* 上箭头样式 */
.sort-icon-up {
    border-left: 0.175rem solid transparent;
    border-right: 0.175rem solid transparent;
    border-bottom: 0.2rem solid var(--border3);
}

.sort-icon-up.active {
    border-bottom-color: var(--cr-font-light-grey);
}

/* 下箭头样式 */
.sort-icon-down {
    border-left: 0.175rem solid transparent;
    border-right: 0.175rem solid transparent;
    border-top: 0.2rem solid var(--border3);
}

.sort-icon-down.active {
    border-top-color: var(--cr-font-light-grey);
}

样式解读:

  • scroll-view 实现横向滚动,同时设置 white-space: nowrap; 保证所有列在同一行。
  • .sort-icons 用来控制排序图标的上下排列,包含上箭头(升序)和下箭头(降序)。
  • sort-icon-upsort-icon-down 通过 border 样式实现三角形的箭头效果,并通过类名 activeinactive 控制高亮显示。

组件使用示例

<template>
  <SortableHeader
    :headers="[
      { key: 'name', label: '名称', sortable: true },
      { key: 'date', label: '日期', sortable: true },
      { key: 'price', label: '价格', sortable: false }
    ]"
    :layoutStyle="'space-between'"
    :itemSpacing="20"
    @sort="handleSort"
  />
</template>

<script setup lang="ts">
import SortableHeader from './SortableHeader.vue';

const handleSort = (sortInfo: { key: string; order: string; state: number }) => {
  console.log('排序信息:', sortInfo);
};
</script>

小结

通过 SortableHeader 组件,您可以轻松实现带有排序功能的表头,并通过事件机制将排序状态传递给父组件。该组件的高扩展性和灵活性使得它可以轻松适配不同的表头需求,同时提供了良好的用户交互体验。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值