news 2025/12/13 22:04:17

封装一个table

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
封装一个table

一、需求描述

1、手动封装一个table,如果数据中有勾选显示勾选列。无勾选不显示这列。

2、table数据中如果有勾选的数据,勾选的这行row设置渐变背景色。

3、采用的是直接传入data,或者通过接口查询。

4、table内容超出时,显示横向滚动条,表头也要一起滚动。

二、效果图

三、代码实现

1、基础模式:直接每列添加宽度

组件封装

1、main.tsx // 级件页面

2、store.ts // ts

3、main.less // 样式

// index.tsx import React, { useEffect, useMemo, useState } from 'react'; import { languageConfig } from '@/language/language'; import { rebatePersonnelInformationListChildApi } from '@/api/rebatePersonnelInformation'; import { queryMapIdpValue } from 'services/api'; import { message, Tooltip } from 'choerodon-ui/pro'; import { handleFormatAmount } from '@/public/store'; import TableEmpty from '../TableEmpty'; import { ColumnConfig, StatusItems, TableData } from './store'; import styles from './main.less'; // 表头配置 const tableHeaders = [ { key: 'isIssueRebate', label: '', width: '50px', align: 'center' as const, visible: true, render: (item: TableData) => item.isIssueRebate === 'Y' ? ( <img src={require('@/assets/imgs/icon_success.svg')} alt="选中" style={{ width: '16px', height: '16px' }} /> ) : null, }, { key: 'serialNumber', label: languageConfig('', '序号'), width: '60px', align: 'center' as const, visible: true, render: (item: TableData, index: number) => index + 1, }, { key: 'personalName', label: languageConfig('', '认证人员'), width: '120px', align: 'left' as const, visible: true, tooltip: true, render: (item: TableData) => item.personalName, }, { key: 'certificationLevel', label: languageConfig('', '认证等级'), width: '120px', align: 'left' as const, visible: true, tooltip: true, render: () => '', // 动态计算 }, { key: 'baseNumber', label: languageConfig('', '等级基数'), width: '120px', align: 'left' as const, visible: true, tooltip: true, render: (item: TableData) => handleFormatAmount(item.baseNumber), }, { key: 'evaluationCoefficient', label: languageConfig('', '年度贡献评价'), width: '120px', align: 'left' as const, visible: true, tooltip: true, render: (item: TableData) => item.evaluationCoefficient, }, { key: 'rebateAmount', label: languageConfig('', '返点金额'), width: '120px', align: 'left' as const, visible: true, tooltip: true, render: (item: TableData) => handleFormatAmount(item.rebateAmount), }, ]; const PersonnelList: React.FC<{ id?: number; data?: TableData[] | null; // styleClass?: any; }> = ({ id, data }) => { const [tableData, setTableData] = useState<TableData[]>([]); // 表格数据 const [hasChecked, setHasChecked] = useState<boolean>(false); // 表格是否有勾选数据 // 认证类型、认证等级 const [certOptions, setCertOptions] = useState<{ type: StatusItems[]; level: StatusItems[]; }>({ type: [], level: [] }); /** 获取值集:'认证类型'、'认证等级' */ useEffect(() => { const fetchCertOptions = async () => { const res = await queryMapIdpValue([ 'PRM_AE_CERTIFICATION_LEVEL_2', 'PRM_AE_CERTIFICATION_LEVEL_3', ]); setCertOptions({ type: res?.[0] || [], level: res?.[1] || [] }); }; fetchCertOptions(); }, []); useEffect(() => { const fetchTableData = async () => { let resData: TableData[] = []; // 优先使用传入的 data,无则调用接口 if (data) { resData = data; } else if (id) { const res = await rebatePersonnelInformationListChildApi({ initialId: id, }); if (res.failed) { message.error(res.message, undefined, undefined, 'top'); return; } resData = res; } // 统一赋值 + 判断勾选状态 setTableData(resData); setHasChecked(resData.some(item => item.isIssueRebate === 'Y')); }; fetchTableData(); }, [id, data]); /** 【渲染】认证等级:认证类型-认证等级 */ const renderCertLevel = (item: TableData) => { const type = certOptions.type.find(v => v.value === item.certificationType)?.meaning || ''; const level = certOptions.level.find(v => v.value === item.certificationLevel) ?.meaning || ''; return `${type}-${level}`; }; /** 【渲染】单元格内容-认证等级 */ const renderCellContent = ( content: React.ReactNode, column: ColumnConfig, ) => { if (column.tooltip && content) { return ( <Tooltip theme="dark" title={content}> <span className={styles.tableCell_cellContent}>{content}</span> </Tooltip> ); } return <span className={styles.tableCell_cellContent}>{content}</span>; }; /** 【获取】可见的列配置(根据 hasChecked 过滤) */ const visibleColumns = useMemo(() => { return tableHeaders .map(col => { // 如果是认证等级列,更新render函数 if (col.key === 'certificationLevel') { return { ...col, render: (item: TableData) => renderCertLevel(item), }; } return col; }) .filter(col => { // 如果 hasChecked 为 false,隐藏 isIssueRebate 列 if (col.key === 'isIssueRebate' && !hasChecked) { return false; } return col.visible !== false; }); }, [hasChecked, certOptions]); return ( <> <div className={styles.tableWrapper}> <div className={styles.tableContainer}> {/* 表头 - 使用 visibleColumns */} <div className={styles.tableHeader}> <div className={styles.headerRow}> {visibleColumns.map(column => ( <div key={column.key} className={styles.headerCell} style={{ width: column.width, flex: `0 0 ${column.width}`, textAlign: column.align || 'left', }} > {column.label} </div> ))} </div> </div> {/* 表格内容 - 统一使用 visibleColumns 渲染 */} <div className={styles.tableBody}> {tableData.map((item, index) => { // 设置的背景是整行的背景 const rowStyle = { // 设置的背景是整行的背景 background: item.isIssueRebate === 'Y' ? 'linear-gradient(90deg, rgba(212, 247, 231, 0.8), rgba(212, 247, 231, 0) 70%)' : undefined, // 设置整行的文字颜色 color: item.isIssueRebate === 'Y' ? '#222222' : '#788295', }; return ( <div key={index} className={styles.tableRow} style={rowStyle}> {visibleColumns.map(column => { const content = column.render ? column.render(item, index) : null; return ( <div key={column.key} className={styles.tableCell} style={{ width: column.width, flex: `0 0 ${column.width}`, textAlign: column.align || 'left', }} > {renderCellContent(content, column)} </div> ); })} </div> ); })} </div> </div> </div> </> ); }; export default PersonnelList; // store.ts export interface TableData { isIssueRebate: string; serialNumber: string; personalName: string; certificationLevel: string; certificationType: string; evaluationCoefficient: string; baseNumber: string; rebateAmount: string; } // 列对齐方式 export type AlignType = 'left' | 'center' | 'right'; export interface ColumnConfig { key: string; label: string; width: string; align?: AlignType; visible?: boolean; tooltip?: boolean; render: (item: TableData, index: number) => React.ReactNode; } export interface StatusItems { value?: string; meaning?: string; tag?: string; description?: string; } // main.less // 表格容器 .tableWrapper { width: 100%; background: #fff; display: flex; flex-direction: column; overflow: hidden; position: relative; z-index: 1; } // 表格主容器 .tableContainer { flex: 1; overflow: auto; position: relative; &::-webkit-scrollbar { width: 6px; height: 6px; } &::-webkit-scrollbar-track { background: #f5f5f5; border-radius: 3px; } &::-webkit-scrollbar-thumb { background: #d9d9d9; border-radius: 3px; &:hover { background: #bfbfbf; } } } // 表头 .tableHeader { position: sticky; top: 0; z-index: 10; background: #fafafa; border-bottom: 1px solid #e5e6e8; min-width: fit-content; .headerRow { display: flex; width: 100%; min-width: 100%; } .headerCell { flex-shrink: 0; line-height: 36px; height: 36px; background: #f5faff; font-size: 12px; color: #000000; font-weight: 700; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; text-align: left; padding: 0 12px; // 第一列固定宽度 // &:first-child { // width: 100px; // flex: 0 0 100px; // } // 其他列均分剩余宽度 // &:not(:first-child) { // flex: 1; // min-width: 120px; // } } } // 表格内容 .tableBody { .tableRow { display: flex; width: 100%; min-width: 100%; border-bottom: 1px solid #e5e6e8; background: #fff; // 默认背景 // 斑马纹 // &:nth-child(even) { // background: pink; // } // 渐变行(JSX中设置的) &.gradientRow { background: linear-gradient( 90deg, rgba(212, 247, 231, 0.8), rgba(212, 247, 231, 0) 70% ) !important; color: #222222 !important; // 如果是偶数行 &:nth-child(even) { background: linear-gradient( 90deg, rgba(212, 247, 231, 0.8), rgba(212, 247, 231, 0) 70% ) !important; } // &:hover { // background: linear-gradient( // 90deg, // rgba(200, 237, 221, 0.9), // rgba(200, 237, 221, 0) 70% // ) !important; // } } // &:hover { // background: #f0f9ff; // } &:last-child { border-bottom: none; } } .tableCell { flex-shrink: 0; line-height: 36px; height: 36px; color: inherit; overflow: hidden; background: none !important; // 关键:不设置任何背景色,完全显示行背景 padding: 0 12px; // &:first-child { // width: 100px; // flex: 0 0 100px; // // border-right: 1px solid #f0f0f0; // } // &:not(:first-child) { // flex: 1; // min-width: 120px; // } &_cellContent { display: block; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; line-height: 36px; color: inherit; } } }

