PrimeReact中双击行不应触发onSelectionChange方法
解决PrimeReact Table单击行选中、双击行跳转不触发选中的冲突问题
问题场景
基于Next.js + TypeScript + PrimeReact的项目,表格需求:
- 单击表格任意行(含非复选框区域)触发行选中(
onSelectionChange) - 双击行时跳转到详情页面,不触发行选中事件
当前问题:双击行时会触发onSelectionChange,导致行被选中后才跳转,不符合需求。
解决方案思路
核心是通过延迟定时器区分单击/双击事件:
- 记录每次行点击的时间戳,判断两次点击的时间差是否在双击阈值(如200ms)内
- 双击时标记状态,让
onSelectionChange跳过选中逻辑 - 调整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;
关键修改说明
- 新增状态管理:添加
lastClickTime记录点击时间,isDoubleClick标记是否为双击事件 - 调整Table选中模式:设置
selectionMode="multiple"+showSelectCheckbox: true,实现单击行或复选框都能选中的效果 - 修改
onSelectionChange:仅在非双击状态下更新选中行 - 合并行点击事件:在
onRowClick中判断单击/双击,双击时直接跳转并阻止选中逻辑,单击时正常允许选中
内容的提问来源于stack exchange,提问作者Ali Ehyaie
相关产品推荐
相关产品推荐

