You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

PrimeReact中双击行不应触发onSelectionChange方法

解决PrimeReact Table单击行选中、双击行跳转不触发选中的冲突问题

问题场景

基于Next.js + TypeScript + PrimeReact的项目,表格需求:

  • 单击表格任意行(含非复选框区域)触发行选中(onSelectionChange)
  • 双击行时跳转到详情页面,不触发行选中事件

当前问题:双击行时会触发onSelectionChange,导致行被选中后才跳转,不符合需求。

解决方案思路

核心是通过延迟定时器区分单击/双击事件:

  1. 记录每次行点击的时间戳,判断两次点击的时间差是否在双击阈值(如200ms)内
  2. 双击时标记状态,让onSelectionChange跳过选中逻辑
  3. 调整PrimeReact Table的选中模式,实现单击行选中 + 显示复选框的效果

修改后的完整代码

import React, {useEffect, useState} from 'react';
import {DataTableDataSelectableParams} from 'primereact/datatable';
import Table from '../Table';
import {AutoCompleteCompleteMethodParams} from 'primereact/autocomplete';
import {FlightStaticService} from '../../../adapter/FlightStaticService';
import OptionsMenu from '../../OptionsMenu/OptionsMenu';
import {InputSwitch} from 'primereact/inputswitch';
import InputWrapper from '../../InputWrapper/InputWrapper';
import {FlightService} from '../../../adapter/FlightService';
import ConfirmationStatus from '../../ConfirmationStatus/ConfirmationStatus';
import {useRouter} from 'next/router';

const flightStaticInstance = new FlightStaticService();
const flightInstance = new FlightService();