2、高级模式:组件传入type:前两列固定,后面均分

组件使用:

<PersonnelList

data={tableRecord?.rebateTechnologyPersonalVOList}

type="look"

/>

(1)组件代码

此模块的组件是,正确可使用版本。

1、样式需注意。

2、逻辑需注意

效果图1:

效果图2:(传入type)

import React, { useEffect, useMemo, useState } from 'react'; import { languageConfig } from '@/language/language'; import { rebatePersonnelInformationListChildApi } from '@/api/rebatePersonnelInformation'; import { queryMapIdpValue } from 'services/api'; import { message, Tooltip } from 'choerodon-ui/pro'; import { handleFormatAmount } from '@/public/store'; import TableEmpty from '../TableEmpty'; import { ColumnConfig, StatusItems, TableData } from './store'; import styles from './main.less'; // 表头配置 const tableHeaders = [ { key: 'isIssueRebate', label: '', width: '50px', align: 'center' as const, visible: true, render: (item: TableData) => item.isIssueRebate === 'Y' ? ( <img src={require('@/assets/imgs/icon_success.svg')} alt="选中" style={{ width: '16px', height: '16px' }} /> ) : null, }, { key: 'serialNumber', label: languageConfig('', '序号'), width: '60px', align: 'center' as const, visible: true, render: (item: TableData, index: number) => index + 1, }, { key: 'personalName', label: languageConfig('', '认证人员'), // width: '120px', align: 'left' as const, visible: true, tooltip: true, render: (item: TableData) => item.personalName, }, { key: 'certificationLevel', label: languageConfig('', '认证等级'), // width: '120px', align: 'left' as const, visible: true, tooltip: true, render: () => '', // 动态计算 }, { key: 'baseNumber', label: languageConfig('', '等级基数'), align: 'left' as const, visible: true, tooltip: true, render: (item: TableData) => handleFormatAmount(item.baseNumber), }, { key: 'evaluationCoefficient', label: languageConfig('', '年度贡献评价'), align: 'left' as const, visible: true, tooltip: true, render: (item: TableData) => item.evaluationCoefficient, }, { key: 'rebateAmount', label: languageConfig('', '返点金额'), align: 'left' as const, visible: true, tooltip: true, render: (item: TableData) => handleFormatAmount(item.rebateAmount), }, ]; const PersonnelList: React.FC<{ id?: number; data?: TableData[] | null; type?: string; }> = ({ id, data, type = '' }) => { const [tableData, setTableData] = useState<TableData[]>([]); const [hasChecked, setHasChecked] = useState<boolean>(false); const [certOptions, setCertOptions] = useState<{ type: StatusItems[]; level: StatusItems[]; }>({ type: [], level: [] }); /** 获取值集:'认证类型'、'认证等级' */ useEffect(() => { const fetchCertOptions = async () => { const res = await queryMapIdpValue([ 'PRM_AE_CERTIFICATION_LEVEL_2', 'PRM_AE_CERTIFICATION_LEVEL_3', ]); setCertOptions({ type: res?.[0] || [], level: res?.[1] || [] }); }; fetchCertOptions(); }, []); useEffect(() => { const fetchTableData = async () => { let resData: TableData[] = []; if (data) { resData = data; } else if (id) { const res = await rebatePersonnelInformationListChildApi({ initialId: id, }); if (res.failed) { message.error(res.message, undefined, undefined, 'top'); return; } resData = res; } setTableData(resData); setHasChecked(resData.some(item => item.isIssueRebate === 'Y')); }; fetchTableData(); }, [id, data]); /** 【渲染】认证等级:认证类型-认证等级 */ const renderCertLevel = (item: TableData) => { const type = certOptions.type.find(v => v.value === item.certificationType)?.meaning || ''; const level = certOptions.level.find(v => v.value === item.certificationLevel) ?.meaning || ''; return `${type}-${level}`; }; /** 【渲染】单元格内容 */ const renderCellContent = ( content: React.ReactNode, column: ColumnConfig, ) => { if (column.tooltip && content) { return ( <Tooltip theme="dark" title={content}> <span className={styles.prmRcTableWrapper__cellContent}> {content} </span> </Tooltip> ); } return ( <span className={styles.prmRcTableWrapper__cellContent}>{content}</span> ); }; /** 【获取】可见的列配置 */ const visibleColumns = useMemo(() => { const headers = tableHeaders.map(col => { if (col.key === 'certificationLevel') { return { ...col, render: (item: TableData) => renderCertLevel(item), }; } return col; }); return headers.filter(col => { if (col.key === 'isIssueRebate' && !hasChecked) { return false; } return col.visible !== false; }); }, [hasChecked, certOptions]); /** 【渲染】单元格样式 */ const getCellStyle = (column: ColumnConfig) => { const style: React.CSSProperties = { textAlign: column.align || 'left', }; // 如果有设置宽度,使用固定宽度 if (column.width) { style.width = column.width; style.flex = `0 0 ${column.width}`; } else { // 没有设置宽度的列均分剩余宽度 style.flex = '1'; style.minWidth = '120px'; } // 如果是 look 模式,序号列特殊处理 if (type === 'look' && column.key === 'serialNumber') { style.width = '50px'; style.flex = '0 0 50px'; } return style; }; /** 【计算】表格总宽度 */ const calculateTotalWidth = useMemo(() => { if (type === 'look') { return '100%'; // look 模式使用百分比宽度 } // 计算所有固定宽度列的总宽度 const fixedWidthColumns = visibleColumns.filter(col => col.width); const totalFixedWidth = fixedWidthColumns.reduce((sum, col) => { const widthMatch = col.width?.match(/(\d+)/); const width = widthMatch ? parseInt(widthMatch[1]) : 0; return sum + width; }, 0); // 为均分列预留最小宽度(假设每列最小120px) const autoWidthColumns = visibleColumns.filter(col => !col.width); const minAutoWidth = autoWidthColumns.length * 120; return totalFixedWidth + minAutoWidth + 20; // 加一些缓冲 }, [visibleColumns, type]); return ( <div className={styles.prmRcTableWrapper}> <div className={styles.prmRcTableWrapper__tableContainer} style={{ minWidth: type === 'look' ? '100%' : `${calculateTotalWidth}px`, }} > {/* 表头 */} <div className={styles.prmRcTableWrapper__tableHeader}> <div className={styles.prmRcTableWrapper__headerRow}> {visibleColumns.map(column => ( <div key={column.key} className={styles.prmRcTableWrapper__headerCell} style={getCellStyle(column)} > {column.label} </div> ))} </div> </div> {/* 表格内容 */} <div className={styles.prmRcTableWrapper__tableBody}> {tableData?.length > 0 ? ( tableData.map((item, index) => { // 设置的背景是整行的背景 const rowStyle = { // 设置的背景是整行的背景 background: item.isIssueRebate === 'Y' ? 'linear-gradient(90deg, rgba(212, 247, 231, 0.8), rgba(212, 247, 231, 0) 70%)' : undefined, // 设置整行的文字颜色 color: item.isIssueRebate === 'Y' ? '#222222' : '#788295', }; return ( <div key={index} className={styles.prmRcTableWrapper__tableRow} style={rowStyle} > {visibleColumns.map(column => { const content = column.render ? column.render(item, index) : null; return ( <div key={column.key} className={styles.prmRcTableWrapper__tableCell} style={getCellStyle(column)} > {renderCellContent(content, column)} </div> ); })} </div> ); }) ) : ( <TableEmpty /> )} </div> </div> </div> ); }; export default PersonnelList; // main.less // 表格容器 - BEM风格 .prmRcTableWrapper { width: 100%; background: #fff; display: flex; flex-direction: column; overflow: hidden; position: relative; z-index: 1; // 表格主容器 &__tableContainer { flex: 1; overflow: auto; position: relative; &::-webkit-scrollbar { width: 6px; height: 6px; } &::-webkit-scrollbar-track { background: #f5f5f5; border-radius: 3px; } &::-webkit-scrollbar-thumb { background: #d9d9d9; border-radius: 3px; &:hover { background: #bfbfbf; } } // 表头 .prmRcTableWrapper__tableHeader { position: sticky; top: 0; z-index: 10; background: #fafafa; border-bottom: 1px solid #e5e6e8; .prmRcTableWrapper__headerRow { display: flex; width: 100%; .prmRcTableWrapper__headerCell { flex-shrink: 0; line-height: 36px; height: 36px; background: #f5faff; font-size: 12px; color: #000000; font-weight: 700; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; text-align: left; padding: 0 12px; &:last-child { border-right: none; } } } } // 表格内容 .prmRcTableWrapper__tableBody { .prmRcTableWrapper__tableRow { display: flex; width: 100%; background: #fff; &:nth-child(even) { background: #fcfcfc; } &[style*='background:'] { background: linear-gradient( 90deg, rgba(212, 247, 231, 0.8), rgba(212, 247, 231, 0) 70% ) !important; color: #222222 !important; &:nth-child(even) { background: linear-gradient( 90deg, rgba(212, 247, 231, 0.8), rgba(212, 247, 231, 0) 70% ) !important; } } &:last-child { border-bottom: none; } .prmRcTableWrapper__tableCell { flex-shrink: 0; line-height: 36px; height: 36px; color: inherit; overflow: hidden; background: none !important; padding: 0 12px; border-bottom: 1px solid #e5e6e8; &:last-child { border-right: none; } // 单元格内容 .prmRcTableWrapper__cellContent { display: block; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; line-height: 36px; color: inherit; } } } } } }

(2)问题版本做参考

使用本段代码,出现问题,给了宽度,但是表格宽度过长,列设置的宽度总和不达表宽度,会出现空白,如下图:

效果图:

// index.tsx import React, { useEffect, useMemo, useState } from 'react'; import { languageConfig } from '@/language/language'; import { rebatePersonnelInformationListChildApi } from '@/api/rebatePersonnelInformation'; import { queryMapIdpValue } from 'services/api'; import { message, Tooltip } from 'choerodon-ui/pro'; import { handleFormatAmount } from '@/public/store'; import TableEmpty from '../TableEmpty'; import { ColumnConfig, StatusItems, TableData } from './store'; import styles from './main.less'; // 表头配置 const tableHeaders = [ { key: 'isIssueRebate', label: '', width: '50px', align: 'center' as const, visible: true, render: (item: TableData) => item.isIssueRebate === 'Y' ? ( <img src={require('@/assets/imgs/icon_success.svg')} alt="选中" style={{ width: '16px', height: '16px' }} /> ) : null, }, { key: 'serialNumber', label: languageConfig('', '序号'), width: '60px', align: 'center' as const, visible: true, render: (item: TableData, index: number) => index + 1, }, { key: 'personalName', label: languageConfig('', '认证人员'), width: '120px', align: 'left' as const, visible: true, tooltip: true, render: (item: TableData) => item.personalName, }, { key: 'certificationLevel', label: languageConfig('', '认证等级'), width: '120px', align: 'left' as const, visible: true, tooltip: true, render: () => '', // 动态计算 }, { key: 'baseNumber', label: languageConfig('', '等级基数'), width: '120px', align: 'left' as const, visible: true, tooltip: true, render: (item: TableData) => handleFormatAmount(item.baseNumber), }, { key: 'evaluationCoefficient', label: languageConfig('', '年度贡献评价'), width: '120px', align: 'left' as const, visible: true, tooltip: true, render: (item: TableData) => item.evaluationCoefficient, }, { key: 'rebateAmount', label: languageConfig('', '返点金额'), width: '120px', align: 'left' as const, visible: true, tooltip: true, render: (item: TableData) => handleFormatAmount(item.rebateAmount), }, ]; const PersonnelList: React.FC<{ id?: number; data?: TableData[] | null; type?: string; // styleClass?: any; }> = ({ id, data, type = '' }) => { const [tableData, setTableData] = useState<TableData[]>([]); // 表格数据 const [hasChecked, setHasChecked] = useState<boolean>(false); // 表格是否有勾选数据 // 认证类型、认证等级 const [certOptions, setCertOptions] = useState<{ type: StatusItems[]; level: StatusItems[]; }>({ type: [], level: [] }); /** 获取值集:'认证类型'、'认证等级' */ useEffect(() => { const fetchCertOptions = async () => { const res = await queryMapIdpValue([ 'PRM_AE_CERTIFICATION_LEVEL_2', 'PRM_AE_CERTIFICATION_LEVEL_3', ]); setCertOptions({ type: res?.[0] || [], level: res?.[1] || [] }); }; fetchCertOptions(); }, []); useEffect(() => { const fetchTableData = async () => { let resData: TableData[] = []; // 优先使用传入的 data,无则调用接口 if (data) { resData = data; } else if (id) { const res = await rebatePersonnelInformationListChildApi({ initialId: id, }); if (res.failed) { message.error(res.message, undefined, undefined, 'top'); return; } resData = res; } // 统一赋值 + 判断勾选状态 setTableData(resData); setHasChecked(resData.some(item => item.isIssueRebate === 'Y')); }; fetchTableData(); }, [id, data]); /** 【渲染】认证等级:认证类型-认证等级 */ const renderCertLevel = (item: TableData) => { const type = certOptions.type.find(v => v.value === item.certificationType)?.meaning || ''; const level = certOptions.level.find(v => v.value === item.certificationLevel) ?.meaning || ''; return `${type}-${level}`; }; /** 【渲染】单元格内容-认证等级 */ const renderCellContent = ( content: React.ReactNode, column: ColumnConfig, ) => { if (column.tooltip && content) { return ( <Tooltip theme="dark" title={content}> <span className={styles.tableCell_cellContent}>{content}</span> </Tooltip> ); } return <span className={styles.tableCell_cellContent}>{content}</span>; }; /** 【获取】根据type计算列宽配置 */ const getTableHeaders = useMemo(() => { if (type === 'look') { // 当 type='look' 时,序号固定50px,其他列均分剩余宽度 return tableHeaders.map((header, index) => { if (header.key === 'serialNumber') { return { ...header, width: '50px', }; } else if (index > 1) { // 从第三列开始(跳过isIssueRebate和serialNumber) // 其他列使用 flex: 1 自动均分 return { ...header, width: 'auto', // 宽度设为auto,使用flex: 1 }; } return header; }); } // 默认使用原始宽度配置 return tableHeaders; }, [type]); /** 【获取】可见的列配置(根据 hasChecked 过滤) */ const visibleColumns = useMemo(() => { const headers = getTableHeaders.map(col => { // 如果是认证等级列,更新render函数 if (col.key === 'certificationLevel') { return { ...col, render: (item: TableData) => renderCertLevel(item), }; } return col; }); return headers.filter(col => { // 如果 hasChecked 为 false,隐藏 isIssueRebate 列 if (col.key === 'isIssueRebate' && !hasChecked) { return false; } return col.visible !== false; }); }, [getTableHeaders, hasChecked, certOptions]); /** 【渲染】单元格样式 */ const getCellStyle = (column: ColumnConfig) => { const style: React.CSSProperties = { textAlign: column.align || 'left', }; if (type === 'look') { if (column.key === 'serialNumber') { // 序号列固定50px style.width = '50px'; style.flex = '0 0 50px'; } else if (column.key !== 'isIssueRebate') { // 其他列(除了isIssueRebate)均分宽度 style.flex = '1'; style.minWidth = '80px'; // 最小宽度防止过窄 } else { // isIssueRebate列保持原样 style.width = column.width; style.flex = `0 0 ${column.width}`; } } else { // 默认模式:使用配置的宽度 style.width = column.width; style.flex = `0 0 ${column.width}`; } return style; }; return ( <> <div className={styles.prmRcTableWrapper}> <div className={styles.prmRcTableWrapper__tableContainer}> {/* 表头 */} <div className={styles.prmRcTableWrapper__tableHeader}> <div className={styles.prmRcTableWrapper__headerRow}> {visibleColumns.map((column, index) => ( <div key={column.key} className={styles.prmRcTableWrapper__headerCell} // style={{ // width: column.width, // flex: `0 0 ${column.width}`, // textAlign: column.align || 'left', // }} style={getCellStyle(column, index)} > {column.label} </div> ))} </div> </div> {/* 表格内容 */} <div className={styles.prmRcTableWrapper__tableBody}> {tableData.map((item, index) => { // 设置的背景是整行的背景 const rowStyle = { // 设置的背景是整行的背景 background: item.isIssueRebate === 'Y' ? 'linear-gradient(90deg, rgba(212, 247, 231, 0.8), rgba(212, 247, 231, 0) 70%)' : undefined, // 设置整行的文字颜色 color: item.isIssueRebate === 'Y' ? '#222222' : '#788295', }; return ( <div key={index} className={styles.prmRcTableWrapper__tableRow} style={rowStyle} > {visibleColumns.map((column, colIndex) => { const content = column.render ? column.render(item, index) : null; return ( <div key={column.key} className={styles.prmRcTableWrapper__tableCell} // style={{ // width: column.width, // flex: `0 0 ${column.width}`, // textAlign: column.align || 'left', // }} style={getCellStyle(column, colIndex)} > {renderCellContent(content, column)} </div> ); })} </div> ); })} </div> </div> </div> </> ); }; export default PersonnelList; // main.less // 表格容器 - BEM风格 .prmRcTableWrapper { width: 100%; background: #fff; display: flex; flex-direction: column; overflow: hidden; position: relative; z-index: 1; // 表格主容器 &__tableContainer { flex: 1; overflow: auto; position: relative; &::-webkit-scrollbar { width: 6px; height: 6px; } &::-webkit-scrollbar-track { background: #f5f5f5; border-radius: 3px; } &::-webkit-scrollbar-thumb { background: #d9d9d9; border-radius: 3px; &:hover { background: #bfbfbf; } } // 表头 .prmRcTableWrapper__tableHeader { position: sticky; top: 0; z-index: 10; background: #fafafa; border-bottom: 1px solid #e5e6e8; min-width: fit-content; .prmRcTableWrapper__headerRow { display: flex; width: 100%; min-width: 100%; .prmRcTableWrapper__headerCell { flex-shrink: 0; line-height: 36px; height: 36px; background: #f5faff; font-size: 12px; color: #000000; font-weight: 700; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; text-align: left; padding: 0 12px; // border-right: 1px solid #E5E6E8; &:last-child { border-right: none; } } } } // 表格内容 .prmRcTableWrapper__tableBody { min-width: 100%; .prmRcTableWrapper__tableRow { display: flex; width: 100%; min-width: 100%; background: #fff; &:nth-child(even) { background: #fcfcfc; } &[style*='background:'] { background: linear-gradient( 90deg, rgba(212, 247, 231, 0.8), rgba(212, 247, 231, 0) 70% ) !important; color: #222222 !important; &:nth-child(even) { background: linear-gradient( 90deg, rgba(212, 247, 231, 0.8), rgba(212, 247, 231, 0) 70% ) !important; } } // &:hover { // background: #f0f9ff !important; // &[style*='background:']:hover { // background: linear-gradient( // 90deg, // rgba(200, 237, 221, 0.9), // rgba(200, 237, 221, 0) 70% // ) !important; // } // } &:last-child { border-bottom: none; } .prmRcTableWrapper__tableCell { flex-shrink: 0; line-height: 36px; height: 36px; color: inherit; overflow: hidden; background: none !important; padding: 0 12px; // border-right: 1px solid #E5E6E8; border-bottom: 1px solid #e5e6e8; &:last-child { border-right: none; } // 单元格内容 .prmRcTableWrapper__cellContent { display: block; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; line-height: 36px; color: inherit; } } } } } }
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2025/12/13 20:35:41