const FlightsListTable = () => {
    const [selectedRows, setSelectedRows] = useState<{ [key: string]: string | number | boolean }[]>([]);
    const [filteredAirlines, setFilteredAirlines] = useState([]);
    const [filteredAirports, setFilteredAirports] = useState([]);
    const [shouldUpdateTable, setShouldUpdateTable] = useState(false);
    // 新增状态:记录上次点击时间、标记是否为双击
    const [lastClickTime, setLastClickTime] = useState<number>(0);
    const [isDoubleClick, setIsDoubleClick] = useState<boolean>(false);
    const router = useRouter();

    const searchAirlines = (e: AutoCompleteCompleteMethodParams) => {
        if (!e.query) {
            e.query = 'as';
        }

        flightStaticInstance
            .getAirlines(e.query)
            .then((res) => {
                setFilteredAirlines(res.data.result);
            })
            .catch(e => {
                setFilteredAirlines([]);
            });
    };
    const searchAirports = (e: AutoCompleteCompleteMethodParams) => {
        if (!e.query) {
            e.query = 'meh';
        }

        flightStaticInstance
            .getAirports(e.query)
            .then((res) => {
                setFilteredAirports(res.data.result);
            })
            .catch(e => {
                setFilteredAirports([]);
            });
    };
    const isRowSelectable = (event: DataTableDataSelectableParams) => {
        const data = event.data;
        if (selectedRows.find((sel) => sel.id === data.id)) {
            return true;
        }
        return selectedRows.length < 2 && data.isActive;
    };

    useEffect(() => {
        if (shouldUpdateTable) {
            setShouldUpdateTable(false);
        }
    }, [shouldUpdateTable]);

    useEffect(() => {
        if (selectedRows.length > 0) {
            sessionStorage.setItem('flights', JSON.stringify(selectedRows));
        }
    }, [selectedRows]);

    const confirmStatusBodyTemplate = (rowData: any) => {
        return <ConfirmationStatus status={rowData.status}/>
    };

    const statusBodyTemplate = (rowData: any) => {
        return rowData.isActive ? 'فعال' : 'غیرفعال';
    };

    const optionsBodyTemplate = (rowData: any) => {
        return <OptionsMenu options={[{
            type: 'link',
            url: `/flight/${rowData.id}`,
            label: 'جزییات پرواز',
            iconName: 'icon-note-text2'
        }, {
            type: 'link',
            url: `/flight/${rowData.id}/edit`,
            label: 'ویرایش پرواز',
            iconName: 'icon-edit-2'
        },
            {
                type: 'link',
                url: `/flight/${rowData.id}/pricing?flightGroupTitle=${rowData.flightGroupTitle}`,
                label: 'تقویم قیمتی',
                iconName: 'icon-calendar-2'
            },
            {
                type: 'element',
                element: <div className='w-full' onClick={e => e.stopPropagation()}>
                    <InputWrapper labelClassName='text-grey-4' className='w-full' labelBeforeInput={true}
                                  labelBesideInput label='وضعیت'>
                        <InputSwitch
                            onChange={e => {
                                flightInstance.toggleFlightStatus(rowData.id).then(res => {
                                    setShouldUpdateTable(true);
                                }).catch(e => {

                                });
                            }
                            }
                            checked={rowData.isActive}
                            className='mr-auto'/>
                    </InputWrapper>
                </div>
            }
        ]}/>
    }

    return (
        <Table
            url="/Flight/GetFlights"
            shouldUpdateTable={shouldUpdateTable}
            filters={[
                {
                    name: 'airlineId',
                    label: 'ایرلاین',
                    type: 'autocomplete',
                    value: '',
                    suggestions: filteredAirlines,
                    completeMethod: searchAirlines,
                    optionValue: 'iata',
                    optionType: 'string',
                    fieldName: 'nameFa'
                },
                {
                    name: 'flightGroupTitle',
                    label: 'عنوان پرواز',
                    type: 'text',
                    value: ''
                },
                {
                    name: 'originAirPortId',
                    label: 'فرودگاه مبدا',
                    type: 'autocomplete',
                    value: '',
                    optionValue: 'iata',
                    optionType: 'string',
                    suggestions: filteredAirports,
                    completeMethod: searchAirports,
                    fieldName: 'nameFa'

                },
                {
                    name: 'destinationAirPortId',
                    label: 'فرودگاه مقصد',
                    type: 'autocomplete',
                    value: '',
                    optionValue: 'iata',
                    optionType: 'string',
                    suggestions: filteredAirports,
                    completeMethod: searchAirports,
                    fieldName: 'nameFa'
                }
            ]}
            columns={[
                {
                    field: 'airlineNameFa',
                    header: 'ایرلاین',
                },
                {
                    field: 'flightGroupTitle',
                    header: 'عنوان پرواز',
                    sortable: true,
                },
                {field: 'originCityNameFa', header: 'مبدa'},
                {field: 'destinationCityNameFa', header: 'مقصد'},
                {field: 'baggageAllowance', header: 'بار مجاز', sortable: true},
                {
                    field: 'confirmStatus',
                    header: 'وضعیت تایید',
                    body: confirmStatusBodyTemplate,
                },
                {
                    field: 'isActive',
                    header: 'وضعیت',
                    body: statusBodyTemplate,
                },
                {
                    field: 'options',
                    body: optionsBodyTemplate
                },
            ]}
            tableProps={{
                selection: selectedRows,
                // 开启行选中模式,同时显示复选框
                selectionMode: "multiple",
                showSelectCheckbox: true,
                // 仅在非双击时执行选中逻辑
                onSelectionChange: (e) => {
                    if (!isDoubleClick) {
                        setSelectedRows(e.value);
                    }
                },
                isDataSelectable: isRowSelectable,
                showSelectAll: false,
                rowClassName: (data) => data.isActive ? '' : 'text-disabled',
                // 合并处理单击/双击事件
                onRowClick: (e) => {
                    const currentTime = Date.now();
                    const timeDiff = currentTime - lastClickTime;

                    if (timeDiff < 200) {
                        // 判定为双击:标记状态,执行跳转
                        setIsDoubleClick(true);
                        router.push(`/flight/${e.data.id}`);
                        // 延迟重置双击标记,避免影响后续操作
                        setTimeout(() => setIsDoubleClick(false), 250);
                    } else {
                        // 判定为单击:重置标记,等待确认无双击后执行选中
                        setIsDoubleClick(false);
                        setTimeout(() => {
                            if (!isDoubleClick) {
                                // 此处无需手动处理选中,selectionMode开启后点击行会自动触发onSelectionChange
                            }
                        }, 200);
                    }
                    setLastClickTime(currentTime);
                }
                // 移除原onRowDoubleClick,避免冲突
            }}
        />
    );
};

export default FlightsListTable;

关键修改说明

  1. 新增状态管理:添加lastClickTime记录点击时间,isDoubleClick标记是否为双击事件
  2. 调整Table选中模式:设置selectionMode="multiple" + showSelectCheckbox: true,实现单击行或复选框都能选中的效果
  3. 修改onSelectionChange:仅在非双击状态下更新选中行
  4. 合并行点击事件:在onRowClick中判断单击/双击,双击时直接跳转并阻止选中逻辑,单击时正常允许选中

内容的提问来源于stack exchange,提问作者Ali Ehyaie

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.07 10:40:53