docker一键部署夜莺监控

1、夜莺监控简介夜莺监控&#xff08;Nightingale&#xff09;是一款国产的开源云原生监控系统&#xff0c;它将数据采集、可视化、监控告警、数据分析等多个功能集成于一体&#xff0c;旨在提供一个开箱即用、功能完整的企业级监控解决方案。2、技术架构与工作流程夜莺在架构上…

作者头像 李华
网站建设 2025/12/13 22:15:26

docker一键部署Flatnotes笔记工具

1、flatnotes简介 Flatnotes是一款纯粹、轻量级的自托管Markdown笔记应用。它的核心设计理念是让笔记回归内容本身&#xff0c;通过极简的方式帮助用户管理和记录知识 2、主要功能与特性 双模式编辑器&#xff1a;支持纯Markdown代码编辑和所见即所得&#xff08;WYSIWYG&#…

作者头像 李华
网站建设 2025/12/13 23:40:30

ShawzinBot终极指南:5分钟实现Warframe音乐自动演奏 [特殊字符]

ShawzinBot终极指南&#xff1a;5分钟实现Warframe音乐自动演奏 &#x1f3b5; 【免费下载链接】ShawzinBot Convert a MIDI input to a series of key presses for the Shawzin 项目地址: https://gitcode.com/gh_mirrors/sh/ShawzinBot 想要在Warframe游戏中轻松演奏复…

作者头像 李华
网站建设 2025/12/13 22:12:54

Vue Admin Box实战指南:快速构建现代化管理后台

Vue Admin Box实战指南&#xff1a;快速构建现代化管理后台 【免费下载链接】vue-admin-box vue-admin-box是一个基于Vue.js的开源后台管理框架项目。特点可能包括预设的后台管理功能模块、灵活的布局和主题定制、以及可能的权限管理、数据可视化等特性&#xff0c;旨在简化和加…

作者头像 李华
网站建设 2025/12/13 23:06:29

SwiftUI坐标空间实战指南:从基础到高级布局技巧

SwiftUI坐标空间实战指南&#xff1a;从基础到高级布局技巧 【免费下载链接】IceCubesApp A SwiftUI Mastodon client 项目地址: https://gitcode.com/GitHub_Trending/ic/IceCubesApp 还在为SwiftUI布局定位不准确而烦恼&#xff1f;想要掌握精准的界面元素定位技巧&am…

作者头像 李华