Show · Table

Table

Tables are used to present structured data content, usually accompanied by the ability to manipulate the data (sort, search, paginate...).

How to Use

Into the header. columns And data. DataSource To render.

Please provide a "key" for each data item in the dataSource that is different from the value of the other data items, or use the row Key parameter to specify an attribute name as the primary key, alternative row operation functions such as row selection and expansion of the table.

<script lang="ts">
    import { Table } from '@fibrae/semi-ui-svelte';

    const columns = [
        {
            title: 'Title',
            dataIndex: 'name',
        },
        {
            title: 'Size',
            dataIndex: 'size',
        },
        {
            title: 'Owner',
            dataIndex: 'owner',
        },
        {
            title: 'Update',
            dataIndex: 'updateTime',
        },
    ];
    const data = [
        {
            key: '1',
            name: 'Semi Design design draft.fig',
            size: '2M',
            owner: 'Jiang Pengzhi',
            updateTime: '2020-02-02 05:13',
        },
        {
            key: '2',
            name: 'Semi Design share presentation',
            size: '2M',
            owner: 'Hao Xuan',
            updateTime: '2020-01-17 05:31',
        },
        {
            key: '3',
            name: 'Design document',
            size: '34KB',
            owner: 'Zoey Edwards',
            updateTime: '2020-01-26 11:01',
        },
    ];
</script>

<Table {columns} dataSource={data} pagination={false} />

The Svelte Table has no Table.Column: columns are declared as columns data only, and the custom content of a column goes in the Table-level snippets such as cell and headerCell.

Demos

Basic Table

For tables, the two most basic parameters are dataSource and columns, the former is the data item, the latter is the configuration of each column, both are array types.

标题 大小 交付状态 所有者 更新日期
Semi Design 设计稿.fig
2M
已交付
姜 姜鹏志
2020-02-02 05:13
Semi Design 分享演示文稿
2M
已延期
郝 郝宣
2020-01-17 05:31
设计文档
34KB
待评审
Z Zoey Edwards
2020-01-26 11:01
Code
<script lang="ts">
    import { IconClear, IconComment, IconMore, IconTickCircle } from '@fibrae/semi-icons-svelte';
    import { Avatar, Table, Tag, type ColumnProps } from '@fibrae/semi-ui-svelte';

    interface Row {
        key: string;
        name: string;
        nameIconSrc: string;
        size: string;
        owner: string;
        status: 'success' | 'pending' | 'wait';
        updateTime: string;
        avatarBg: 'grey' | 'red' | 'light-blue';
    }

    const tagConfig = {
        success: { color: 'green', icon: IconTickCircle, text: '已交付' },
        pending: { color: 'pink', icon: IconClear, text: '已延期' },
        wait: { color: 'cyan', icon: IconComment, text: '待评审' },
    } as const;

    const columns: ColumnProps[] = [
        {
            title: '标题',
            dataIndex: 'name',
        },
        {
            title: '大小',
            dataIndex: 'size',
        },
        {
            title: '交付状态',
            dataIndex: 'status',
        },
        {
            title: '所有者',
            dataIndex: 'owner',
        },
        {
            title: '更新日期',
            dataIndex: 'updateTime',
        },
        {
            title: '',
            dataIndex: 'operate',
        },
    ];

    const data: Row[] = [
        {
            key: '1',
            name: 'Semi Design 设计稿.fig',
            nameIconSrc: '/assets/placeholder/app.svg',
            size: '2M',
            owner: '姜鹏志',
            status: 'success',
            updateTime: '2020-02-02 05:13',
            avatarBg: 'grey',
        },
        {
            key: '2',
            name: 'Semi Design 分享演示文稿',
            nameIconSrc: '/assets/docs-icon.png',
            size: '2M',
            owner: '郝宣',
            status: 'pending',
            updateTime: '2020-01-17 05:31',
            avatarBg: 'red',
        },
        {
            key: '3',
            name: '设计文档',
            nameIconSrc: '/assets/docs-icon.png',
            size: '34KB',
            status: 'wait',
            owner: 'Zoey Edwards',
            updateTime: '2020-01-26 11:01',
            avatarBg: 'light-blue',
        },
    ];
</script>

<Table {columns} dataSource={data} pagination={false}>
    {#snippet cell({ column, record, text })}
        {#if column.dataIndex === 'name'}
            <div>
                <Avatar alt="" shape="square" size="small" src={record.nameIconSrc} style="margin-right: 12px" />
                {text}
            </div>
        {:else if column.dataIndex === 'status'}
            {@const config = tagConfig[text as Row['status']]}
            <Tag color={config.color} shape="circle" style="user-select: text">
                {#snippet prefixIcon()}
                    <config.icon />
                {/snippet}
                {config.text}
            </Tag>
        {:else if column.dataIndex === 'owner'}
            <div>
                <Avatar color={record.avatarBg} size="small" style="margin-right: 4px">
                    {typeof text === 'string' && text.slice(0, 1)}
                </Avatar>
                {text}
            </div>
        {:else if column.dataIndex === 'operate'}
            <IconMore />
        {:else}
            {text}
        {/if}
    {/snippet}
</Table>

Row Selection Operation

This feature can be turned on by passing in rowSelection.

  • Click the selection box in the header, and all rows in the dataSource that are not in the state of disabled will be selected. The callback function for selecting all rows is onSelectAll;
  • Clicking on the row selection box will select the current row. Its callback function is onSelect;
  • Shift+clicking a row's selection box (or the row itself with clickRow, or Shift+Space on the selection box) gives every visible row between the last row clicked without Shift (the anchor) and the current row the anchor's selected state, skipping disabled rows. While the anchor stays, another Shift+click replaces the previous range and leaves the selection outside it unchanged. The range stays within the current page; when the anchor is not on the current page or checkRelation is 'related', the click acts as a plain click. A range selection calls onChange once and does not call onSelect;
标题 大小 交付状态 所有者 更新日期
Semi Design 设计稿.fig
2M
已交付
姜 姜鹏志
2020-02-02 05:13
Semi Design 分享演示文稿
2M
已延期
郝 郝宣
2020-01-17 05:31
设计文档
34KB
待评审
Z Zoey Edwards
2020-01-26 11:01
显示第 1 条-第 3 条,共 6 条
Code
<script lang="ts">
    import { IconClear, IconComment, IconMore, IconTickCircle } from '@fibrae/semi-icons-svelte';
    import { Avatar, Table, Tag, type ColumnProps, type RowSelectionProps } from '@fibrae/semi-ui-svelte';

    interface Row {
        key: string;
        name: string;
        nameIconSrc: string;
        size: string;
        owner: string;
        status: 'success' | 'pending' | 'wait';
        updateTime: string;
        avatarBg: 'grey' | 'red' | 'light-blue';
    }

    const tagConfig = {
        success: { color: 'green', icon: IconTickCircle, text: '已交付' },
        pending: { color: 'pink', icon: IconClear, text: '已延期' },
        wait: { color: 'cyan', icon: IconComment, text: '待评审' },
    } as const;

    const columns: ColumnProps[] = [
        {
            title: '标题',
            dataIndex: 'name',
            width: 400,
        },
        {
            title: '大小',
            dataIndex: 'size',
        },
        {
            title: '交付状态',
            dataIndex: 'status',
        },
        {
            title: '所有者',
            dataIndex: 'owner',
        },
        {
            title: '更新日期',
            dataIndex: 'updateTime',
        },
        {
            title: '',
            dataIndex: 'operate',
        },
    ];

    const data: Row[] = [
        {
            key: '1',
            name: 'Semi Design 设计稿.fig',
            nameIconSrc: '/assets/placeholder/app.svg',
            size: '2M',
            owner: '姜鹏志',
            status: 'success',
            updateTime: '2020-02-02 05:13',
            avatarBg: 'grey',
        },
        {
            key: '2',
            name: 'Semi Design 分享演示文稿',
            nameIconSrc: '/assets/docs-icon.png',
            size: '2M',
            owner: '郝宣',
            status: 'pending',
            updateTime: '2020-01-17 05:31',
            avatarBg: 'red',
        },
        {
            key: '3',
            name: '设计文档',
            nameIconSrc: '/assets/docs-icon.png',
            size: '34KB',
            status: 'wait',
            owner: 'Zoey Edwards',
            updateTime: '2020-01-26 11:01',
            avatarBg: 'light-blue',
        },
        {
            key: '4',
            name: 'Semi D2C 设计稿.fig',
            nameIconSrc: '/assets/placeholder/app.svg',
            size: '2M',
            owner: '姜鹏志',
            status: 'wait',
            updateTime: '2020-02-02 05:13',
            avatarBg: 'grey',
        },
        {
            key: '5',
            name: 'Semi D2C 分享演示文稿',
            nameIconSrc: '/assets/docs-icon.png',
            size: '2M',
            owner: '郝宣',
            status: 'pending',
            updateTime: '2020-01-17 05:31',
            avatarBg: 'red',
        },
        {
            key: '6',
            name: 'Semi D2C 设计文档',
            nameIconSrc: '/assets/docs-icon.png',
            size: '34KB',
            status: 'success',
            owner: 'Zoey Edwards',
            updateTime: '2020-01-26 11:01',
            avatarBg: 'light-blue',
        },
    ];

    const rowSelection: RowSelectionProps = {
        getCheckboxProps: (record) => ({
            disabled: record.name === '设计文档', // 这一行不可勾选
            name: record.name,
        }),
        onSelect: (record, selected) => {
            console.log(`select row: ${selected}`, record);
        },
        onSelectAll: (selected, selectedRows) => {
            console.log(`select all rows: ${selected}`, selectedRows);
        },
        onChange: (selectedRowKeys, selectedRows) => {
            console.log(`selectedRowKeys: ${selectedRowKeys}`, 'selectedRows: ', selectedRows);
        },
    };

    const pagination = { pageSize: 3 };
</script>

<Table {columns} dataSource={data} {pagination} {rowSelection}>
    {#snippet cell({ column, record, text })}
        {#if column.dataIndex === 'name'}
            <div>
                <Avatar alt="" shape="square" size="small" src={record.nameIconSrc} style="margin-right: 12px" />
                {text}
            </div>
        {:else if column.dataIndex === 'status'}
            {@const config = tagConfig[text as Row['status']]}
            <Tag color={config.color} shape="circle" style="user-select: text">
                {#snippet prefixIcon()}
                    <config.icon />
                {/snippet}
                {config.text}
            </Tag>
        {:else if column.dataIndex === 'owner'}
            <div>
                <Avatar color={record.avatarBg} size="small" style="margin-right: 4px">
                    {typeof text === 'string' && text.slice(0, 1)}
                </Avatar>
                {text}
            </div>
        {:else if column.dataIndex === 'operate'}
            <IconMore />
        {:else}
            {text}
        {/if}
    {/snippet}
</Table>

Custom Rendering

Users can use the cell snippet of Table to customize the rendering of cells, which is suitable for rendering more complex cell content. cell is called for every data column; tell the columns apart with column.dataIndex or column.key. Render text yourself in the {:else} branch for the columns you do not customize, otherwise their cells are empty.

The parameter of cell is an object containing the following properties:

  • text: the value at column.dataIndex, or the data item itself for a column without dataIndex
  • record: the data item of the current row
  • index: the index of the data item among its siblings
  • column: the configuration of the current column
  • expandIcon: the expand icon as a snippet (when using tree data or expandable rows); render it where it is inserted with {@render expandIcon?.()}
  • isHovering: Whether the current row is in hover state
  • selection, indentText: the selection checkbox and the indent content as snippets, provided only when the column enables useFullRender, see Fully custom rendering

With the isHovering parameter, you can implement interaction effects such as displaying action buttons on mouse hover.

<Table {columns} dataSource={data}>
    {#snippet cell({ column, record, text })}
        {#if column.dataIndex === 'name'}
            <Avatar alt="" size="small" src={record.nameIconSrc} /> {text}
        {:else}
            {text}
        {/if}
    {/snippet}
</Table>
标题 大小 交付状态 所有者 更新日期
Semi Design 设计稿标题可能有点长这时候应该显示 Tooltip.fig 2M
已交付
姜 姜鹏志
2020-02-02 05:13
Semi Design 分享演示文稿 2M
已延期
郝 郝宣
2020-01-17 05:31
设计文档 34KB
待评审
Z Zoey Edwards
2020-01-26 11:01
Semi D2C 设计文档可能也有点长所以也会显示 Tooltip 34KB
已交付
姜 姜琪
2020-01-26 11:01
Code
<script lang="ts">
    import { IconClear, IconComment, IconDelete, IconTickCircle } from '@fibrae/semi-icons-svelte';
    import { IllustrationNoResult, IllustrationNoResultDark } from '@fibrae/semi-illustrations-svelte';
    import { Avatar, Button, Empty, Table, Tag, TypographyText, type ColumnProps } from '@fibrae/semi-ui-svelte';

    interface Row {
        key: string;
        name: string;
        nameIconSrc: string;
        size: string;
        owner: string;
        status: 'success' | 'pending' | 'wait';
        updateTime: string;
        avatarBg: 'grey' | 'red' | 'light-blue' | 'green';
    }

    const tagConfig = {
        success: { color: 'green', icon: IconTickCircle, text: '已交付' },
        pending: { color: 'pink', icon: IconClear, text: '已延期' },
        wait: { color: 'cyan', icon: IconComment, text: '待评审' },
    } as const;

    const raw: Row[] = [
        {
            key: '1',
            name: 'Semi Design 设计稿标题可能有点长这时候应该显示 Tooltip.fig',
            nameIconSrc: '/assets/placeholder/app.svg',
            size: '2M',
            owner: '姜鹏志',
            status: 'success',
            updateTime: '2020-02-02 05:13',
            avatarBg: 'grey',
        },
        {
            key: '2',
            name: 'Semi Design 分享演示文稿',
            nameIconSrc: '/assets/docs-icon.png',
            size: '2M',
            owner: '郝宣',
            status: 'pending',
            updateTime: '2020-01-17 05:31',
            avatarBg: 'red',
        },
        {
            key: '3',
            name: '设计文档',
            nameIconSrc: '/assets/docs-icon.png',
            size: '34KB',
            status: 'wait',
            owner: 'Zoey Edwards',
            updateTime: '2020-01-26 11:01',
            avatarBg: 'light-blue',
        },
        {
            key: '4',
            name: 'Semi D2C 设计文档可能也有点长所以也会显示 Tooltip',
            nameIconSrc: '/assets/docs-icon.png',
            size: '34KB',
            status: 'success',
            owner: '姜琪',
            updateTime: '2020-01-26 11:01',
            avatarBg: 'green',
        },
    ];

    let dataSource = $state.raw(raw);

    const removeRecord = (key: string) => {
        dataSource = dataSource.filter((data) => data.key !== key);
    };
    const resetData = () => {
        dataSource = [...raw];
    };

    const columns: ColumnProps[] = [
        {
            title: '标题',
            dataIndex: 'name',
            width: 400,
        },
        {
            title: '大小',
            dataIndex: 'size',
            width: 150,
        },
        {
            title: '交付状态',
            dataIndex: 'status',
        },
        {
            title: '所有者',
            dataIndex: 'owner',
            width: 300,
        },
        {
            title: '更新日期',
            dataIndex: 'updateTime',
            width: 200,
        },
        {
            title: '',
            dataIndex: 'operate',
        },
    ];
</script>

{#snippet image()}<IllustrationNoResult style="width: 150px; height: 150px" />{/snippet}
{#snippet darkModeImage()}<IllustrationNoResultDark style="width: 150px; height: 150px" />{/snippet}

<div>
    <Button style="margin-bottom: 10px" onclick={resetData}>重置</Button>
    <Table {columns} {dataSource} pagination={false} style="min-height: 350px">
        {#snippet cell({ column, record, text })}
            {#if column.dataIndex === 'name'}
                <span style="display: flex; align-items: center">
                    <Avatar alt="" shape="square" size="small" src={record.nameIconSrc} style="margin-right: 12px" />
                    <!-- Width = the cell's 400px minus the non-text content -->
                    <TypographyText ellipsis={{ showTooltip: true }} style="width: calc(400px - 76px)">{text}</TypographyText>
                </span>
            {:else if column.dataIndex === 'status'}
                {@const config = tagConfig[text as Row['status']]}
                <Tag color={config.color} shape="circle" style="user-select: text">
                    {#snippet prefixIcon()}
                        <config.icon />
                    {/snippet}
                    {config.text}
                </Tag>
            {:else if column.dataIndex === 'owner'}
                <div>
                    <Avatar color={record.avatarBg} size="small" style="margin-right: 4px">
                        {typeof text === 'string' && text.slice(0, 1)}
                    </Avatar>
                    {text}
                </div>
            {:else if column.dataIndex === 'operate'}
                <Button aria-label="删除" theme="borderless" onclick={() => removeRecord(record.key)}>
                    {#snippet icon()}
                        <IconDelete />
                    {/snippet}
                </Button>
            {:else}
                {text}
            {/if}
        {/snippet}
        {#snippet empty()}
            <Empty {darkModeImage} {image}>
                {#snippet description()}搜索无结果{/snippet}
            </Empty>
        {/snippet}
    </Table>
</div>

Table With Pagination

Table paging currently supports two modes: controlled and uncontrolled.

  • In controlled mode, the paging state is passed entirely externally, depending on whether the pagination .currentPage field is passed. In general, the controlled mode is suitable for remotely pulling data and rendering.
  • In uncontrolled mode, Table passes the incoming dataSource length as total to the Pagination component by default, and of course you can also pass a total field to overwrite the value of the Table component, but we do not recommend users to pass this field in uncontrolled paging mode.

Note: The custom pagination.total field passed in uncontrolled conditions is supported.

To fully customize the pagination area, pass the paginationContent snippet. Its pagination argument can be spread on Pagination as is, and the snippet replaces both the page info text and the pager.

标题
交付状态 所有者
Semi Design 设计稿0.fig
0 KB
已交付
姜 姜鹏志
2024-05-06
Semi D2C 设计稿1.fig
5 KB
待评审
郝 郝宣
2024-05-11
Semi Design 设计稿2.fig
10 KB
已交付
姜 姜鹏志
2024-05-16
Semi D2C 设计稿3.fig
15 KB
待评审
郝 郝宣
2024-05-21
Semi Design 设计稿4.fig
20 KB
已交付
姜 姜鹏志
2024-05-26
Semi D2C 设计稿5.fig
25 KB
待评审
郝 郝宣
2024-05-31
Semi Design 设计稿6.fig
30 KB
已交付
姜 姜鹏志
2024-06-05
Semi D2C 设计稿7.fig
35 KB
待评审
郝 郝宣
2024-06-10
Semi Design 设计稿8.fig
40 KB
已交付
姜 姜鹏志
2024-06-15
Semi D2C 设计稿9.fig
45 KB
待评审
郝 郝宣
2024-06-20
显示第 1 条-第 10 条,共 46 条
Code
<script lang="ts">
    import { format } from 'date-fns';
    import { IconClear, IconComment, IconTickCircle } from '@fibrae/semi-icons-svelte';
    import { Avatar, Table, Tag, type ColumnProps, type RowSelectionProps } from '@fibrae/semi-ui-svelte';

    const figmaIconUrl = '/assets/placeholder/app.svg';

    interface Row {
        key: string;
        name: string;
        owner: string;
        size: number;
        status: 'success' | 'wait';
        updateTime: number;
        avatarBg: 'grey' | 'red';
    }

    const columns: ColumnProps[] = [
        {
            title: '标题',
            dataIndex: 'name',
            width: 400,
            filters: [
                {
                    text: 'Semi Design 设计稿',
                    value: 'Semi Design 设计稿',
                },
                {
                    text: 'Semi D2C 设计稿',
                    value: 'Semi D2C 设计稿',
                },
            ],
            onFilter: (value, record) => record.name.includes(value),
        },
        {
            title: '大小',
            dataIndex: 'size',
            sorter: (a, b) => (a.size - b.size > 0 ? 1 : -1),
        },
        {
            title: '交付状态',
            dataIndex: 'status',
        },
        {
            title: '所有者',
            dataIndex: 'owner',
        },
        {
            title: '更新日期',
            dataIndex: 'updateTime',
            sorter: (a, b) => (a.updateTime - b.updateTime > 0 ? 1 : -1),
        },
    ];

    const tagConfig = {
        success: { color: 'green', icon: IconTickCircle, text: '已交付' },
        pending: { color: 'pink', icon: IconClear, text: '已延期' },
        wait: { color: 'cyan', icon: IconComment, text: '待评审' },
    } as const;

    const DAY = 24 * 60 * 60 * 1000;
    const getData = () => {
        const data: Row[] = [];
        for (let i = 0; i < 46; i++) {
            const isSemiDesign = i % 2 === 0;
            const randomNumber = (i * 1000) % 199;
            data.push({
                key: '' + i,
                name: isSemiDesign ? `Semi Design 设计稿${i}.fig` : `Semi D2C 设计稿${i}.fig`,
                owner: isSemiDesign ? '姜鹏志' : '郝宣',
                size: randomNumber,
                status: isSemiDesign ? 'success' : 'wait',
                updateTime: new Date(2024, 4, 6, 12).valueOf() + randomNumber * DAY,
                avatarBg: isSemiDesign ? 'grey' : 'red',
            });
        }
        return data;
    };

    const dataSource = getData();

    const rowSelection: RowSelectionProps = {
        onChange: (selectedRowKeys, selectedRows) => {
            console.log(`selectedRowKeys: ${selectedRowKeys}`, 'selectedRows: ', selectedRows);
        },
        getCheckboxProps: (record) => ({
            disabled: record.name === 'Semi Design 设计稿0.fig', // 这一行不可勾选
            name: record.name,
        }),
    };

    const scroll = { y: 300 };
</script>

<Table {columns} {dataSource} {rowSelection} {scroll}>
    {#snippet cell({ column, text, record })}
        {#if column.dataIndex === 'name'}
            <div>
                <Avatar alt="" shape="square" size="small" src={figmaIconUrl} style="margin-right: 12px" />
                {text}
            </div>
        {:else if column.dataIndex === 'size'}
            {text} KB
        {:else if column.dataIndex === 'status'}
            {@const config = tagConfig[text as Row['status']]}
            <Tag color={config.color} shape="circle" style="user-select: text">
                {#snippet prefixIcon()}
                    <config.icon />
                {/snippet}
                {config.text}
            </Tag>
        {:else if column.dataIndex === 'owner'}
            <div>
                <Avatar color={record.avatarBg} size="small" style="margin-right: 4px">
                    {typeof text === 'string' && text.slice(0, 1)}
                </Avatar>
                {text}
            </div>
        {:else if column.dataIndex === 'updateTime'}
            {format(new Date(text), 'yyyy-MM-dd')}
        {:else}
            {text}
        {/if}
    {/snippet}
</Table>

Pull Remote Data

Under normal circumstances, the data is often not obtained at one time. We will retrieve the data from the interface when clicking on the page number, filter or sort button. In this case, please use Controlled mode To handle pagination. The user needs to pass in the pagination.currentPage field, where the rendering of the pagination component depends entirely on the incoming pagination object.

标题
交付状态 所有者
暂无数据
显示第 1 条-第 5 条,共 46 条
Code
<script lang="ts">
    import { format } from 'date-fns';
    import { IconClear, IconComment, IconTickCircle } from '@fibrae/semi-icons-svelte';
    import { Avatar, Table, Tag, type ColumnProps } from '@fibrae/semi-ui-svelte';

    const DAY = 24 * 60 * 60 * 1000;
    const figmaIconUrl = '/assets/placeholder/app.svg';
    const pageSize = 5;

    interface Row {
        key: string;
        name: string;
        owner: string;
        size: number;
        status: 'success' | 'wait';
        updateTime: number;
        avatarBg: 'grey' | 'red';
    }

    const tagConfig = {
        success: { color: 'green', icon: IconTickCircle, text: '已交付' },
        pending: { color: 'pink', icon: IconClear, text: '已延期' },
        wait: { color: 'cyan', icon: IconComment, text: '待评审' },
    } as const;

    const columns: ColumnProps[] = [
        {
            title: '标题',
            dataIndex: 'name',
            width: 400,
            filters: [
                {
                    text: 'Semi Design 设计稿',
                    value: 'Semi Design 设计稿',
                },
                {
                    text: 'Semi D2C 设计稿',
                    value: 'Semi D2C 设计稿',
                },
            ],
            onFilter: (value, record) => record.name.includes(value),
        },
        {
            title: '大小',
            dataIndex: 'size',
            sorter: (a, b) => (a.size - b.size > 0 ? 1 : -1),
        },
        {
            title: '交付状态',
            dataIndex: 'status',
        },
        {
            title: '所有者',
            dataIndex: 'owner',
        },
        {
            title: '更新日期',
            dataIndex: 'updateTime',
            sorter: (a, b) => (a.updateTime - b.updateTime > 0 ? 1 : -1),
        },
    ];

    const getData = () => {
        const data: Row[] = [];
        for (let i = 0; i < 46; i++) {
            const isSemiDesign = i % 2 === 0;
            const randomNumber = (i * 1000) % 199;
            data.push({
                key: '' + i,
                name: isSemiDesign ? `Semi Design 设计稿${i}.fig` : `Semi D2C 设计稿${i}.fig`,
                owner: isSemiDesign ? '姜鹏志' : '郝宣',
                size: randomNumber,
                status: isSemiDesign ? 'success' : 'wait',
                updateTime: new Date(2024, 4, 6, 12).valueOf() + randomNumber * DAY,
                avatarBg: isSemiDesign ? 'grey' : 'red',
            });
        }
        return data;
    };

    const data = getData();

    const fetchData = (currentPage: number) => {
        return new Promise<Row[]>((res) => {
            setTimeout(() => {
                res(data.slice((currentPage - 1) * pageSize, currentPage * pageSize));
            }, 300);
        });
    };

    let dataSource = $state.raw<Row[]>([]);
    let loading = $state(true);
    let currentPage = $state(1);

    const handlePageChange = (page: number) => {
        loading = true;
        currentPage = page;
    };

    $effect(() => {
        // Drop responses for pages the user has left
        let ignore = false;
        void fetchData(currentPage).then((rows) => {
            if (!ignore) {
                loading = false;
                dataSource = rows;
            }
        });
        return () => {
            ignore = true;
        };
    });
</script>

<Table {columns} {dataSource} {loading} pagination={{ currentPage, pageSize, total: data.length, onPageChange: handlePageChange }}>
    {#snippet cell({ column, record, text })}
        {#if column.dataIndex === 'name'}
            <div>
                <Avatar alt="" shape="square" size="small" src={figmaIconUrl} style="margin-right: 12px" />
                {text}
            </div>
        {:else if column.dataIndex === 'size'}
            {text} KB
        {:else if column.dataIndex === 'status'}
            {@const config = tagConfig[text as Row['status']]}
            <Tag color={config.color} shape="circle" style="user-select: text">
                {#snippet prefixIcon()}
                    <config.icon />
                {/snippet}
                {config.text}
            </Tag>
        {:else if column.dataIndex === 'owner'}
            <div>
                <Avatar color={record.avatarBg} size="small" style="margin-right: 4px">
                    {typeof text === 'string' && text.slice(0, 1)}
                </Avatar>
                {text}
            </div>
        {:else if column.dataIndex === 'updateTime'}
            {format(new Date(text), 'yyyy-MM-dd')}
        {:else}
            {text}
        {/if}
    {/snippet}
</Table>

Fixed Column or Head

You can fix the column by setting the Fixed attribute of the column and scroll.x, and fix the header by setting scroll.y.

  • It is recommended to specify scroll.x as a fixed value or percentage greater than the width of the table. If it is a fixed value, set it to >= the sum of all fixed column widths + the sum of all table column widths.
  • Make sure that all elements inside the table do not affect the height of the cells after rendering (e.g. containing unloaded pictures, etc.). In this case, give the stator element a definite height to ensure that the left and right Fixed columns of cells are not deranged.
  • If the column header is not aligned with the content or there is a column duplication or when the fixed column fails, specify the width width of the fixed column, if still not effective, try to recommend leaving a column with no width to accommodate the elastic layout, or check for ultra-long continuous fields to destroy the layout.
标题
所有者
Semi Design 设计稿0.fig
0 KB
姜 姜鹏志
2024-05-06
Semi D2C 设计稿1.fig
5 KB
郝 郝宣
2024-05-11
Semi Design 设计稿2.fig
10 KB
姜 姜鹏志
2024-05-16
Semi D2C 设计稿3.fig
15 KB
郝 郝宣
2024-05-21
Semi Design 设计稿4.fig
20 KB
姜 姜鹏志
2024-05-26
Semi D2C 设计稿5.fig
25 KB
郝 郝宣
2024-05-31
Semi Design 设计稿6.fig
30 KB
姜 姜鹏志
2024-06-05
Semi D2C 设计稿7.fig
35 KB
郝 郝宣
2024-06-10
Semi Design 设计稿8.fig
40 KB
姜 姜鹏志
2024-06-15
Semi D2C 设计稿9.fig
45 KB
郝 郝宣
2024-06-20
显示第 1 条-第 10 条,共 46 条
Code
<script lang="ts">
    import { format } from 'date-fns';
    import { IconMore } from '@fibrae/semi-icons-svelte';
    import { Avatar, Table, type ColumnProps, type RowSelectionProps } from '@fibrae/semi-ui-svelte';

    const DAY = 24 * 60 * 60 * 1000;
    const figmaIconUrl = '/assets/placeholder/app.svg';

    interface Row {
        key: string;
        name: string;
        owner: string;
        size: number;
        updateTime: number;
        avatarBg: 'grey' | 'red';
    }

    const columns: ColumnProps[] = [
        {
            title: '标题',
            dataIndex: 'name',
            fixed: true,
            width: 250,
            filters: [
                {
                    text: 'Semi Design 设计稿',
                    value: 'Semi Design 设计稿',
                },
                {
                    text: 'Semi D2C 设计稿',
                    value: 'Semi D2C 设计稿',
                },
            ],
            onFilter: (value, record) => record.name.includes(value),
        },
        {
            title: '大小',
            dataIndex: 'size',
            width: 200,
            sorter: (a, b) => (a.size - b.size > 0 ? 1 : -1),
        },
        {
            title: '所有者',
            dataIndex: 'owner',
            width: 200,
        },
        {
            title: '更新日期',
            dataIndex: 'updateTime',
            width: 200,
            sorter: (a, b) => (a.updateTime - b.updateTime > 0 ? 1 : -1),
        },
        {
            title: '',
            dataIndex: 'operate',
            fixed: 'right',
            align: 'center',
            width: 100,
        },
    ];

    const getData = () => {
        const data: Row[] = [];
        for (let i = 0; i < 46; i++) {
            const isSemiDesign = i % 2 === 0;
            const randomNumber = (i * 1000) % 199;
            data.push({
                key: '' + i,
                name: isSemiDesign ? `Semi Design 设计稿${i}.fig` : `Semi D2C 设计稿${i}.fig`,
                owner: isSemiDesign ? '姜鹏志' : '郝宣',
                size: randomNumber,
                updateTime: new Date(2024, 4, 6, 12).valueOf() + randomNumber * DAY,
                avatarBg: isSemiDesign ? 'grey' : 'red',
            });
        }
        return data;
    };

    const dataSource = getData();
    const scroll = { y: 300, x: 1200 };

    const rowSelection: RowSelectionProps = {
        onChange: (selectedRowKeys, selectedRows) => {
            console.log(`selectedRowKeys: ${selectedRowKeys}`, 'selectedRows: ', selectedRows);
        },
        getCheckboxProps: (record) => ({
            disabled: record.name === 'Semi Design 设计稿0.fig', // 这一行不可勾选
            name: record.name,
        }),
        fixed: true,
    };
</script>

<Table {columns} {dataSource} {rowSelection} {scroll}>
    {#snippet cell({ column, text, record })}
        {#if column.dataIndex === 'name'}
            <div>
                <Avatar alt="" shape="square" size="small" src={figmaIconUrl} style="margin-right: 12px" />
                {text}
            </div>
        {:else if column.dataIndex === 'size'}
            {text} KB
        {:else if column.dataIndex === 'owner'}
            <div>
                <Avatar color={record.avatarBg} size="small" style="margin-right: 4px">
                    {text.slice(0, 1)}
                </Avatar>
                {text}
            </div>
        {:else if column.dataIndex === 'updateTime'}
            {format(new Date(text), 'yyyy-MM-dd')}
        {:else if column.dataIndex === 'operate'}
            <IconMore />
        {:else}
            {text}
        {/if}
    {/snippet}
</Table>

The header can be fixed to the top of the page with the sticky property. When passing top, you can control the distance from the scroll container.

After turning on sticky, Table will automatically turn on the fixed layout, and the column width will be determined by column.width. Columns without a given width are automatically assigned by the browser.

Table Header With Sorting and Filtering Function

Filters and sorting controls are integrated inside the table, and users can pass in the sorter display of the sorter open header by passing filters in Column and the filter control display of the onFilter open header.

Note: Be sure to provide a "key" for each row of data that is different from other row values, or use the rowKey parameter to specify a property name as the primary key.

Note: Sorting and filtering columns must set independent "dataIndex"

交付状态 所有者
Semi Design 设计稿0.fig
0 KB
已交付
姜 姜鹏志
2024-05-06
Semi D2C 设计稿1.fig
5 KB
待评审
郝 郝宣
2024-05-11
Semi Design 设计稿2.fig
10 KB
已交付
姜 姜鹏志
2024-05-16
Semi D2C 设计稿3.fig
15 KB
待评审
郝 郝宣
2024-05-21
Semi Design 设计稿4.fig
20 KB
已交付
姜 姜鹏志
2024-05-26
Semi D2C 设计稿5.fig
25 KB
待评审
郝 郝宣
2024-05-31
Semi Design 设计稿6.fig
30 KB
已交付
姜 姜鹏志
2024-06-05
Semi D2C 设计稿7.fig
35 KB
待评审
郝 郝宣
2024-06-10
Semi Design 设计稿8.fig
40 KB
已交付
姜 姜鹏志
2024-06-15
Semi D2C 设计稿9.fig
45 KB
待评审
郝 郝宣
2024-06-20
显示第 1 条-第 10 条,共 46 条
Code
<script lang="ts">
    import { format } from 'date-fns';
    import { IconClear, IconComment, IconTickCircle } from '@fibrae/semi-icons-svelte';
    import { Avatar, Table, Tag, type ColumnProps, type RowSelectionProps } from '@fibrae/semi-ui-svelte';

    const DAY = 24 * 60 * 60 * 1000;
    const figmaIconUrl = '/assets/placeholder/app.svg';

    interface Row {
        key: string;
        name: string;
        owner: string;
        size: number;
        status: 'success' | 'wait';
        updateTime: number;
        avatarBg: 'grey' | 'red';
    }

    const tagConfig = {
        success: { color: 'green', icon: IconTickCircle, text: '已交付' },
        pending: { color: 'pink', icon: IconClear, text: '已延期' },
        wait: { color: 'cyan', icon: IconComment, text: '待评审' },
    } as const;

    const columns: ColumnProps[] = [
        {
            title: '标题',
            dataIndex: 'name',
            width: 400,
            filters: [
                {
                    text: 'Semi Design 设计稿',
                    value: 'Semi Design 设计稿',
                },
                {
                    text: 'Semi D2C 设计稿',
                    value: 'Semi D2C 设计稿',
                },
            ],
            onFilter: (value, record) => record.name.includes(value),
            sorter: (a, b) => (a.name.length - b.name.length > 0 ? 1 : -1),
        },
        {
            title: '大小',
            dataIndex: 'size',
            sorter: (a, b) => (a.size - b.size > 0 ? 1 : -1),
        },
        {
            title: '交付状态',
            dataIndex: 'status',
        },
        {
            title: '所有者',
            dataIndex: 'owner',
        },
        {
            title: '更新日期',
            dataIndex: 'updateTime',
            sorter: (a, b) => (a.updateTime - b.updateTime > 0 ? 1 : -1),
        },
    ];

    const dataSource: Row[] = [];
    for (let i = 0; i < 46; i++) {
        const isSemiDesign = i % 2 === 0;
        const randomNumber = (i * 1000) % 199;
        dataSource.push({
            key: '' + i,
            name: isSemiDesign ? `Semi Design 设计稿${i}.fig` : `Semi D2C 设计稿${i}.fig`,
            owner: isSemiDesign ? '姜鹏志' : '郝宣',
            size: randomNumber,
            status: isSemiDesign ? 'success' : 'wait',
            updateTime: new Date(2024, 4, 6, 12).valueOf() + randomNumber * DAY,
            avatarBg: isSemiDesign ? 'grey' : 'red',
        });
    }

    const rowSelection: RowSelectionProps = {
        onChange: (selectedRowKeys, selectedRows) => {
            console.log(`selectedRowKeys: ${selectedRowKeys}`, 'selectedRows: ', selectedRows);
        },
        getCheckboxProps: (record) => ({
            disabled: record.name === 'Semi Design 设计稿0.fig', // 这一行不可勾选
            name: record.name,
        }),
    };

    const scroll = { y: 300 };
</script>

<Table {columns} {dataSource} {rowSelection} {scroll}>
    {#snippet cell({ column, record, text })}
        {#if column.dataIndex === 'name'}
            <div>
                <Avatar alt="" shape="square" size="small" src={figmaIconUrl} style="margin-right: 12px" />
                {text}
            </div>
        {:else if column.dataIndex === 'size'}
            {text} KB
        {:else if column.dataIndex === 'status'}
            {@const config = tagConfig[text as Row['status']]}
            <Tag color={config.color} shape="circle" style="user-select: text">
                {#snippet prefixIcon()}
                    <config.icon />
                {/snippet}
                {config.text}
            </Tag>
        {:else if column.dataIndex === 'owner'}
            <div>
                <Avatar color={record.avatarBg} size="small" style="margin-right: 4px">
                    {typeof text === 'string' && text.slice(0, 1)}
                </Avatar>
                {text}
            </div>
        {:else if column.dataIndex === 'updateTime'}
            {format(new Date(text), 'yyyy-MM-dd')}
        {:else}
            {text}
        {/if}
    {/snippet}
</Table>

When sorter is a function type, the sortOrder status can be obtained through the third parameter of the function. The function type is (a?: RecordType, b?: RecordType, sortOrder?: 'ascend' | 'descend') => number.

You can control whether to display the sorting tip through the showSortTip attribute, which defaults to false. When the tip is turned on, when there is only sorting function, the sorting prompt will be displayed when the mouse is moved to the table header; in other cases, the sorting prompt will be displayed only when the mouse is moved to the sorting icon.

Note: When using the sortOrder attribute for controlled sorting, since the next sort order cannot be predicted, showSortTip does not take effect and the prompt will not be displayed.

标题
所有者 更新日期
Semi Design 设计稿.fig
3 KB
姜 姜鹏志
2020-02-02
Semi Design 分享演示文稿
未知
郝 郝宣
2020-01-17
设计文档 3
1 KB
Z Zoey Edwards
2020-01-26
设计文档 4
5 KB
Z Zoey Edwards
2020-01-26
设计文档 5
未知
Z Zoey Edwards
2020-01-26
设计文档 6
2 KB
Z Zoey Edwards
2020-01-26
显示第 1 条-第 6 条,共 6 条
Code
<script lang="ts">
    import { format } from 'date-fns';
    import { Avatar, Table, type ColumnProps } from '@fibrae/semi-ui-svelte';

    interface Row {
        key: string;
        name: string;
        nameIconSrc: string;
        size?: number;
        owner: string;
        updateTime: string;
        avatarBg: 'grey' | 'red' | 'light-blue';
    }

    const figmaIconUrl = '/assets/placeholder/app.svg';
    const docIconUrl = '/assets/docs-icon.png';

    const columns: ColumnProps[] = [
        {
            title: '标题',
            dataIndex: 'name',
            width: 400,
        },
        {
            title: '大小',
            dataIndex: 'size',
            sorter: (r1, r2, order) => {
                const a = r1.size;
                const b = r2.size;
                if (typeof a === 'number' && typeof b === 'number') {
                    return a - b; // 数字正常比较大小
                } else if (typeof a === 'undefined') {
                    return order === 'ascend' ? 1 : -1; // undefined 在后面
                } else if (typeof b === 'undefined') {
                    return order === 'ascend' ? -1 : 1; // undefined 在后面
                } else {
                    return 0; // 保持原来的顺序
                }
            },
            showSortTip: true,
        },
        {
            title: '所有者',
            dataIndex: 'owner',
        },
        {
            title: '更新日期',
            dataIndex: 'updateTime',
        },
    ];

    const dataSource: Row[] = [
        {
            key: '1',
            name: 'Semi Design 设计稿.fig',
            nameIconSrc: figmaIconUrl,
            size: 3,
            owner: '姜鹏志',
            updateTime: '2020-02-02 05:13',
            avatarBg: 'grey',
        },
        {
            key: '2',
            name: 'Semi Design 分享演示文稿',
            nameIconSrc: docIconUrl,
            size: undefined,
            owner: '郝宣',
            updateTime: '2020-01-17 05:31',
            avatarBg: 'red',
        },
        {
            key: '3',
            name: '设计文档 3',
            nameIconSrc: docIconUrl,
            size: 1,
            owner: 'Zoey Edwards',
            updateTime: '2020-01-26 11:01',
            avatarBg: 'light-blue',
        },
        {
            key: '4',
            name: '设计文档 4',
            nameIconSrc: docIconUrl,
            size: 5,
            owner: 'Zoey Edwards',
            updateTime: '2020-01-26 11:01',
            avatarBg: 'light-blue',
        },
        {
            key: '5',
            name: '设计文档 5',
            nameIconSrc: docIconUrl,
            size: undefined,
            owner: 'Zoey Edwards',
            updateTime: '2020-01-26 11:01',
            avatarBg: 'light-blue',
        },
        {
            key: '6',
            name: '设计文档 6',
            nameIconSrc: docIconUrl,
            size: 2,
            owner: 'Zoey Edwards',
            updateTime: '2020-01-26 11:01',
            avatarBg: 'light-blue',
        },
    ];
</script>

<Table {columns} {dataSource}>
    {#snippet cell({ column, record, text })}
        {#if column.dataIndex === 'name'}
            <div>
                <Avatar alt="" shape="square" size="small" src={figmaIconUrl} style="margin-right: 12px" />
                {text}
            </div>
        {:else if column.dataIndex === 'size'}
            {text ? `${text} KB` : '未知'}
        {:else if column.dataIndex === 'owner'}
            <div>
                <Avatar color={record.avatarBg} size="small" style="margin-right: 4px">
                    {typeof text === 'string' && text.slice(0, 1)}
                </Avatar>
                {text}
            </div>
        {:else if column.dataIndex === 'updateTime'}
            {format(new Date(text), 'yyyy-MM-dd')}
        {:else}
            {text}
        {/if}
    {/snippet}
</Table>

Custom Header Filtering

If you need to display the filter input box in the table header, render the column title with the headerCell snippet of Table and use it with filteredValue. The title of a column stays plain text; render title in the {:else} branch for the columns you do not customize.

标题
所有者
Semi Design 设计稿0.fig
0 KB
姜 姜鹏志
2024-05-06
Semi Design 设计稿2.fig
10 KB
姜 姜鹏志
2024-05-16
Semi Design 设计稿4.fig
20 KB
姜 姜鹏志
2024-05-26
Semi Design 设计稿6.fig
30 KB
姜 姜鹏志
2024-06-05
Semi Design 设计稿8.fig
40 KB
姜 姜鹏志
2024-06-15
Semi Design 设计稿10.fig
50 KB
姜 姜鹏志
2024-06-25
Semi Design 设计稿12.fig
60 KB
姜 姜鹏志
2024-07-05
Semi Design 设计稿14.fig
70 KB
姜 姜鹏志
2024-07-15
Semi Design 设计稿16.fig
80 KB
姜 姜鹏志
2024-07-25
Semi Design 设计稿18.fig
90 KB
姜 姜鹏志
2024-08-04
显示第 1 条-第 10 条,共 23 条
Code
<script lang="ts">
    import { tick } from 'svelte';
    import { format } from 'date-fns';
    import { Avatar, Button, Input, Space, Table, type ColumnProps } from '@fibrae/semi-ui-svelte';

    const DAY = 24 * 60 * 60 * 1000;
    const figmaIconUrl = '/assets/placeholder/app.svg';

    interface Row {
        key: string;
        name: string;
        owner: string;
        size: number;
        updateTime: number;
        avatarBg: 'grey' | 'red';
    }

    const getData = () => {
        const data: Row[] = [];
        for (let i = 0; i < 46; i++) {
            const isSemiDesign = i % 2 === 0;
            const randomNumber = (i * 1000) % 199;
            data.push({
                key: '' + i,
                name: isSemiDesign ? `Semi Design 设计稿${i}.fig` : `Semi D2C 设计稿${i}.fig`,
                owner: isSemiDesign ? '姜鹏志' : '郝宣',
                size: randomNumber,
                updateTime: new Date(2024, 4, 6, 12).valueOf() + randomNumber * DAY,
                avatarBg: isSemiDesign ? 'grey' : 'red',
            });
        }
        return data;
    };

    const dataSource = getData();
    let nameInput = $state<Input>();

    const columns: ColumnProps[] = [
        {
            title: '标题',
            dataIndex: 'name',
            width: 400,
            onFilter: (value, record) => record.name.includes(value),
            filterDropdown: true,
            onFilterDropdownVisibleChange: () => {
                // the panel is rendered after the visibility changes
                void tick().then(() => nameInput?.focus());
            },
        },
        {
            title: '大小',
            dataIndex: 'size',
            sorter: (a, b) => (a.size - b.size > 0 ? 1 : -1),
        },
        {
            title: '所有者',
            dataIndex: 'owner',
            onFilter: (value, record) => record.owner.includes(value),
            defaultFilteredValue: ['姜鹏志'],
            filterDropdown: true,
        },
        {
            title: '更新日期',
            dataIndex: 'updateTime',
            sorter: (a, b) => (a.updateTime - b.updateTime > 0 ? 1 : -1),
        },
    ];
</script>

<Table {columns} {dataSource}>
    {#snippet filterDropdown({ column, tempFilteredValue, onTempFilteredValueChange, onConfirm, onClear, onClose })}
        {#if column.dataIndex === 'name'}
            <Space align="start" style="padding: 8px" vertical>
                <Input bind:this={nameInput} onChange={(value) => onTempFilteredValueChange(value ? [value] : [])} value={tempFilteredValue?.[0]} />
                <Space>
                    <Button onclick={() => onConfirm({ closeDropdown: true })}>筛选 + 关闭</Button>
                    <Button onclick={() => onClear({ closeDropdown: true })}>清除 + 关闭</Button>
                    <Button onclick={() => onClose()}>直接关闭</Button>
                </Space>
            </Space>
        {:else}
            <Space align="start" style="padding: 8px" vertical>
                <Input onChange={(value) => onTempFilteredValueChange(value ? [value] : [])} value={tempFilteredValue?.[0]} />
                <Space>
                    <Button onclick={() => onConfirm({ closeDropdown: false })}>筛选后不关闭</Button>
                    <Button onclick={() => onClear({ closeDropdown: false })}>清除后不关闭</Button>
                    <Button onclick={() => onClose()}>直接关闭</Button>
                </Space>
            </Space>
        {/if}
    {/snippet}
    {#snippet cell({ column, record, text })}
        {#if column.dataIndex === 'name'}
            <div>
                <Avatar alt="" shape="square" size="small" src={figmaIconUrl} style="margin-right: 12px" />
                {text}
            </div>
        {:else if column.dataIndex === 'size'}
            {text} KB
        {:else if column.dataIndex === 'owner'}
            <div>
                <Avatar color={record.avatarBg} size="small" style="margin-right: 4px">
                    {typeof text === 'string' && text.slice(0, 1)}
                </Avatar>
                {text}
            </div>
        {:else if column.dataIndex === 'updateTime'}
            {format(new Date(text), 'yyyy-MM-dd')}
        {:else}
            {text}
        {/if}
    {/snippet}
</Table>

Custom Filter Rendering

Set filterDropdown of the column to true, then customize the filter panel with the filterDropdown snippet of Table. The snippet is called for every column whose filterDropdown is true; tell the columns apart with column.

You can call onTempFilteredValueChange to store the filter value when the user enters the filter value, and call onConfirm to trigger the actual filtering after the filter value is entered. You can also filter directly through onConfirm({ filteredValue }).

interface TableFilterDropdownSnippetProps {
    /** The configuration of the current column */
    column: ColumnProps;
    /** Filter configuration items, `undefined` when the column has no `filters` */
    filters: TableFilter[] | undefined;
    /** Temporary filter value, the applied filter value when nothing is stored */
    tempFilteredValue: any[] | undefined;
    /** Set temporary filter value */
    onTempFilteredValueChange: (value?: any[]) => void;
    /** `onConfirm` will assign `tempFilteredValue` to `filteredValue` by default and call `onChange`. You can also set the filter value directly by passing in `filteredValue` */
    onConfirm: (options?: { closeDropdown?: boolean; filteredValue?: any[] }) => void;
    /** Clear filter values and temporary filter values */
    onClear: (options?: { closeDropdown?: boolean }) => void;
    /** Close dropdown */
    onClose: () => void;
    /** Restore the temporary filter value to the applied filter value */
    onReset: () => void;
}

The onVisibleChange in filterDropdownProps does not take effect; get the visibility of the filter panel through onFilterDropdownVisibleChange of the column.

标题
交付状态 所有者
Semi Design 设计稿0.fig
0 KB
已交付
姜 姜鹏志
2024-01-25
Semi D2C 首页1.fig
5 KB
待评审
郝 郝宣
2024-01-30
Semi Design 设计稿2.fig
10 KB
已交付
姜 姜鹏志
2024-02-04
Semi D2C 首页3.fig
15 KB
待评审
郝 郝宣
2024-02-09
Semi Design 设计稿4.fig
20 KB
已交付
姜 姜鹏志
2024-02-14
Semi D2C 首页5.fig
25 KB
待评审
郝 郝宣
2024-02-19
Semi Design 设计稿6.fig
30 KB
已交付
姜 姜鹏志
2024-02-24
Semi D2C 首页7.fig
35 KB
待评审
郝 郝宣
2024-02-29
Semi Design 设计稿8.fig
40 KB
已交付
姜 姜鹏志
2024-03-05
Semi D2C 首页9.fig
45 KB
待评审
郝 郝宣
2024-03-10
显示第 1 条-第 10 条,共 46 条
Code
<script lang="ts">
    import { format } from 'date-fns';
    import { IconClear, IconComment, IconTickCircle } from '@fibrae/semi-icons-svelte';
    import { Avatar, Input, Space, Table, Tag, type ColumnProps } from '@fibrae/semi-ui-svelte';

    const DAY = 24 * 60 * 60 * 1000;
    const figmaIconUrl = '/assets/placeholder/app.svg';

    interface Row {
        key: string;
        name: string;
        owner: string;
        size: number;
        status: 'success' | 'wait';
        updateTime: number;
        avatarBg: 'grey' | 'red';
    }

    const tagConfig = {
        success: { color: 'green', icon: IconTickCircle, text: '已交付' },
        pending: { color: 'pink', icon: IconClear, text: '已延期' },
        wait: { color: 'cyan', icon: IconComment, text: '待评审' },
    } as const;

    const getData = () => {
        const data: Row[] = [];
        for (let i = 0; i < 46; i++) {
            const isSemiDesign = i % 2 === 0;
            const randomNumber = (i * 1000) % 199;
            data.push({
                key: '' + i,
                name: isSemiDesign ? `Semi Design 设计稿${i}.fig` : `Semi D2C 首页${i}.fig`,
                owner: isSemiDesign ? '姜鹏志' : '郝宣',
                size: randomNumber,
                status: isSemiDesign ? 'success' : 'wait',
                updateTime: new Date('2024-01-25').valueOf() + randomNumber * DAY,
                avatarBg: isSemiDesign ? 'grey' : 'red',
            });
        }
        return data;
    };

    const dataSource = getData();
    let filteredValue = $state<string[]>([]);

    // composition makes the Input report the value once an IME composition ends
    const handleChange = (value: string) => {
        filteredValue = value ? [value] : [];
    };

    const columns = $derived<ColumnProps[]>([
        {
            title: '标题',
            dataIndex: 'name',
            width: 400,
            onFilter: (value, record) => record.name.includes(value),
            filteredValue,
        },
        {
            title: '大小',
            dataIndex: 'size',
            sorter: (a, b) => (a.size - b.size > 0 ? 1 : -1),
        },
        {
            title: '交付状态',
            dataIndex: 'status',
        },
        {
            title: '所有者',
            dataIndex: 'owner',
        },
        {
            title: '更新日期',
            dataIndex: 'updateTime',
            sorter: (a, b) => (a.updateTime - b.updateTime > 0 ? 1 : -1),
        },
    ]);
</script>

<Table {columns} {dataSource}>
    {#snippet headerCell({ column, title })}
        {#if column.dataIndex === 'name'}
            <Space>
                <span>{title}</span>
                <Input composition onChange={handleChange} placeholder="请输入筛选值" showClear style="width: 200px" />
            </Space>
        {:else}
            {title}
        {/if}
    {/snippet}
    {#snippet cell({ column, record, text })}
        {#if column.dataIndex === 'name'}
            <div>
                <Avatar alt="" shape="square" size="small" src={figmaIconUrl} style="margin-right: 12px" />
                {text}
            </div>
        {:else if column.dataIndex === 'size'}
            {text} KB
        {:else if column.dataIndex === 'status'}
            {@const config = tagConfig[text as Row['status']]}
            <Tag color={config.color} shape="circle" style="user-select: text">
                {#snippet prefixIcon()}
                    <config.icon />
                {/snippet}
                {config.text}
            </Tag>
        {:else if column.dataIndex === 'owner'}
            <div>
                <Avatar color={record.avatarBg} size="small" style="margin-right: 4px">
                    {typeof text === 'string' && text.slice(0, 1)}
                </Avatar>
                {text}
            </div>
        {:else if column.dataIndex === 'updateTime'}
            {format(new Date(text), 'yyyy-MM-dd')}
        {:else}
            {text}
        {/if}
    {/snippet}
</Table>

Filter Confirm Mode

By setting filterConfirmMode='confirm', the filter dropdown panel can support confirm mode. In this mode:

  • Clicking filter items will not take effect immediately, but will be temporarily stored
  • The bottom of the dropdown panel will show "Confirm" and "Reset" buttons
  • Clicking the "Confirm" button will apply the filter conditions and close the dropdown panel
  • Clicking the "Reset" button will restore to the initial state when the dropdown was opened (will not close the panel)

This feature is suitable for scenarios where multiple filter conditions need to be selected before applying them at once, avoiding triggering filtering on every click.

标题
所有者
Semi Design 设计稿0.fig
0 KB
姜 姜鹏志
2024-01-25
Semi Pro 首页1.fig
5 KB
郝 郝宣
2024-01-30
Semi Design 设计稿2.fig
10 KB
姜 姜鹏志
2024-02-04
Semi Pro 首页3.fig
15 KB
郝 郝宣
2024-02-09
Semi Design 设计稿4.fig
20 KB
姜 姜鹏志
2024-02-14
Semi Pro 首页5.fig
25 KB
郝 郝宣
2024-02-19
Semi Design 设计稿6.fig
30 KB
姜 姜鹏志
2024-02-24
Semi Pro 首页7.fig
35 KB
郝 郝宣
2024-02-29
Semi Design 设计稿8.fig
40 KB
姜 姜鹏志
2024-03-05
Semi Pro 首页9.fig
45 KB
郝 郝宣
2024-03-10
显示第 1 条-第 10 条,共 20 条
Code
<script lang="ts">
    import { format } from 'date-fns';
    import { Avatar, Table, type ColumnProps } from '@fibrae/semi-ui-svelte';

    const DAY = 24 * 60 * 60 * 1000;
    const figmaIconUrl = '/assets/placeholder/app.svg';

    interface Row {
        key: string;
        name: string;
        owner: string;
        size: number;
        updateTime: number;
        avatarBg: 'grey' | 'red';
    }

    const columns: ColumnProps[] = [
        {
            title: '标题',
            dataIndex: 'name',
            width: 300,
            filters: [
                {
                    text: 'Semi Design 设计稿',
                    value: 'Semi Design',
                },
                {
                    text: 'Semi Pro 设计稿',
                    value: 'Semi Pro',
                },
            ],
            onFilter: (value, record) => record.name.includes(value),
            filterMultiple: true,
            filterConfirmMode: 'confirm',
        },
        {
            title: '大小',
            dataIndex: 'size',
            sorter: (a, b) => (a.size - b.size > 0 ? 1 : -1),
        },
        {
            title: '所有者',
            dataIndex: 'owner',
            filters: [
                {
                    text: '姜鹏志',
                    value: '姜鹏志',
                },
                {
                    text: '郝宣',
                    value: '郝宣',
                },
            ],
            onFilter: (value, record) => record.owner.includes(value),
            filterMultiple: true,
            filterConfirmMode: 'confirm',
        },
        {
            title: '更新日期',
            dataIndex: 'updateTime',
            sorter: (a, b) => (a.updateTime - b.updateTime > 0 ? 1 : -1),
        },
    ];

    const getData = () => {
        const data: Row[] = [];
        for (let i = 0; i < 20; i++) {
            const isSemiDesign = i % 2 === 0;
            const randomNumber = (i * 1000) % 199;
            data.push({
                key: '' + i,
                name: isSemiDesign ? `Semi Design 设计稿${i}.fig` : `Semi Pro 首页${i}.fig`,
                owner: isSemiDesign ? '姜鹏志' : '郝宣',
                size: randomNumber,
                updateTime: new Date('2024-01-25').valueOf() + randomNumber * DAY,
                avatarBg: isSemiDesign ? 'grey' : 'red',
            });
        }
        return data;
    };

    const dataSource = getData();
</script>

<Table {columns} {dataSource}>
    {#snippet cell({ column, text, record })}
        {#if column.dataIndex === 'name'}
            <div>
                <Avatar alt="" shape="square" size="small" src={figmaIconUrl} style="margin-right: 12px" />
                {text}
            </div>
        {:else if column.dataIndex === 'size'}
            {text} KB
        {:else if column.dataIndex === 'owner'}
            <div>
                <Avatar color={record.avatarBg} size="small" style="margin-right: 4px">
                    {text.slice(0, 1)}
                </Avatar>
                {text}
            </div>
        {:else if column.dataIndex === 'updateTime'}
            {format(new Date(text), 'yyyy-MM-dd')}
        {:else}
            {text}
        {/if}
    {/snippet}
</Table>

Custom Filter Item Rendering

Use the filterDropdownItem snippet of Table to customize the rendering of each filter item. Once given, it replaces every default filter item; tell the columns apart with column. The parameters are:

  • column: ColumnProps the configuration of the current column;
  • text: string | undefined the text of the current filter item;
  • value: any the value of the current filter item;
  • checked: boolean whether the current filter item has been selected;
  • filteredValue: any[] the filter values the menu currently shows; in confirm mode these are the temporary values that have not been applied;
  • level: number the level of the menu the current filter item is in, 0 for the top level menu;
  • filterMultiple: boolean whether the current filter item is multi-select;
  • onChange: (event: MouseEvent) => void toggles the selection of the current filter item, bind it to the onclick of the item.
所有者
Semi Design 设计稿0.fig
0 KB
姜 姜鹏志
2024-05-06
Semi D2C 设计稿1.fig
5 KB
郝 郝宣
2024-05-11
Semi Design 设计稿2.fig
10 KB
姜 姜鹏志
2024-05-16
Semi D2C 设计稿3.fig
15 KB
郝 郝宣
2024-05-21
Semi Design 设计稿4.fig
20 KB
姜 姜鹏志
2024-05-26
Semi D2C 设计稿5.fig
25 KB
郝 郝宣
2024-05-31
Semi Design 设计稿6.fig
30 KB
姜 姜鹏志
2024-06-05
Semi D2C 设计稿7.fig
35 KB
郝 郝宣
2024-06-10
Semi Design 设计稿8.fig
40 KB
姜 姜鹏志
2024-06-15
Semi D2C 设计稿9.fig
45 KB
郝 郝宣
2024-06-20
显示第 1 条-第 10 条,共 46 条
Code
<script lang="ts">
    import { format } from 'date-fns';
    import { Avatar, DropdownItem, Table, type ColumnProps, type RowSelectionProps } from '@fibrae/semi-ui-svelte';

    const DAY = 24 * 60 * 60 * 1000;
    const figmaIconUrl = '/assets/placeholder/app.svg';

    interface Row {
        key: string;
        name: string;
        owner: string;
        size: number;
        updateTime: number;
        avatarBg: 'grey' | 'red';
    }

    const columns: ColumnProps[] = [
        {
            title: '标题',
            dataIndex: 'name',
            width: 400,
            filters: [
                {
                    text: 'Semi Design 设计稿',
                    value: 'Semi Design 设计稿',
                },
                {
                    text: 'Semi D2C 设计稿',
                    value: 'Semi D2C 设计稿',
                },
            ],
            onFilter: (value, record) => record.name.includes(value),
            filterDropdownProps: {
                showTick: true,
            },
            sorter: (a, b) => (a.name.length - b.name.length > 0 ? 1 : -1),
        },
        {
            title: '大小',
            dataIndex: 'size',
            sorter: (a, b) => (a.size - b.size > 0 ? 1 : -1),
        },
        {
            title: '所有者',
            dataIndex: 'owner',
        },
        {
            title: '更新日期',
            dataIndex: 'updateTime',
            sorter: (a, b) => (a.updateTime - b.updateTime > 0 ? 1 : -1),
        },
    ];

    const getData = () => {
        const data: Row[] = [];
        for (let i = 0; i < 46; i++) {
            const isSemiDesign = i % 2 === 0;
            const randomNumber = (i * 1000) % 199;
            data.push({
                key: '' + i,
                name: isSemiDesign ? `Semi Design 设计稿${i}.fig` : `Semi D2C 设计稿${i}.fig`,
                owner: isSemiDesign ? '姜鹏志' : '郝宣',
                size: randomNumber,
                updateTime: new Date(2024, 4, 6, 12).valueOf() + randomNumber * DAY,
                avatarBg: isSemiDesign ? 'grey' : 'red',
            });
        }
        return data;
    };

    const dataSource = getData();

    const rowSelection: RowSelectionProps = {
        onChange: (selectedRowKeys, selectedRows) => {
            console.log(`selectedRowKeys: ${selectedRowKeys}`, 'selectedRows: ', selectedRows);
        },
        getCheckboxProps: (record) => ({
            disabled: record.name === 'Semi Design 设计稿0.fig', // 这一行不可勾选
            name: record.name,
        }),
    };

    const scroll = { y: 300 };
</script>

<Table {columns} {dataSource} {rowSelection} {scroll}>
    {#snippet cell({ column, text, record })}
        {#if column.dataIndex === 'name'}
            <div>
                <Avatar alt="" shape="square" size="small" src={figmaIconUrl} style="margin-right: 12px" />
                {text}
            </div>
        {:else if column.dataIndex === 'size'}
            {text} KB
        {:else if column.dataIndex === 'owner'}
            <div>
                <Avatar color={record.avatarBg} size="small" style="margin-right: 4px">
                    {text.slice(0, 1)}
                </Avatar>
                {text}
            </div>
        {:else if column.dataIndex === 'updateTime'}
            {format(new Date(text), 'yyyy-MM-dd')}
        {:else}
            {text}
        {/if}
    {/snippet}
    {#snippet filterDropdownItem({ text, checked, onChange })}
        <DropdownItem active={checked} onclick={onChange}>{text}</DropdownItem>
    {/snippet}
</Table>

A Table That Can Be Expanded

A Common Table That Can Be Expanded

If you need to render a table that can be expanded, in addition to the expandedRow snippet of Table (parameters record, index and expanded; a row whose output is empty renders no expanded content), you must specify rowKey(by default key), Table will be based on rowKey Gets a row unique identifier. The expanded rows can be controlled with bind:expandedRowKeys; give expandedRowKeys an initial value to start with some rows expanded.

  • If rowKey is a Function, the result of rowKey(record) is taken as the row unique ID
  • If rowKey is a string type, you take record[rowKey] as the row unique ID
标题 大小 所有者 更新日期
Semi Design 设计稿.fig 2M
姜 姜鹏志
2020-02-02 05:13
Semi Design 分享演示文稿 2M
郝 郝宣
2020-01-17 05:31
设计文档 34KB
Z Zoey Edwards
2020-01-26 11:01
Code
<script lang="ts">
    import { IconMore } from '@fibrae/semi-icons-svelte';
    import { Avatar, Descriptions, Table, Tag, type ColumnProps, type RowSelectionProps } from '@fibrae/semi-ui-svelte';

    interface Row {
        key: string;
        name: string;
        nameIconSrc: string;
        size: string;
        owner: string;
        updateTime: string;
        avatarBg: 'grey' | 'red' | 'light-blue';
    }

    const columns: ColumnProps[] = [
        { title: '标题', width: 500, dataIndex: 'name' },
        { title: '大小', dataIndex: 'size' },
        { title: '所有者', dataIndex: 'owner' },
        { title: '更新日期', dataIndex: 'updateTime' },
        { title: '', dataIndex: 'operate' },
    ];

    const data: Row[] = [
        {
            key: '1',
            name: 'Semi Design 设计稿.fig',
            nameIconSrc: '/assets/placeholder/app.svg',
            size: '2M',
            owner: '姜鹏志',
            updateTime: '2020-02-02 05:13',
            avatarBg: 'grey',
        },
        {
            key: '2',
            name: 'Semi Design 分享演示文稿',
            nameIconSrc: '/assets/docs-icon.png',
            size: '2M',
            owner: '郝宣',
            updateTime: '2020-01-17 05:31',
            avatarBg: 'red',
        },
        {
            key: '3',
            name: '设计文档',
            nameIconSrc: '/assets/docs-icon.png',
            size: '34KB',
            owner: 'Zoey Edwards',
            updateTime: '2020-01-26 11:01',
            avatarBg: 'light-blue',
        },
    ];

    const expandData: Record<number, { key: string; value: string }[]> = {
        0: [
            { key: '实际用户数量', value: '1,480,000' },
            { key: '7 天留存', value: '98%' },
            { key: '安全等级', value: '3 级' },
            { key: '垂类标签', value: '设计' },
            { key: '认证状态', value: '未认证' },
        ],
        1: [
            { key: '实际用户数量', value: '2,480,000' },
            { key: '7 天留存', value: '90%' },
            { key: '安全等级', value: '1 级' },
            { key: '垂类标签', value: '模板' },
            { key: '认证状态', value: '已认证' },
        ],
        2: [
            { key: '实际用户数量', value: '2,920,000' },
            { key: '7 天留存', value: '98%' },
            { key: '安全等级', value: '2 级' },
            { key: '垂类标签', value: '文档' },
            { key: '认证状态', value: '已认证' },
        ],
    };

    const rowSelection: RowSelectionProps = {
        getCheckboxProps: (record) => ({
            disabled: record.name === '设计文档', // 这一行不可勾选
            name: record.name,
        }),
        onSelect: (record, selected) => {
            console.log(`select row: ${selected}`, record);
        },
        onSelectAll: (selected, selectedRows) => {
            console.log(`select all rows: ${selected}`, selectedRows);
        },
        onChange: (selectedRowKeys, selectedRows) => {
            console.log(`selectedRowKeys: ${selectedRowKeys}`, 'selectedRows: ', selectedRows);
        },
    };
</script>

<Table {columns} dataSource={data} pagination={false} rowKey="name" {rowSelection}>
    {#snippet cell({ column, text, record })}
        {#if column.dataIndex === 'name'}
            <span>
                <Avatar alt="" shape="square" size="small" src={record.nameIconSrc} style="margin-right: 12px" />
                {text}
            </span>
        {:else if column.dataIndex === 'owner'}
            <div>
                <Avatar color={record.avatarBg} size="small" style="margin-right: 4px">
                    {text.slice(0, 1)}
                </Avatar>
                {text}
            </div>
        {:else if column.dataIndex === 'operate'}
            <IconMore />
        {:else}
            {text}
        {/if}
    {/snippet}
    {#snippet expandedRow({ index })}
        <Descriptions align="justify" data={expandData[index]}>
            {#snippet value({ item })}
                {#if item.key === '垂类标签'}
                    <Tag style="margin: 0">{item.value}</Tag>
                {:else}
                    {item.value}
                {/if}
            {/snippet}
        </Descriptions>
    {/snippet}
</Table>

The Unfold Button Is Rendered In a Separated Column

By default, the expansion button will be rendered in the same cell as the first copy, and you can set hideExpandedColumn to false to render as a separate column:

展开 标题 大小 所有者 更新日期
Semi Design 设计稿.fig 2M
姜 姜鹏志
2020-02-02 05:13
Semi Design 分享演示文稿 2M
郝 郝宣
2020-01-17 05:31
设计文档 34KB
Z Zoey Edwards
2020-01-26 11:01
Code
<script lang="ts">
    import { IconMore } from '@fibrae/semi-icons-svelte';
    import { Avatar, Descriptions, Table, Tag, type ColumnProps, type RowSelectionProps } from '@fibrae/semi-ui-svelte';

    interface Row {
        key: string;
        name: string;
        nameIconSrc: string;
        size: string;
        owner: string;
        updateTime: string;
        avatarBg: 'grey' | 'red' | 'light-blue';
    }

    const columns: ColumnProps[] = [
        { title: '标题', width: 500, dataIndex: 'name' },
        { title: '大小', dataIndex: 'size' },
        { title: '所有者', dataIndex: 'owner' },
        { title: '更新日期', dataIndex: 'updateTime' },
        { title: '', dataIndex: 'operate' },
    ];

    const data: Row[] = [
        {
            key: '1',
            name: 'Semi Design 设计稿.fig',
            nameIconSrc: '/assets/placeholder/app.svg',
            size: '2M',
            owner: '姜鹏志',
            updateTime: '2020-02-02 05:13',
            avatarBg: 'grey',
        },
        {
            key: '2',
            name: 'Semi Design 分享演示文稿',
            nameIconSrc: '/assets/docs-icon.png',
            size: '2M',
            owner: '郝宣',
            updateTime: '2020-01-17 05:31',
            avatarBg: 'red',
        },
        {
            key: '3',
            name: '设计文档',
            nameIconSrc: '/assets/docs-icon.png',
            size: '34KB',
            owner: 'Zoey Edwards',
            updateTime: '2020-01-26 11:01',
            avatarBg: 'light-blue',
        },
    ];

    const expandData: Record<number, { key: string; value: string }[]> = {
        0: [
            { key: '实际用户数量', value: '1,480,000' },
            { key: '7 天留存', value: '98%' },
            { key: '安全等级', value: '3 级' },
            { key: '垂类标签', value: '设计' },
            { key: '认证状态', value: '未认证' },
        ],
        1: [
            { key: '实际用户数量', value: '2,480,000' },
            { key: '7 天留存', value: '90%' },
            { key: '安全等级', value: '1 级' },
            { key: '垂类标签', value: '模板' },
            { key: '认证状态', value: '已认证' },
        ],
        2: [
            { key: '实际用户数量', value: '2,920,000' },
            { key: '7 天留存', value: '98%' },
            { key: '安全等级', value: '2 级' },
            { key: '垂类标签', value: '文档' },
            { key: '认证状态', value: '已认证' },
        ],
    };

    const rowSelection: RowSelectionProps = {
        getCheckboxProps: (record) => ({
            disabled: record.name === '设计文档', // 这一行不可勾选
            name: record.name,
        }),
        onSelect: (record, selected) => {
            console.log(`select row: ${selected}`, record);
        },
        onSelectAll: (selected, selectedRows) => {
            console.log(`select all rows: ${selected}`, selectedRows);
        },
        onChange: (selectedRowKeys, selectedRows) => {
            console.log(`selectedRowKeys: ${selectedRowKeys}`, 'selectedRows: ', selectedRows);
        },
    };
</script>

<Table {columns} dataSource={data} hideExpandedColumn={false} pagination={false} rowKey="name" {rowSelection}>
    {#snippet cell({ column, text, record })}
        {#if column.dataIndex === 'name'}
            <span>
                <Avatar alt="" shape="square" size="small" src={record.nameIconSrc} style="margin-right: 12px" />
                {text}
            </span>
        {:else if column.dataIndex === 'owner'}
            <div>
                <Avatar color={record.avatarBg} size="small" style="margin-right: 4px">
                    {text.slice(0, 1)}
                </Avatar>
                {text}
            </div>
        {:else if column.dataIndex === 'operate'}
            <IconMore />
        {:else}
            {text}
        {/if}
    {/snippet}
    {#snippet expandedRow({ index })}
        <Descriptions align="justify" data={expandData[index]}>
            {#snippet value({ item })}
                {#if item.key === '垂类标签'}
                    <Tag style="margin: 0">{item.value}</Tag>
                {:else}
                    {item.value}
                {/if}
            {/snippet}
        </Descriptions>
    {/snippet}
</Table>

Turn Off Rendering of Expandable Button of a Row

You can pass the row Expandable method, enter the reference as record, and determine whether the return value is false to close the rendering of the expandable button on a row.

展开 标题 大小 所有者 更新日期
Semi Design 设计稿.fig 2M
姜 姜鹏志
2020-02-02 05:13
Semi Design 分享演示文稿 2M
郝 郝宣
2020-01-17 05:31
设计文档 34KB
Z Zoey Edwards
2020-01-26 11:01
Code
<script lang="ts">
    import { IconMore } from '@fibrae/semi-icons-svelte';
    import { Avatar, Descriptions, Table, Tag, type ColumnProps, type RowSelectionProps } from '@fibrae/semi-ui-svelte';

    interface Row {
        key: string;
        name: string;
        nameIconSrc: string;
        size: string;
        owner: string;
        updateTime: string;
        avatarBg: 'grey' | 'red' | 'light-blue';
    }

    const columns: ColumnProps[] = [
        { title: '标题', width: 500, dataIndex: 'name' },
        { title: '大小', dataIndex: 'size' },
        { title: '所有者', dataIndex: 'owner' },
        { title: '更新日期', dataIndex: 'updateTime' },
        { title: '', dataIndex: 'operate' },
    ];

    const data: Row[] = [
        {
            key: '1',
            name: 'Semi Design 设计稿.fig',
            nameIconSrc: '/assets/placeholder/app.svg',
            size: '2M',
            owner: '姜鹏志',
            updateTime: '2020-02-02 05:13',
            avatarBg: 'grey',
        },
        {
            key: '2',
            name: 'Semi Design 分享演示文稿',
            nameIconSrc: '/assets/docs-icon.png',
            size: '2M',
            owner: '郝宣',
            updateTime: '2020-01-17 05:31',
            avatarBg: 'red',
        },
        {
            key: '3',
            name: '设计文档',
            nameIconSrc: '/assets/docs-icon.png',
            size: '34KB',
            owner: 'Zoey Edwards',
            updateTime: '2020-01-26 11:01',
            avatarBg: 'light-blue',
        },
    ];

    const expandData: Record<number, { key: string; value: string }[]> = {
        0: [
            { key: '实际用户数量', value: '1,480,000' },
            { key: '7 天留存', value: '98%' },
            { key: '安全等级', value: '3 级' },
            { key: '垂类标签', value: '设计' },
            { key: '认证状态', value: '未认证' },
        ],
        1: [
            { key: '实际用户数量', value: '2,480,000' },
            { key: '7 天留存', value: '90%' },
            { key: '安全等级', value: '1 级' },
            { key: '垂类标签', value: '模板' },
            { key: '认证状态', value: '已认证' },
        ],
        2: [
            { key: '实际用户数量', value: '2,920,000' },
            { key: '7 天留存', value: '98%' },
            { key: '安全等级', value: '2 级' },
            { key: '垂类标签', value: '文档' },
            { key: '认证状态', value: '已认证' },
        ],
    };

    const rowSelection: RowSelectionProps = {
        getCheckboxProps: (record) => ({
            disabled: record.name === '设计文档', // 这一行不可勾选
            name: record.name,
        }),
        onSelect: (record, selected) => {
            console.log(`select row: ${selected}`, record);
        },
        onSelectAll: (selected, selectedRows) => {
            console.log(`select all rows: ${selected}`, selectedRows);
        },
        onChange: (selectedRowKeys, selectedRows) => {
            console.log(`selectedRowKeys: ${selectedRowKeys}`, 'selectedRows: ', selectedRows);
        },
    };

    const rowExpandable = (record: Record<string, any>) => record.name !== '设计文档';
</script>

<Table {columns} dataSource={data} hideExpandedColumn={false} pagination={false} {rowExpandable} rowKey="name" {rowSelection}>
    {#snippet cell({ column, text, record })}
        {#if column.dataIndex === 'name'}
            <span>
                <Avatar alt="" shape="square" size="small" src={record.nameIconSrc} style="margin-right: 12px" />
                {text}
            </span>
        {:else if column.dataIndex === 'owner'}
            <div>
                <Avatar color={record.avatarBg} size="small" style="margin-right: 4px">
                    {text.slice(0, 1)}
                </Avatar>
                {text}
            </div>
        {:else if column.dataIndex === 'operate'}
            <IconMore />
        {:else}
            {text}
        {/if}
    {/snippet}
    {#snippet expandedRow({ index })}
        <Descriptions align="justify" data={expandData[index]}>
            {#snippet value({ item })}
                {#if item.key === '垂类标签'}
                    <Tag style="margin: 0">{item.value}</Tag>
                {:else}
                    {item.value}
                {/if}
            {/snippet}
        </Descriptions>
    {/snippet}
</Table>

Tree Data Display

The table supports the display of tree data and is automatically displayed as a tree table when there are children fields in the data. If you do not need or use other fields, you can configure it with the childrenRecordName. In addition, you can control the indent width of each layer by setting indent Size.

Note: Be sure to provide a "key" for each row of data that is different from the other row values, or use the rowKey parameter to specify a property name as the primary key.

Simple example of tree data

Key 名称 数据类型 描述 默认值
videos_info 视频信息 Object 对象 视频的元信息 无
text_info 文本信息 Object 对象 视频的元信息 无
显示第 1 条-第 2 条,共 2 条
Code
<script lang="ts">
    import { Table, type ColumnProps } from '@fibrae/semi-ui-svelte';

    interface TreeRow {
        key?: number;
        dataKey: string;
        name: string;
        type: string;
        description: string;
        default: string;
        children?: TreeRow[];
    }

    const columns: ColumnProps[] = [
        {
            title: 'Key',
            dataIndex: 'dataKey',
            key: 'dataKey',
        },
        {
            title: '名称',
            dataIndex: 'name',
            key: 'name',
            width: 200,
        },
        {
            title: '数据类型',
            dataIndex: 'type',
            key: 'type',
            width: 400,
        },
        {
            title: '描述',
            dataIndex: 'description',
            key: 'description',
        },
        {
            title: '默认值',
            dataIndex: 'default',
            key: 'default',
            width: 100,
        },
    ];

    const data: TreeRow[] = [
        {
            key: 1,
            dataKey: 'videos_info',
            name: '视频信息',
            type: 'Object 对象',
            description: '视频的元信息',
            default: '无',
            children: [
                {
                    key: 11,
                    dataKey: 'status',
                    name: '视频状态',
                    type: 'Enum <Integer> 枚举',
                    description: '视频的可见、推荐状态',
                    default: '1',
                },
                {
                    key: 12,
                    dataKey: 'vid',
                    name: '视频 ID',
                    type: 'String 字符串',
                    description: '标识视频的唯一 ID',
                    default: '无',
                    children: [
                        {
                            dataKey: 'video_url',
                            name: '视频地址',
                            type: 'String 字符串',
                            description: '视频的唯一链接',
                            default: '无',
                        },
                    ],
                },
            ],
        },
        {
            key: 2,
            dataKey: 'text_info',
            name: '文本信息',
            type: 'Object 对象',
            description: '视频的元信息',
            default: '无',
            children: [
                {
                    key: 21,
                    dataKey: 'title',
                    name: '视频标题',
                    type: 'String 字符串',
                    description: '视频的标题',
                    default: '无',
                },
                {
                    key: 22,
                    dataKey: 'video_description',
                    name: '视频描述',
                    type: 'String 字符串',
                    description: '视频的描述',
                    default: '无',
                },
            ],
        },
    ];
</script>

<Table {columns} dataSource={data} defaultExpandAllRows />

Rows of Interchangeable Tree Data

You can do row switching by changing the order of the dataSource elements.

Key 名称 数据类型 描述 默认值
videos_info 视频信息 Object 对象 视频的元信息 无
status 视频状态 Enum <Integer> 枚举 视频的可见、推荐状态 1
vid 视频 ID String 字符串 标识视频的唯一 ID 无
text_info 文本信息 Object 对象 视频的元信息 无
title 视频标题 String 字符串 视频的标题 无
video_description 视频描述 String 字符串 视频的描述 无
显示第 1 条-第 2 条,共 2 条
Code
<script lang="ts">
    import { IconArrowDown, IconArrowUp } from '@fibrae/semi-icons-svelte';
    import { Button, Table, type ColumnProps } from '@fibrae/semi-ui-svelte';

    interface TreeRow {
        key?: number;
        dataKey: string;
        name: string;
        type: string;
        description: string;
        default: string;
        children?: TreeRow[];
    }

    const raw: TreeRow[] = [
        {
            key: 1,
            dataKey: 'videos_info',
            name: '视频信息',
            type: 'Object 对象',
            description: '视频的元信息',
            default: '无',
            children: [
                {
                    key: 11,
                    dataKey: 'status',
                    name: '视频状态',
                    type: 'Enum <Integer> 枚举',
                    description: '视频的可见、推荐状态',
                    default: '1',
                },
                {
                    key: 12,
                    dataKey: 'vid',
                    name: '视频 ID',
                    type: 'String 字符串',
                    description: '标识视频的唯一 ID',
                    default: '无',
                    children: [
                        {
                            dataKey: 'video_url',
                            name: '视频地址',
                            type: 'String 字符串',
                            description: '视频的唯一链接',
                            default: '无',
                        },
                    ],
                },
            ],
        },
        {
            key: 2,
            dataKey: 'text_info',
            name: '文本信息',
            type: 'Object 对象',
            description: '视频的元信息',
            default: '无',
            children: [
                {
                    key: 21,
                    dataKey: 'title',
                    name: '视频标题',
                    type: 'String 字符串',
                    description: '视频的标题',
                    default: '无',
                },
                {
                    key: 22,
                    dataKey: 'video_description',
                    name: '视频描述',
                    type: 'String 字符串',
                    description: '视频的描述',
                    default: '无',
                },
            ],
        },
    ];

    const rowKey = 'key';
    const childrenRecordName = 'children';

    const columns: ColumnProps[] = [
        {
            title: 'Key',
            dataIndex: 'dataKey',
            key: 'dataKey',
        },
        {
            title: '名称',
            dataIndex: 'name',
            key: 'name',
            width: 200,
        },
        {
            title: '数据类型',
            dataIndex: 'type',
            key: 'type',
        },
        {
            title: '描述',
            dataIndex: 'description',
            key: 'description',
        },
        {
            title: '默认值',
            dataIndex: 'default',
            key: 'default',
            width: 100,
        },
        {
            key: 'operation',
        },
    ];

    let expandedRowKeys = $state<(string | number)[]>([1, 2]);
    let data = $state.raw(raw);

    const getSameLevelRecords = (key: number | undefined, rows: TreeRow[]): TreeRow[] => {
        if (key == null) {
            return [];
        }
        if (rows.some((item) => item[rowKey] === key)) {
            return rows;
        }
        for (const item of rows) {
            const records = getSameLevelRecords(key, item[childrenRecordName] ?? []);
            if (records.length) {
                return records;
            }
        }
        return [];
    };

    // Returns a copy of the tree in which the record swaps places with its sibling `offset` rows away.
    const moveRecord = (rows: TreeRow[], key: number | undefined, offset: -1 | 1): TreeRow[] => {
        const index = rows.findIndex((item) => item[rowKey] === key);
        if (index === -1) {
            return rows.map((item) => (item[childrenRecordName] ? { ...item, children: moveRecord(item[childrenRecordName], key, offset) } : item));
        }
        const next = [...rows];
        [next[index], next[index + offset]] = [next[index + offset], next[index]];
        return next;
    };

    const canMove = (record: TreeRow, offset: -1 | 1) => {
        const records = getSameLevelRecords(record[rowKey], data);
        const index = records.findIndex((item) => item[rowKey] === record[rowKey]);
        return index >= 0 && index + offset >= 0 && index + offset < records.length;
    };

    const move = (record: TreeRow, offset: -1 | 1) => {
        data = moveRecord(data, record[rowKey], offset);
    };
</script>

<Table bind:expandedRowKeys {childrenRecordName} {columns} dataSource={data} {rowKey}>
    {#snippet cell({ column, record, text })}
        {#if column.key === 'operation'}
            <Button aria-label="上移" disabled={!canMove(record as TreeRow, -1)} onclick={() => move(record as TreeRow, -1)}>
                {#snippet icon()}
                    <IconArrowUp />
                {/snippet}
            </Button>
            <Button aria-label="下移" disabled={!canMove(record as TreeRow, 1)} onclick={() => move(record as TreeRow, 1)}>
                {#snippet icon()}
                    <IconArrowDown />
                {/snippet}
            </Button>
        {:else}
            {text}
        {/if}
    {/snippet}
</Table>

Tree Selection

By default, the row selection of the table is independent. You can simulate a tree selection by defining selectedRowKeys.

Key 名称 数据类型 描述 默认值
videos_info 视频信息 Object 对象 视频的元信息 无
text_info 文本信息 Object 对象 视频的元信息 无
Code
<script lang="ts">
    import { pull, union } from 'es-toolkit';
    import { Table, type ColumnProps, type RowSelectionProps } from '@fibrae/semi-ui-svelte';

    interface TreeRow {
        key: number;
        dataKey: string;
        name: string;
        type: string;
        description: string;
        default: string;
        children?: TreeRow[];
    }

    type RowKey = string | number;

    const childrenRecordName = 'children';
    const rowKey = 'key';

    const columns: ColumnProps[] = [
        {
            title: 'Key',
            dataIndex: 'dataKey',
            key: 'dataKey',
        },
        {
            title: '名称',
            dataIndex: 'name',
            key: 'name',
            width: 200,
        },
        {
            title: '数据类型',
            dataIndex: 'type',
            key: 'type',
            width: 400,
        },
        {
            title: '描述',
            dataIndex: 'description',
            key: 'description',
        },
        {
            title: '默认值',
            dataIndex: 'default',
            key: 'default',
            width: 100,
        },
    ];

    const data: TreeRow[] = [
        {
            key: 1,
            dataKey: 'videos_info',
            name: '视频信息',
            type: 'Object 对象',
            description: '视频的元信息',
            default: '无',
            children: [
                {
                    key: 11,
                    dataKey: 'status',
                    name: '视频状态',
                    type: 'Enum <Integer> 枚举',
                    description: '视频的可见、推荐状态',
                    default: '1',
                },
                {
                    key: 12,
                    dataKey: 'vid',
                    name: '视频 ID',
                    type: 'String 字符串',
                    description: '标识视频的唯一 ID',
                    default: '无',
                    children: [
                        {
                            key: 121,
                            dataKey: 'video_url',
                            name: '视频地址',
                            type: 'String 字符串',
                            description: '视频的唯一链接',
                            default: '无',
                        },
                    ],
                },
            ],
        },
        {
            key: 2,
            dataKey: 'text_info',
            name: '文本信息',
            type: 'Object 对象',
            description: '视频的元信息',
            default: '无',
            children: [
                {
                    key: 21,
                    dataKey: 'title',
                    name: '视频标题',
                    type: 'String 字符串',
                    description: '视频的标题',
                    default: '无',
                },
                {
                    key: 22,
                    dataKey: 'video_description',
                    name: '视频描述',
                    type: 'String 字符串',
                    description: '视频的描述',
                    default: '无',
                },
            ],
        },
    ];

    // Collects the keys of the rows and all their descendants.
    const collectKeys = (rows: TreeRow[]): RowKey[] => rows.flatMap((row) => [row.key, ...collectKeys(row.children ?? [])]);

    let selectedRowKeys = $state<RowKey[]>([]);

    // Selecting a row selects its descendants too, and clearing it clears them.
    const doSelect = (record: Record<string, any>, selected: boolean) => {
        const keys = [record.key, ...collectKeys((record as TreeRow).children ?? [])];
        selectedRowKeys = selected ? union(selectedRowKeys, keys) : pull([...selectedRowKeys], keys);
    };

    const doSelectAll = (selected: boolean) => {
        selectedRowKeys = selected ? collectKeys(data) : [];
    };

    const rowSelection = $derived<RowSelectionProps>({
        selectedRowKeys,
        onSelect: doSelect,
        onSelectAll: doSelectAll,
    });
</script>

<Table {childrenRecordName} {columns} dataSource={data} pagination={false} {rowKey} {rowSelection} />

Tree Selection Association (checkRelation)

By setting rowSelection.checkRelation to 'related', you can enable parent-child node selection association. Selecting a parent node automatically selects all child nodes, and selecting a child node affects the parent node's state (checked/half-checked/unchecked).

Key 名称 数据类型 描述
videos_info 视频信息 Object 视频的元信息
text_info 文本信息 Object 文本的元信息
Code
<script lang="ts">
    import { Table, type ColumnProps } from '@fibrae/semi-ui-svelte';

    interface Row {
        key: string;
        dataKey: string;
        name: string;
        type: string;
        description: string;
        children?: Row[];
    }

    let selectedRowKeys = $state<(string | number)[]>([]);

    const columns: ColumnProps[] = [
        {
            title: 'Key',
            dataIndex: 'dataKey',
            key: 'dataKey',
        },
        {
            title: '名称',
            dataIndex: 'name',
            key: 'name',
            width: 200,
        },
        {
            title: '数据类型',
            dataIndex: 'type',
            key: 'type',
            width: 200,
        },
        {
            title: '描述',
            dataIndex: 'description',
            key: 'description',
        },
    ];

    const data: Row[] = [
        {
            key: '1',
            dataKey: 'videos_info',
            name: '视频信息',
            type: 'Object',
            description: '视频的元信息',
            children: [
                {
                    key: '1-1',
                    dataKey: 'status',
                    name: '视频状态',
                    type: 'Enum',
                    description: '视频的可见状态',
                },
                {
                    key: '1-2',
                    dataKey: 'vid',
                    name: '视频 ID',
                    type: 'String',
                    description: '标识视频的唯一 ID',
                    children: [
                        {
                            key: '1-2-1',
                            dataKey: 'video_url',
                            name: '视频地址',
                            type: 'String',
                            description: '视频的唯一链接',
                        },
                    ],
                },
            ],
        },
        {
            key: '2',
            dataKey: 'text_info',
            name: '文本信息',
            type: 'Object',
            description: '文本的元信息',
            children: [
                {
                    key: '2-1',
                    dataKey: 'title',
                    name: '标题',
                    type: 'String',
                    description: '文本标题',
                },
                {
                    key: '2-2',
                    dataKey: 'description',
                    name: '描述',
                    type: 'String',
                    description: '文本描述',
                },
            ],
        },
    ];

    const rowSelection = $derived({
        selectedRowKeys,
        onChange: (keys: (string | number)[]) => {
            selectedRowKeys = keys;
        },
        checkRelation: 'related' as const,
    });
</script>

<Table {columns} dataSource={data} pagination={false} {rowSelection} />

Custom Row or Cell Events and Properties

  • The incoming onRow / onHeaderRow can define the native event or property of a table or table header line.
  • The incoming Column.onCell / Column.onHeaderCell can define a table or header cell native event or property.

In principle, any attribute or event supported on tr/td/th can be defined. Returned objects use the native Svelte attribute names: class, style (a string), data-*, and lowercase events such as onclick, onmouseenter, onmouseleave, ondblclick and oncontextmenu; colspan and rowspan are lowercase too. For example:

  • The tr of the header defines onmouseenter/onmouseleave
  • The tr of the table defines class
  • The third row of the table defines onclick
标题
所有者
Semi Design 设计稿0.fig
0 KB
姜 姜鹏志
2024-05-06
Semi D2C 设计稿1.fig
5 KB
郝 郝宣
2024-05-11
Semi Design 设计稿2.fig
10 KB
姜 姜鹏志
2024-05-16
Semi D2C 设计稿3.fig
15 KB
郝 郝宣
2024-05-21
Semi Design 设计稿4.fig
20 KB
姜 姜鹏志
2024-05-26
Semi D2C 设计稿5.fig
25 KB
郝 郝宣
2024-05-31
Semi Design 设计稿6.fig
30 KB
姜 姜鹏志
2024-06-05
Semi D2C 设计稿7.fig
35 KB
郝 郝宣
2024-06-10
Semi Design 设计稿8.fig
40 KB
姜 姜鹏志
2024-06-15
Semi D2C 设计稿9.fig
45 KB
郝 郝宣
2024-06-20
显示第 1 条-第 10 条,共 46 条
Code
<script lang="ts">
    import { format } from 'date-fns';
    import { Avatar, Table, type ColumnProps, type TableProps } from '@fibrae/semi-ui-svelte';

    const DAY = 24 * 60 * 60 * 1000;
    const figmaIconUrl = '/assets/placeholder/app.svg';

    interface Row {
        key: string;
        name: string;
        owner: string;
        size: number;
        updateTime: number;
        avatarBg: 'grey' | 'red';
    }

    const columns: ColumnProps[] = [
        {
            title: '标题',
            dataIndex: 'name',
            width: 400,
            filters: [
                {
                    text: 'Semi Design 设计稿',
                    value: 'Semi Design 设计稿',
                },
                {
                    text: 'Semi D2C 设计稿',
                    value: 'Semi D2C 设计稿',
                },
            ],
            onFilter: (value, record) => record.name.includes(value),
        },
        {
            title: '大小',
            dataIndex: 'size',
            sorter: (a, b) => (a.size - b.size > 0 ? 1 : -1),
        },
        {
            title: '所有者',
            dataIndex: 'owner',
        },
        {
            title: '更新日期',
            dataIndex: 'updateTime',
            sorter: (a, b) => (a.updateTime - b.updateTime > 0 ? 1 : -1),
        },
    ];

    const data: Row[] = [];
    for (let i = 0; i < 46; i++) {
        const isSemiDesign = i % 2 === 0;
        const randomNumber = (i * 1000) % 199;
        data.push({
            key: '' + i,
            name: isSemiDesign ? `Semi Design 设计稿${i}.fig` : `Semi D2C 设计稿${i}.fig`,
            owner: isSemiDesign ? '姜鹏志' : '郝宣',
            size: randomNumber,
            updateTime: new Date(2024, 4, 6, 12).valueOf() + randomNumber * DAY,
            avatarBg: isSemiDesign ? 'grey' : 'red',
        });
    }

    const onRow: TableProps['onRow'] = (record, index) => {
        const props = {
            class: 'my-tr-class',
        };
        if (index === 2) {
            return {
                ...props,
                onclick: () => console.log('mouse click: ', record, index),
            };
        }
        return props;
    };

    const onHeaderRow: TableProps['onHeaderRow'] = (headerColumns, index) => ({
        onmouseenter: () => console.log('mouse enter: ', headerColumns, index),
        onmouseleave: () => console.log('mouse leave: ', headerColumns, index),
    });
</script>

<Table {columns} dataSource={data} {onHeaderRow} {onRow}>
    {#snippet cell({ column, record, text })}
        {#if column.dataIndex === 'name'}
            <div>
                <Avatar alt="" shape="square" size="small" src={figmaIconUrl} style="margin-right: 12px" />
                {text}
            </div>
        {:else if column.dataIndex === 'size'}
            {text} KB
        {:else if column.dataIndex === 'owner'}
            <div>
                <Avatar color={record.avatarBg} size="small" style="margin-right: 4px">
                    {typeof text === 'string' && text.slice(0, 1)}
                </Avatar>
                {text}
            </div>
        {:else if column.dataIndex === 'updateTime'}
            {format(new Date(text), 'yyyy-MM-dd')}
        {:else}
            {text}
        {/if}
    {/snippet}
</Table>

Zebra Pattern Table

Use OnRow to set a background color for each row to create a zebra stripped table.

标题 大小 所有者 更新日期
Semi Design 设计稿.fig
2M
姜 姜鹏志
2020-02-02 05:13
Semi Design 分享演示文稿
2M
郝 郝宣
2020-01-17 05:31
设计文档
34KB
Z Zoey Edwards
2020-01-26 11:01
Semi D2C 设计稿.fig
2M
姜 姜鹏志
2020-02-02 05:13
Semi D2C 分享演示文稿
2M
郝 郝宣
2020-01-17 05:31
Semi D2C 设计文档
34KB
Z Zoey Edwards
2020-01-26 11:01
Code
<script lang="ts">
    import { IconMore } from '@fibrae/semi-icons-svelte';
    import { Avatar, Table, type ColumnProps, type TableProps } from '@fibrae/semi-ui-svelte';

    interface Row {
        key: string;
        name: string;
        nameIconSrc: string;
        size: string;
        owner: string;
        updateTime: string;
        avatarBg: 'grey' | 'red' | 'light-blue';
    }

    const columns: ColumnProps[] = [
        {
            title: '标题',
            dataIndex: 'name',
            width: 400,
        },
        {
            title: '大小',
            dataIndex: 'size',
        },
        {
            title: '所有者',
            dataIndex: 'owner',
        },
        {
            title: '更新日期',
            dataIndex: 'updateTime',
        },
        {
            title: '',
            dataIndex: 'operate',
        },
    ];

    const data: Row[] = [
        {
            key: '1',
            name: 'Semi Design 设计稿.fig',
            nameIconSrc: '/assets/placeholder/app.svg',
            size: '2M',
            owner: '姜鹏志',
            updateTime: '2020-02-02 05:13',
            avatarBg: 'grey',
        },
        {
            key: '2',
            name: 'Semi Design 分享演示文稿',
            nameIconSrc: '/assets/docs-icon.png',
            size: '2M',
            owner: '郝宣',
            updateTime: '2020-01-17 05:31',
            avatarBg: 'red',
        },
        {
            key: '3',
            name: '设计文档',
            nameIconSrc: '/assets/docs-icon.png',
            size: '34KB',
            owner: 'Zoey Edwards',
            updateTime: '2020-01-26 11:01',
            avatarBg: 'light-blue',
        },
        {
            key: '4',
            name: 'Semi D2C 设计稿.fig',
            nameIconSrc: '/assets/placeholder/app.svg',
            size: '2M',
            owner: '姜鹏志',
            updateTime: '2020-02-02 05:13',
            avatarBg: 'grey',
        },
        {
            key: '5',
            name: 'Semi D2C 分享演示文稿',
            nameIconSrc: '/assets/docs-icon.png',
            size: '2M',
            owner: '郝宣',
            updateTime: '2020-01-17 05:31',
            avatarBg: 'red',
        },
        {
            key: '6',
            name: 'Semi D2C 设计文档',
            nameIconSrc: '/assets/docs-icon.png',
            size: '34KB',
            owner: 'Zoey Edwards',
            updateTime: '2020-01-26 11:01',
            avatarBg: 'light-blue',
        },
    ];

    const handleRow: TableProps['onRow'] = (_record, index) => (index % 2 === 0 ? { style: 'background: var(--semi-color-fill-0)' } : {});
</script>

<Table {columns} dataSource={data} onRow={handleRow} pagination={false}>
    {#snippet cell({ column, text, record })}
        {#if column.dataIndex === 'name'}
            <div>
                <Avatar alt="" shape="square" size="small" src={record.nameIconSrc} style="margin-right: 12px" />
                {text}
            </div>
        {:else if column.dataIndex === 'owner'}
            <div>
                <Avatar color={record.avatarBg} size="small" style="margin-right: 4px">
                    {typeof text === 'string' ? text.slice(0, 1) : ''}
                </Avatar>
                {text}
            </div>
        {:else if column.dataIndex === 'operate'}
            <IconMore />
        {:else}
            {text}
        {/if}
    {/snippet}
</Table>

Custom Header Style

You can return a specific style (a string) or class from Column.onHeaderCell to customize the header style.
The example below changes the header background color by passing in background-color.

标题 大小 所有者 更新日期
Semi Design 设计稿.fig
2M
姜 姜鹏志
2020-02-02 05:13
Semi Design 分享演示文稿
2M
郝 郝宣
2020-01-17 05:31
设计文档
34KB
Z Zoey Edwards
2020-01-26 11:01
Code
<script lang="ts">
    import { IconMore } from '@fibrae/semi-icons-svelte';
    import { Avatar, Table, type ColumnProps } from '@fibrae/semi-ui-svelte';

    interface Row {
        key: string;
        name: string;
        nameIconSrc: string;
        size: string;
        owner: string;
        updateTime: string;
        avatarBg: 'grey' | 'red' | 'light-blue';
    }

    const onHeaderCell = () => ({
        style: 'background-color: var(--semi-color-fill-0)',
    });

    const columns: ColumnProps[] = [
        {
            title: '标题',
            dataIndex: 'name',
            width: 280,
        },
        {
            title: '大小',
            width: 100,
            dataIndex: 'size',
        },
        {
            title: '所有者',
            dataIndex: 'owner',
            width: 200,
        },
        {
            title: '更新日期',
            width: 300,
            dataIndex: 'updateTime',
        },
        {
            title: '',
            dataIndex: 'operate',
        },
    ].map((column) => ({ ...column, onHeaderCell }));

    const data: Row[] = [
        {
            key: '1',
            name: 'Semi Design 设计稿.fig',
            nameIconSrc: '/assets/placeholder/app.svg',
            size: '2M',
            owner: '姜鹏志',
            updateTime: '2020-02-02 05:13',
            avatarBg: 'grey',
        },
        {
            key: '2',
            name: 'Semi Design 分享演示文稿',
            nameIconSrc: '/assets/docs-icon.png',
            size: '2M',
            owner: '郝宣',
            updateTime: '2020-01-17 05:31',
            avatarBg: 'red',
        },
        {
            key: '3',
            name: '设计文档',
            nameIconSrc: '/assets/docs-icon.png',
            size: '34KB',
            owner: 'Zoey Edwards',
            updateTime: '2020-01-26 11:01',
            avatarBg: 'light-blue',
        },
    ];
</script>

<Table {columns} dataSource={data} pagination={false}>
    {#snippet cell({ column, text, record })}
        {#if column.dataIndex === 'name'}
            <div>
                <Avatar alt="" shape="square" size="small" src={record.nameIconSrc} style="margin-right: 12px" />
                {text}
            </div>
        {:else if column.dataIndex === 'owner'}
            <div>
                <Avatar color={record.avatarBg} size="small" style="margin-right: 4px">
                    {typeof text === 'string' && text.slice(0, 1)}
                </Avatar>
                {text}
            </div>
        {:else if column.dataIndex === 'operate'}
            <IconMore />
        {:else}
            {text}
        {/if}
    {/snippet}
</Table>

Custom Cell Hover Style

Table applies the hover style to the whole row by default. To change it, override the styles with CSS.
In the example below, CSS overrides change the hover background from row highlight to cell highlight.

.component-table-demo-cell-hover-custom {
    .semi-table-tbody {
        // Remove the original row hover style
    .semi-table-row:hover {
            .semi-table-row-cell {
                background-color: transparent;
                background-image: none;
                &.semi-table-cell-fixed-left::before {
                    background-color: transparent;
                }
            }
            // Add a style to a single cell
      .semi-table-row-cell:hover {
                background-color: var(--semi-light-green-1);
            }
        }
    }
}
标题 大小 所有者 更新日期
Semi Design 设计稿.fig
2M
姜 姜鹏志
2020-02-02 05:13
Semi Design 分享演示文稿
2M
郝 郝宣
2020-01-17 05:31
设计文档
34KB
Z Zoey Edwards
2020-01-26 11:01
Code
<script lang="ts">
    import { IconMore } from '@fibrae/semi-icons-svelte';
    import { Avatar, Table, type ColumnProps } from '@fibrae/semi-ui-svelte';

    interface Row {
        key: string;
        name: string;
        nameIconSrc: string;
        size: string;
        owner: string;
        updateTime: string;
        avatarBg: 'grey' | 'red' | 'light-blue';
    }

    const columns: ColumnProps[] = [
        {
            title: '标题',
            dataIndex: 'name',
        },
        {
            title: '大小',
            width: 100,
            dataIndex: 'size',
        },
        {
            title: '所有者',
            dataIndex: 'owner',
            width: 200,
        },
        {
            title: '更新日期',
            width: 300,
            dataIndex: 'updateTime',
        },
        {
            title: '',
            dataIndex: 'operate',
        },
    ];

    const data: Row[] = [
        {
            key: '1',
            name: 'Semi Design 设计稿.fig',
            nameIconSrc: '/assets/placeholder/app.svg',
            size: '2M',
            owner: '姜鹏志',
            updateTime: '2020-02-02 05:13',
            avatarBg: 'grey',
        },
        {
            key: '2',
            name: 'Semi Design 分享演示文稿',
            nameIconSrc: '/assets/docs-icon.png',
            size: '2M',
            owner: '郝宣',
            updateTime: '2020-01-17 05:31',
            avatarBg: 'red',
        },
        {
            key: '3',
            name: '设计文档',
            nameIconSrc: '/assets/docs-icon.png',
            size: '34KB',
            owner: 'Zoey Edwards',
            updateTime: '2020-01-26 11:01',
            avatarBg: 'light-blue',
        },
    ];
</script>

<Table class="component-table-demo-cell-hover-custom" {columns} dataSource={data} pagination={false}>
    {#snippet cell({ column, record, text })}
        {#if column.dataIndex === 'name'}
            <div>
                <Avatar alt="" shape="square" size="small" src={record.nameIconSrc} style="margin-right: 12px" />
                {text}
            </div>
        {:else if column.dataIndex === 'owner'}
            <div>
                <Avatar color={record.avatarBg} size="small" style="margin-right: 4px">
                    {typeof text === 'string' && text.slice(0, 1)}
                </Avatar>
                {text}
            </div>
        {:else if column.dataIndex === 'operate'}
            <IconMore />
        {:else}
            {text}
        {/if}
    {/snippet}
</Table>

Column Ellipsis

Use ellipsis to make cells automatically clipped.

所有者 大小 更新日期
Semi is designed based on FA architecture, and the main logic is extracted as Foundation package, which is easy to migrate to other frameworks Pengzhi Jiang Pengzhi Jiang Pengzhi Jiang Pengzhi Jiang 2M 2020-02-02 05:13
由抖音前端与 UED 团队维护,易于定制的现代化设计系统,帮助设计师与开发者打造高质量产品。由抖音前端与 UED 团队维护,易于定制的现代化设计系统,帮助设计师与开发者打造高质量产品。 郝宣郝宣郝宣郝宣郝宣郝宣郝宣郝宣郝宣郝宣郝宣郝宣郝宣郝宣郝宣郝宣 2M 2020-02-02 05:13
Semi is designed based on FA architecture, and the main logic is extracted as Foundation package, which is easy to migrate to other frameworks Pengzhi Jiang Pengzhi Jiang Pengzhi Jiang Pengzhi Jiang 34KB 2020-02-02 05:13
由抖音前端与 UED 团队维护,易于定制的现代化设计系统,帮助设计师与开发者打造高质量产品。由抖音前端与 UED 团队维护,易于定制的现代化设计系统,帮助设计师与开发者打造高质量产品。 郝宣郝宣郝宣郝宣郝宣郝宣郝宣郝宣郝宣郝宣郝宣郝宣郝宣郝宣郝宣郝宣 34KB 2020-02-02 05:13
Code
<script lang="ts">
    import { IconMore } from '@fibrae/semi-icons-svelte';
    import { Table, type ColumnProps } from '@fibrae/semi-ui-svelte';

    const columns: ColumnProps[] = [
        {
            title: '标题',
            dataIndex: 'name',
            fixed: true,
            width: 250,
            filters: [
                {
                    text: 'Semi Design 设计稿',
                    value: 'Semi Design 设计稿',
                },
                {
                    text: 'Semi D2C 设计稿',
                    value: 'Semi D2C 设计稿',
                },
            ],
            onFilter: (value, record) => record.name.includes(value),
            sorter: (a, b) => (a.name.length - b.name.length > 0 ? 1 : -1),
            ellipsis: true,
        },
        {
            title: '所有者',
            dataIndex: 'owner',
            width: 200,
            ellipsis: true,
        },
        {
            title: '大小',
            dataIndex: 'size',
            ellipsis: true,
        },
        {
            title: '更新日期',
            dataIndex: 'updateTime',
            width: 200,
            ellipsis: true,
        },
        {
            title: '',
            dataIndex: 'operate',
            fixed: 'right',
            align: 'center',
            width: 100,
        },
    ];

    const data = [
        {
            key: '1',
            name: 'Semi is designed based on FA architecture, and the main logic is extracted as Foundation package, which is easy to migrate to other frameworks',
            nameIconSrc: '/assets/placeholder/app.svg',
            size: '2M',
            owner: 'Pengzhi Jiang Pengzhi Jiang Pengzhi Jiang Pengzhi Jiang',
            updateTime: '2020-02-02 05:13',
            avatarBg: 'grey',
        },
        {
            key: '2',
            name: '由抖音前端与 UED 团队维护,易于定制的现代化设计系统,帮助设计师与开发者打造高质量产品。由抖音前端与 UED 团队维护,易于定制的现代化设计系统,帮助设计师与开发者打造高质量产品。',
            nameIconSrc: '/assets/docs-icon.png',
            size: '2M',
            owner: '郝宣郝宣郝宣郝宣郝宣郝宣郝宣郝宣郝宣郝宣郝宣郝宣郝宣郝宣郝宣郝宣',
            updateTime: '2020-02-02 05:13',
            avatarBg: 'red',
        },
        {
            key: '3',
            nameIconSrc: '/assets/docs-icon.png',
            name: 'Semi is designed based on FA architecture, and the main logic is extracted as Foundation package, which is easy to migrate to other frameworks',
            size: '34KB',
            owner: 'Pengzhi Jiang Pengzhi Jiang Pengzhi Jiang Pengzhi Jiang',
            updateTime: '2020-02-02 05:13',
            avatarBg: 'light-blue',
        },
        {
            key: '4',
            name: '由抖音前端与 UED 团队维护,易于定制的现代化设计系统,帮助设计师与开发者打造高质量产品。由抖音前端与 UED 团队维护,易于定制的现代化设计系统,帮助设计师与开发者打造高质量产品。',
            nameIconSrc: '/assets/docs-icon.png',
            size: '34KB',
            owner: '郝宣郝宣郝宣郝宣郝宣郝宣郝宣郝宣郝宣郝宣郝宣郝宣郝宣郝宣郝宣郝宣',
            updateTime: '2020-02-02 05:13',
            avatarBg: 'light-blue',
        },
    ];

    const scroll = { x: 1200 };
</script>

<Table {columns} dataSource={data} pagination={false} {scroll}>
    {#snippet cell({ column, text })}
        {#if column.dataIndex === 'operate'}
            <IconMore />
        {:else}
            {text}
        {/if}
    {/snippet}
</Table>

Set ellipsis.showTitle to false to hide the default native HTML title. With the cell snippet you can customize the content prompt.

所有者 大小 更新日期
Semi is designed based on FA architecture, and the main logic is extracted as Foundation package, which is easy to migrate to other frameworks Pengzhi Jiang Pengzhi Jiang Pengzhi Jiang Pengzhi Jiang 2M 2020-02-02 05:13
由抖音前端与 UED 团队维护,易于定制的现代化设计系统,帮助设计师与开发者打造高质量产品。由抖音前端与 UED 团队维护,易于定制的现代化设计系统,帮助设计师与开发者打造高质量产品。 郝宣郝宣郝宣郝宣郝宣郝宣郝宣郝宣郝宣郝宣郝宣郝宣郝宣郝宣郝宣郝宣 2M 2020-02-02 05:13
Semi is designed based on FA architecture, and the main logic is extracted as Foundation package, which is easy to migrate to other frameworks Pengzhi Jiang 34KB 2020-02-02 05:13
由抖音前端与 UED 团队维护,易于定制的现代化设计系统,帮助设计师与开发者打造高质量产品。由抖音前端与 UED 团队维护,易于定制的现代化设计系统,帮助设计师与开发者打造高质量产品。 郝宣 34KB 2020-02-02 05:13
Code
<script lang="ts">
    import { IconMore } from '@fibrae/semi-icons-svelte';
    import { Table, TypographyText, type ColumnProps } from '@fibrae/semi-ui-svelte';

    const columns: ColumnProps[] = [
        {
            title: '标题',
            dataIndex: 'name',
            fixed: true,
            width: 250,
            filters: [
                {
                    text: 'Semi Design 设计稿',
                    value: 'Semi Design 设计稿',
                },
                {
                    text: 'Semi D2C 设计稿',
                    value: 'Semi D2C 设计稿',
                },
            ],
            onFilter: (value, record) => record.name.includes(value),
            sorter: (a, b) => (a.name.length - b.name.length > 0 ? 1 : -1),
            ellipsis: { showTitle: false },
        },
        {
            title: '所有者',
            dataIndex: 'owner',
            width: 200,
            ellipsis: { showTitle: false },
        },
        {
            title: '大小',
            dataIndex: 'size',
            ellipsis: true,
        },
        {
            title: '更新日期',
            dataIndex: 'updateTime',
            width: 200,
            ellipsis: true,
        },
        {
            title: '',
            dataIndex: 'operate',
            fixed: 'right',
            align: 'center',
            width: 100,
        },
    ];

    const data = [
        {
            key: '1',
            name: 'Semi is designed based on FA architecture, and the main logic is extracted as Foundation package, which is easy to migrate to other frameworks',
            nameIconSrc: '/assets/placeholder/app.svg',
            size: '2M',
            owner: 'Pengzhi Jiang Pengzhi Jiang Pengzhi Jiang Pengzhi Jiang',
            updateTime: '2020-02-02 05:13',
            avatarBg: 'grey',
        },
        {
            key: '2',
            name: '由抖音前端与 UED 团队维护,易于定制的现代化设计系统,帮助设计师与开发者打造高质量产品。由抖音前端与 UED 团队维护,易于定制的现代化设计系统,帮助设计师与开发者打造高质量产品。',
            nameIconSrc: '/assets/docs-icon.png',
            size: '2M',
            owner: '郝宣郝宣郝宣郝宣郝宣郝宣郝宣郝宣郝宣郝宣郝宣郝宣郝宣郝宣郝宣郝宣',
            updateTime: '2020-02-02 05:13',
            avatarBg: 'red',
        },
        {
            key: '3',
            nameIconSrc: '/assets/docs-icon.png',
            name: 'Semi is designed based on FA architecture, and the main logic is extracted as Foundation package, which is easy to migrate to other frameworks',
            size: '34KB',
            owner: 'Pengzhi Jiang',
            updateTime: '2020-02-02 05:13',
            avatarBg: 'light-blue',
        },
        {
            key: '4',
            name: '由抖音前端与 UED 团队维护,易于定制的现代化设计系统,帮助设计师与开发者打造高质量产品。由抖音前端与 UED 团队维护,易于定制的现代化设计系统,帮助设计师与开发者打造高质量产品。',
            nameIconSrc: '/assets/docs-icon.png',
            size: '34KB',
            owner: '郝宣',
            updateTime: '2020-02-02 05:13',
            avatarBg: 'light-blue',
        },
    ];

    const scroll = { x: 1200 };
</script>

<Table {columns} dataSource={data} pagination={false} {scroll}>
    {#snippet cell({ column, text })}
        {#if column.dataIndex === 'name' || column.dataIndex === 'owner'}
            <TypographyText ellipsis={{ showTooltip: true }}>
                {text}
            </TypographyText>
        {:else if column.dataIndex === 'operate'}
            <IconMore />
        {:else}
            {text}
        {/if}
    {/snippet}
</Table>

Resizable Column

Basic Resizable Column

For some columns with more content, you can choose to turn on the telescopic column function and pull and pull at the head to realize the real-time change of column width.

But you need to pay attention to some parameters:

  • resizable is set to true or an object
  • Any column in columns that requires a telescopic function should specify the widthfield (if not passed, the column does not have a telescopic function and its column width will be automatically adjusted by the browser)
  • column.resize can take effect after resizable is enabled. After setting to false, the column no longer supports scaling.
  • A column that has been dragged keeps its dragged width, while columns that have not been dragged follow the width in columns

When used with fixed columns, you need to specify a column without setting the width

It is not recommended to use it with scroll.x at the same time. scroll.x specifies that the table has a width range, and stretching columns will expand the column width, which may cause the table to be misaligned

标题
所有者
操作列
Semi Design 设计稿0.fig
0 KB
姜 姜鹏志
2024-05-06
Semi D2C 设计稿1.fig
5 KB
郝 郝宣
2024-05-11
Semi Design 设计稿2.fig
10 KB
姜 姜鹏志
2024-05-16
Semi D2C 设计稿3.fig
15 KB
郝 郝宣
2024-05-21
Semi Design 设计稿4.fig
20 KB
姜 姜鹏志
2024-05-26
Semi D2C 设计稿5.fig
25 KB
郝 郝宣
2024-05-31
Semi Design 设计稿6.fig
30 KB
姜 姜鹏志
2024-06-05
Semi D2C 设计稿7.fig
35 KB
郝 郝宣
2024-06-10
Semi Design 设计稿8.fig
40 KB
姜 姜鹏志
2024-06-15
Semi D2C 设计稿9.fig
45 KB
郝 郝宣
2024-06-20
显示第 1 条-第 10 条,共 46 条
Code
<script lang="ts">
    import { format } from 'date-fns';
    import { IconMore } from '@fibrae/semi-icons-svelte';
    import { Avatar, Table, type ColumnProps } from '@fibrae/semi-ui-svelte';

    const DAY = 24 * 60 * 60 * 1000;
    const figmaIconUrl = '/assets/placeholder/app.svg';

    interface Row {
        key: string;
        name: string;
        owner: string;
        size: number;
        updateTime: number;
        avatarBg: 'grey' | 'red';
    }

    const columns: ColumnProps[] = [
        {
            title: '标题',
            dataIndex: 'name',
            width: 300,
            resize: false,
            filters: [
                {
                    text: 'Semi Design 设计稿',
                    value: 'Semi Design 设计稿',
                },
                {
                    text: 'Semi D2C 设计稿',
                    value: 'Semi D2C 设计稿',
                },
            ],
            onFilter: (value, record) => record.name.includes(value),
        },
        {
            title: '大小',
            dataIndex: 'size',
            width: 200,
            sorter: (a, b) => (a.size - b.size > 0 ? 1 : -1),
        },
        {
            title: '所有者',
            width: 200,
            dataIndex: 'owner',
        },
        {
            title: '更新日期',
            dataIndex: 'updateTime',
            sorter: (a, b) => (a.updateTime - b.updateTime > 0 ? 1 : -1),
        },
        {
            title: '操作列',
            dataIndex: 'operate',
            fixed: 'right',
            width: 100,
            resize: false,
        },
    ];

    const data: Row[] = [];
    for (let i = 0; i < 46; i++) {
        const isSemiDesign = i % 2 === 0;
        const randomNumber = (i * 1000) % 199;
        data.push({
            key: '' + i,
            name: isSemiDesign ? `Semi Design 设计稿${i}.fig` : `Semi D2C 设计稿${i}.fig`,
            owner: isSemiDesign ? '姜鹏志' : '郝宣',
            size: randomNumber,
            updateTime: new Date(2024, 4, 6, 12).valueOf() + randomNumber * DAY,
            avatarBg: isSemiDesign ? 'grey' : 'red',
        });
    }
</script>

<Table bordered {columns} dataSource={data} resizable>
    {#snippet cell({ column, record, text })}
        {#if column.dataIndex === 'name'}
            <div>
                <Avatar alt="" shape="square" size="small" src={figmaIconUrl} style="margin-right: 12px" />
                {text}
            </div>
        {:else if column.dataIndex === 'size'}
            {text} KB
        {:else if column.dataIndex === 'owner'}
            <div>
                <Avatar color={record.avatarBg} size="small" style="margin-right: 4px">
                    {typeof text === 'string' && text.slice(0, 1)}
                </Avatar>
                {text}
            </div>
        {:else if column.dataIndex === 'updateTime'}
            {format(new Date(text), 'yyyy-MM-dd')}
        {:else if column.dataIndex === 'operate'}
            <IconMore />
        {:else}
            {text}
        {/if}
    {/snippet}
</Table>

Advanced Telescopic Columns

resizable can also be an object, including three event methods:

  • onResize
  • onResizeStart
  • onResizeStop

These three callback will be triggered on changing column width, start changing column width and stop changing column width respectively. Developers can choose to modify the column at this time, such as adding a vertical line effect when dragging, as shown below.

标题
所有者
Semi Design 设计稿0.fig
0 KB
姜 姜鹏志
2024-05-06
Semi D2C 设计稿1.fig
5 KB
郝 郝宣
2024-05-11
Semi Design 设计稿2.fig
10 KB
姜 姜鹏志
2024-05-16
Semi D2C 设计稿3.fig
15 KB
郝 郝宣
2024-05-21
Semi Design 设计稿4.fig
20 KB
姜 姜鹏志
2024-05-26
显示第 1 条-第 5 条,共 46 条
Code
<script lang="ts">
    import { format } from 'date-fns';
    import { addClass, removeClass } from '@fibrae/semi-foundation/utils/classnames';
    import { Avatar, Table, type ColumnProps, type TableResizableProps } from '@fibrae/semi-ui-svelte';

    const DAY = 24 * 60 * 60 * 1000;
    const figmaIconUrl = '/assets/placeholder/app.svg';
    const pagination = { pageSize: 5 };

    interface Row {
        key: string;
        name: string;
        owner: string;
        size: number;
        updateTime: number;
        avatarBg: 'grey' | 'red';
    }

    const columns: ColumnProps[] = [
        {
            title: '标题',
            dataIndex: 'name',
            width: 400,
            filters: [
                {
                    text: 'Semi Design 设计稿',
                    value: 'Semi Design 设计稿',
                },
                {
                    text: 'Semi D2C 设计稿',
                    value: 'Semi D2C 设计稿',
                },
            ],
            onFilter: (value, record) => record.name.includes(value),
        },
        {
            title: '大小',
            dataIndex: 'size',
            width: 200,
            sorter: (a, b) => (a.size - b.size > 0 ? 1 : -1),
        },
        {
            title: '所有者',
            width: 200,
            dataIndex: 'owner',
        },
        {
            title: '更新日期',
            dataIndex: 'updateTime',
            sorter: (a, b) => (a.updateTime - b.updateTime > 0 ? 1 : -1),
        },
    ];

    const data: Row[] = [];
    for (let i = 0; i < 46; i++) {
        const isSemiDesign = i % 2 === 0;
        const randomNumber = (i * 1000) % 199;
        data.push({
            key: '' + i,
            name: isSemiDesign ? `Semi Design 设计稿${i}.fig` : `Semi D2C 设计稿${i}.fig`,
            owner: isSemiDesign ? '姜鹏志' : '郝宣',
            size: randomNumber,
            updateTime: new Date(2024, 4, 6, 12).valueOf() + randomNumber * DAY,
            avatarBg: isSemiDesign ? 'grey' : 'red',
        });
    }

    const resizable: TableResizableProps = {
        onResizeStart: (curColumn) => ({ class: addClass(curColumn.class, 'my-resizing') }),
        onResizeStop: (curColumn) => ({ class: removeClass(curColumn.class, 'my-resizing') }),
    };
</script>

<div id="components-table-demo-resizable-column">
    <Table bordered {columns} dataSource={data} {pagination} {resizable}>
        {#snippet cell({ column, record, text })}
            {#if column.dataIndex === 'name'}
                <div>
                    <Avatar alt="" shape="square" size="small" src={figmaIconUrl} style="margin-right: 12px" />
                    {text}
                </div>
            {:else if column.dataIndex === 'size'}
                {text} KB
            {:else if column.dataIndex === 'owner'}
                <div>
                    <Avatar color={record.avatarBg} size="small" style="margin-right: 4px">
                        {typeof text === 'string' && text.slice(0, 1)}
                    </Avatar>
                    {text}
                </div>
            {:else if column.dataIndex === 'updateTime'}
                {format(new Date(text), 'yyyy-MM-dd')}
            {:else}
                {text}
            {/if}
        {/snippet}
    </Table>
</div>

The CSS style definition used in this example:

#components-table-demo-resizable-column .my-resizing {
    border-right: 2px solid red;
}

#components-table-demo-resizable-column .semi-table-resize-handle:hover {
    background-color: red;
}

#components-table-demo-resizable-column .my-resizing:hover .semi-table-resize-handle {
    background-color: inherit;
}

Drag Sorting

The Svelte components prop takes tag names only, so a component cannot be passed for each row and createSortable of @dnd-kit/svelte cannot be mounted in the row. The example uses native HTML5 drag and drop instead: onRow returns draggable, ondragstart, ondragenter, ondragover and ondragend for every row, and dataSource is reordered by row key while a row is dragged over another. Native drag and drop works with a pointer only; use a sortable library with keyboard support when rows must be sortable from the keyboard.

标题
所有者
Semi Design 设计稿0.fig
0 KB
姜 姜鹏志
2024-05-06
Semi D2C 设计稿1.fig
5 KB
郝 郝宣
2024-05-06
Semi Design 设计稿2.fig
10 KB
姜 姜鹏志
2024-05-06
Semi D2C 设计稿3.fig
15 KB
郝 郝宣
2024-05-06
Semi Design 设计稿4.fig
20 KB
姜 姜鹏志
2024-05-06
Semi D2C 设计稿5.fig
25 KB
郝 郝宣
2024-05-06
Semi Design 设计稿6.fig
30 KB
姜 姜鹏志
2024-05-06
Semi D2C 设计稿7.fig
35 KB
郝 郝宣
2024-05-06
Semi Design 设计稿8.fig
40 KB
姜 姜鹏志
2024-05-06
Semi D2C 设计稿9.fig
45 KB
郝 郝宣
2024-05-06
显示第 1 条-第 10 条,共 46 条
Code
<script lang="ts">
    import { format } from 'date-fns';
    import { Avatar, Table, type ColumnProps, type TableChangeInfo, type TableProps } from '@fibrae/semi-ui-svelte';

    const figmaIconUrl = '/assets/placeholder/app.svg';

    interface Row {
        key: string;
        name: string;
        owner: string;
        size: number;
        updateTime: number;
        avatarBg: 'grey' | 'red';
    }

    const getData = () => {
        const data: Row[] = [];
        for (let i = 0; i < 46; i++) {
            const isSemiDesign = i % 2 === 0;
            const randomNumber = (i * 1000) % 199;
            data.push({
                key: '' + i,
                name: isSemiDesign ? `Semi Design 设计稿${i}.fig` : `Semi D2C 设计稿${i}.fig`,
                owner: isSemiDesign ? '姜鹏志' : '郝宣',
                size: randomNumber,
                updateTime: new Date(2024, 4, 6, 12).valueOf() + randomNumber,
                avatarBg: isSemiDesign ? 'grey' : 'red',
            });
        }
        return data;
    };

    const pageSize = 10;
    let dataSource = $state.raw(getData());
    let pageNum = $state(1);
    const pageData = $derived(dataSource.slice((pageNum - 1) * pageSize, pageNum * pageSize));

    const columns: ColumnProps[] = [
        {
            title: '标题',
            dataIndex: 'name',
            width: 400,
            filters: [
                {
                    text: 'Semi Design 设计稿',
                    value: 'Semi Design 设计稿',
                },
                {
                    text: 'Semi D2C 设计稿',
                    value: 'Semi D2C 设计稿',
                },
            ],
            onFilter: (value, record) => record.name.includes(value),
        },
        {
            title: '大小',
            dataIndex: 'size',
            sorter: (a, b) => (a.size - b.size > 0 ? 1 : -1),
        },
        {
            title: '所有者',
            dataIndex: 'owner',
        },
        {
            title: '更新日期',
            dataIndex: 'updateTime',
            sorter: (a, b) => (a.updateTime - b.updateTime > 0 ? 1 : -1),
        },
    ];

    // HTML5 drag and drop through the onRow attributes: Table takes only a tag name for its rows, so a sortable library
    // cannot wrap each row in a component of its own. Known limit: pointer only; use a sortable library with keyboard
    // support when rows must be sortable from the keyboard.
    let draggingKey = $state<string | null>(null);

    const moveTo = (targetKey: string) => {
        const from = dataSource.findIndex((row) => row.key === draggingKey);
        const to = dataSource.findIndex((row) => row.key === targetKey);
        if (from < 0 || to < 0 || from === to) {
            return;
        }
        const next = [...dataSource];
        const [moved] = next.splice(from, 1);
        next.splice(to, 0, moved);
        dataSource = next;
    };

    const onRow: TableProps['onRow'] = (record) => ({
        draggable: true,
        style: `cursor: grab; opacity: ${draggingKey === record.key ? 0.5 : 1}`,
        ondragstart: () => (draggingKey = record.key),
        ondragenter: (event) => {
            event.preventDefault();
            moveTo(record.key);
        },
        ondragover: (event) => event.preventDefault(),
        ondragend: () => (draggingKey = null),
    });

    const handleChange = ({ pagination }: TableChangeInfo) => {
        if (pagination?.currentPage) {
            pageNum = pagination.currentPage;
        }
    };
</script>

<Table {columns} dataSource={pageData} onChange={handleChange} {onRow} pagination={{ currentPage: pageNum, pageSize, total: dataSource.length }} rowKey="key">
    {#snippet cell({ column, record, text })}
        {#if column.dataIndex === 'name'}
            <div>
                <Avatar alt="" shape="square" size="small" src={figmaIconUrl} style="margin-right: 12px" />
                {text}
            </div>
        {:else if column.dataIndex === 'size'}
            {text} KB
        {:else if column.dataIndex === 'owner'}
            <div>
                <Avatar color={record.avatarBg} size="small" style="margin-right: 4px">
                    {typeof text === 'string' && text.slice(0, 1)}
                </Avatar>
                {text}
            </div>
        {:else if column.dataIndex === 'updateTime'}
            {format(new Date(text), 'yyyy-MM-dd')}
        {:else}
            {text}
        {/if}
    {/snippet}
</Table>

Table Grouping

For some tables whose data needs to be displayed in groups, you can pass in groupBy to define the grouping rules, and use the groupSection snippet (parameters groupKey and group) to define the rendering of the grouping table header; the column configuration of the only column of a group row is returned by groupSectionColumn.

Note: Be sure to provide a "key" for each row of data that is different from other row values, or use the rowKey parameter to specify a property name as the primary key.

标题
所有者
根据文件大小分组 100 KB
根据文件大小分组 112 KB
根据文件大小分组 105 KB
根据文件大小分组 117 KB
显示第 1 条-第 10 条,共 46 条
Code
<script lang="ts">
    import { format } from 'date-fns';
    import { Avatar, Table, type ColumnProps } from '@fibrae/semi-ui-svelte';

    const DAY = 24 * 60 * 60 * 1000;
    const figmaIconUrl = '/assets/placeholder/app.svg';

    interface Row {
        key: string;
        name: string;
        owner: string;
        size: number;
        updateTime: number;
        avatarBg: 'grey' | 'red';
    }

    const columns: ColumnProps[] = [
        {
            title: '标题',
            dataIndex: 'name',
            width: 400,
            filters: [
                {
                    text: 'Semi Design 设计稿',
                    value: 'Semi Design 设计稿',
                },
                {
                    text: 'Semi D2C 设计稿',
                    value: 'Semi D2C 设计稿',
                },
            ],
            onFilter: (value, record) => record.name.includes(value),
        },
        {
            title: '大小',
            dataIndex: 'size',
            sorter: (a, b) => (a.size - b.size > 0 ? 1 : -1),
        },
        {
            title: '所有者',
            dataIndex: 'owner',
        },
        {
            title: '更新日期',
            dataIndex: 'updateTime',
            sorter: (a, b) => (a.updateTime - b.updateTime > 0 ? 1 : -1),
        },
    ];

    const getData = () => {
        const data: Row[] = [];
        for (let i = 0; i < 46; i++) {
            const isSemiDesign = i % 2 === 0;
            const randomNumber = ((i * 1000) % 19) + 100;
            data.push({
                key: '' + i,
                name: isSemiDesign ? `Semi Design 设计稿${i}.fig` : `Semi D2C 设计稿${i}.fig`,
                owner: isSemiDesign ? '姜鹏志' : '郝宣',
                size: randomNumber,
                updateTime: new Date(2024, 4, 6, 12).valueOf() + randomNumber * DAY,
                avatarBg: isSemiDesign ? 'grey' : 'red',
            });
        }
        return data;
    };

    const data = getData();
    const scroll = { y: 480 };
    const rowKey = (record: Record<string, any>) => `${record.owner.toLowerCase()}-${record.name.toLowerCase()}`;
    const onGroupedRow = (group: { groupKey: string | number; records: Record<string, any>[] }, index: number) => ({
        onclick: () => {
            console.log(`Grouped row clicked: `, group, index);
        },
    });
</script>

<div style="padding: 20px 0px">
    <!-- clickGroupedRowToExpand: a click anywhere on the group row expands it -->
    <Table clickGroupedRowToExpand {columns} dataSource={data} groupBy="size" {onGroupedRow} {rowKey} {scroll}>
        {#snippet groupSection({ groupKey })}
            <strong>根据文件大小分组 {groupKey} KB</strong>
        {/snippet}
        {#snippet cell({ column, text, record })}
            {#if column.dataIndex === 'name'}
                <div>
                    <Avatar alt="" shape="square" size="small" src={figmaIconUrl} style="margin-right: 12px" />
                    {text}
                </div>
            {:else if column.dataIndex === 'size'}
                {text} KB
            {:else if column.dataIndex === 'owner'}
                <div>
                    <Avatar color={record.avatarBg} size="small" style="margin-right: 4px">
                        {typeof text === 'string' && text.slice(0, 1)}
                    </Avatar>
                    {text}
                </div>
            {:else if column.dataIndex === 'updateTime'}
                {format(new Date(text), 'yyyy-MM-dd')}
            {:else}
                {text}
            {/if}
        {/snippet}
    </Table>
</div>

Virtualized Table

Virtualization can be used for scenes that need to render large-scale data. You can enable this feature by configuring the virtualized parameter. have to be aware of is:

  • Must pass scroll.y (number); the table width comes from style.width, or from scroll.x when it is not set. style is a style string that lands on the root element, so write the width with a unit, such as 'width: 750px';
  • Need to pass the height of each line virtualized.itemSize (when not passed, the default row height follows size: 53 for default, 45 for middle and 37 for small, and a group header row is as high as a normal row), can be of the following types:
    • number
    • (index, { sectionRow?: boolean, expandedRow?: boolean }) => number
  • Table grouping virtualization is supported.
  • The virtualized object also takes overscanCount: the number of rows rendered above and below the visible area, 2 by default

Get the instance of Table (type TableHandle) with bind:this and call scrollTo(offset) and scrollToItem(index, align), where align is auto (default), smart, center, start or end; calling them on a table without virtualization prints a warning in development.

The following is an example of rendering 1000 pieces of data.

标题
所有者
Code
<script lang="ts">
    import { format } from 'date-fns';
    import { Avatar, Button, Table, type ColumnProps, type TableHandle } from '@fibrae/semi-ui-svelte';

    const DAY = 24 * 60 * 60 * 1000;

    interface Row {
        key: string;
        name: string;
        owner: string;
        size: number;
        updateTime: number;
        avatarBg: 'grey' | 'red';
    }

    const columns: ColumnProps[] = [
        {
            title: '标题',
            dataIndex: 'name',
            width: 200,
            fixed: true,
            filters: [
                {
                    text: 'Semi Design 设计稿',
                    value: 'Semi Design 设计稿',
                },
                {
                    text: 'Semi D2C 设计稿',
                    value: 'Semi D2C 设计稿',
                },
            ],
            onFilter: (value, record) => record.name.includes(value),
        },
        {
            title: '大小',
            dataIndex: 'size',
            width: 150,
            sorter: (a, b) => (a.size - b.size > 0 ? 1 : -1),
        },
        {
            title: '所有者',
            dataIndex: 'owner',
        },
        {
            title: '更新日期',
            dataIndex: 'updateTime',
            fixed: 'right',
            width: 150,
            sorter: (a, b) => (a.updateTime - b.updateTime > 0 ? 1 : -1),
        },
    ];

    const getData = () => {
        const data: Row[] = [];
        for (let i = 0; i < 1000; i++) {
            const isSemiDesign = i % 2 === 0;
            const randomNumber = (i * 1000) % 199;
            data.push({
                key: '' + i,
                name: isSemiDesign ? `Semi Design 设计稿${i}.fig` : `Semi D2C 设计稿${i}.fig`,
                owner: isSemiDesign ? '姜鹏志' : '郝宣',
                size: randomNumber,
                updateTime: new Date(2024, 4, 6, 12).valueOf() + randomNumber * DAY,
                avatarBg: isSemiDesign ? 'grey' : 'red',
            });
        }
        return data;
    };

    const data = getData();
    const scroll = { y: 400, x: 900 };
    const style = 'width: 750px; margin: 0 auto';
    let table = $state<TableHandle>();
</script>

<Button onclick={() => table?.scrollToItem(100)}>Scroll to 100</Button>
<Table bind:this={table} {columns} dataSource={data} pagination={false} {scroll} {style} virtualized>
    {#snippet cell({ column, text, record })}
        {#if column.dataIndex === 'size'}
            {text} KB
        {:else if column.dataIndex === 'owner'}
            <div>
                <Avatar color={record.avatarBg} size="small" style="margin-right: 4px">
                    {typeof text === 'string' ? text.slice(0, 1) : ''}
                </Avatar>
                {text}
            </div>
        {:else if column.dataIndex === 'updateTime'}
            {format(new Date(text), 'yyyy-MM-dd')}
        {:else}
            {text}
        {/if}
    {/snippet}
</Table>

Infinite Scroll

Based on the virtualization feature, we can achieve infinite scroll loading data by passing in onScroll. Its argument holds only scrollOffset, and it fires only when the vertical scroll offset changes; keep the previous offset yourself and compare when you need the scroll direction.

标题
所有者
Code
<script lang="ts">
    import { format } from 'date-fns';
    import { Avatar, Table, type ColumnProps, type TableVirtualizedProps, type TableVirtualizedScrollArgs } from '@fibrae/semi-ui-svelte';

    const DAY = 24 * 60 * 60 * 1000;

    interface Row {
        key: string;
        name: string;
        owner: string;
        size: number;
        updateTime: number;
        avatarBg: 'grey' | 'red';
    }

    const columns: ColumnProps[] = [
        {
            title: '标题',
            dataIndex: 'name',
            width: 200,
            fixed: true,
            filters: [
                {
                    text: 'Semi Design 设计稿',
                    value: 'Semi Design 设计稿',
                },
                {
                    text: 'Semi D2C 设计稿',
                    value: 'Semi D2C 设计稿',
                },
            ],
            onFilter: (value, record) => record.name.includes(value),
        },
        {
            title: '大小',
            dataIndex: 'size',
            width: 150,
            sorter: (a, b) => (a.size - b.size > 0 ? 1 : -1),
        },
        {
            title: '所有者',
            dataIndex: 'owner',
        },
        {
            title: '更新日期',
            dataIndex: 'updateTime',
            fixed: 'right',
            width: 150,
            sorter: (a, b) => (a.updateTime - b.updateTime > 0 ? 1 : -1),
        },
    ];

    const pageSize = 20; // 每次加载 20 条
    const getPage = (start: number) => {
        const page: Row[] = [];
        for (let i = start; i < start + pageSize; i++) {
            const isSemiDesign = i % 2 === 0;
            const randomNumber = (i * 1000) % 199;
            page.push({
                key: '' + i,
                name: isSemiDesign ? `Semi Design 设计稿${i}.fig` : `Semi D2C 设计稿${i}.fig`,
                owner: isSemiDesign ? '姜鹏志' : '郝宣',
                size: randomNumber,
                updateTime: new Date(2024, 4, 6, 12).valueOf() + randomNumber * DAY,
                avatarBg: isSemiDesign ? 'grey' : 'red',
            });
        }
        return page;
    };

    let data = $state.raw(getPage(0));
    const scroll = { y: 600, x: 1000 };
    const style = 'width: 750px; margin: 0 auto';
    const itemSize = 56;
    const virtualized: TableVirtualizedProps = { itemSize };

    const loadMore = () => {
        data = [...data, ...getPage(data.length)];
    };

    // onScroll reports the offset only, so the scroll direction is kept here
    let lastOffset = 0;
    const onScroll = ({ scrollOffset }: TableVirtualizedScrollArgs) => {
        const forward = scrollOffset > lastOffset;
        lastOffset = scrollOffset;
        if (forward && scrollOffset >= (data.length - Math.ceil(Number(scroll.y) / itemSize) * 1.5) * itemSize) {
            loadMore();
        }
    };
</script>

<Table {columns} dataSource={data} {onScroll} pagination={false} {scroll} {style} {virtualized}>
    {#snippet cell({ column, text, record })}
        {#if column.dataIndex === 'name'}
            <div>{text}</div>
        {:else if column.dataIndex === 'size'}
            {text} KB
        {:else if column.dataIndex === 'owner'}
            <div>
                <Avatar color={record.avatarBg} size="small" style="margin-right: 4px">
                    {typeof text === 'string' && text.slice(0, 1)}
                </Avatar>
                {text}
            </div>
        {:else if column.dataIndex === 'updateTime'}
            {format(new Date(text), 'yyyy-MM-dd')}
        {:else}
            {text}
        {/if}
    {/snippet}
</Table>

Controlled Dynamic Tables

固定表头:
隐藏表头:
显示标题:
显示底部:
固定列:
显示选择列:
显示加载状态:
无数据:
开启排序功能:
开启过滤功能:
开启行展开功能:
展开当前所有行:
显示边框:
开启列伸缩功能:
分页控件:
标题
所有者
暂无数据
显示第 1 条-第 8 条,共 46 条
Code
<script lang="ts">
    import { onMount } from 'svelte';
    import { format } from 'date-fns';
    import { Avatar, Button, ButtonGroup, Switch, Table, type ColumnProps, type RowSelectionProps, type TableChangeInfo, type TablePaginationPosition, type TablePaginationProps, type TableScroll } from '@fibrae/semi-ui-svelte';

    interface Row {
        key: string;
        name: string;
        owner: string;
        size: number;
        updateTime: number;
        avatarBg: string;
        description?: string;
    }

    interface PageState extends TablePaginationProps {
        currentPage: number;
        pageSize: number;
        total: number;
    }

    interface FetchResult {
        dataSource: Row[];
        pagination: PageState;
        sorter: ColumnProps;
        filters: ColumnProps[];
    }

    interface SwitchItem {
        text: string;
        /** Absent: the switch keeps its own state */
        checked?: () => boolean;
        onChange: (checked: boolean) => void;
    }

    const DAY = 24 * 60 * 60 * 1000;
    const figmaIconUrl = '/assets/placeholder/app.svg';
    const dataTotalSize = 46;
    const wrapStyle = 'margin-bottom: 15px; display: flex; justify-content: space-around; flex-wrap: wrap';
    const switchStyle = 'display: inline-flex; align-items: center; margin: 5px';

    const initialColumns: ColumnProps[] = [
        {
            title: '标题',
            dataIndex: 'name',
            width: 400,
            filters: [
                {
                    text: 'Semi Design 设计稿',
                    value: 'Semi Design 设计稿',
                },
                {
                    text: 'Semi D2C 设计稿',
                    value: 'Semi D2C 设计稿',
                },
            ],
            onFilter: (value, record) => record.name.includes(value),
        },
        {
            title: '大小',
            dataIndex: 'size',
            sorter: (a, b) => (a.size - b.size > 0 ? 1 : -1),
        },
        {
            title: '所有者',
            dataIndex: 'owner',
        },
        {
            title: '更新日期',
            dataIndex: 'updateTime',
            sorter: (a, b) => (a.updateTime - b.updateTime > 0 ? 1 : -1),
        },
    ];

    const allData = Array.from({ length: dataTotalSize }, (_v, i): Row => {
        const isSemiDesign = i % 2 === 0;
        const randomNumber = (i * 1000) % 199;
        return {
            key: '' + i,
            name: isSemiDesign ? `Semi Design 设计稿${i}.fig` : `Semi D2C 设计稿${i}.fig`,
            owner: isSemiDesign ? '姜鹏志' : '郝宣',
            size: randomNumber,
            updateTime: new Date(2024, 4, 6, 12).valueOf() + randomNumber * DAY,
            avatarBg: isSemiDesign ? 'grey' : 'red',
            description: `这是第 ${i} 份设计稿的详细说明。`,
        };
    });

    const defaultPagination: PageState = {
        currentPage: 1,
        pageSize: 8,
        total: allData.length,
    };

    const mergeColumns = (column: ColumnProps, columns: ColumnProps[], keys: (keyof ColumnProps)[] = ['dataIndex']) => {
        const nextColumns = [...columns];
        nextColumns.forEach((curColumn, index) => {
            let isTarget = !!(keys && keys.length);
            for (const key of keys) {
                if (column[key] !== curColumn[key]) {
                    isTarget = false;
                    break;
                }
            }
            if (isTarget) {
                nextColumns[index] = { ...curColumn, ...column };
            }
        });
        return nextColumns;
    };

    const filterData = (filters: ColumnProps[], dataSource: Row[]) => {
        let result = [...dataSource];
        filters.forEach((filter) => {
            const filteredValue = filter.filteredValue;
            const dataIndex = filter.dataIndex;
            if (Array.isArray(filteredValue) && filteredValue.length && dataIndex) {
                result = result.filter((data) => filteredValue.filter((value) => String(data[dataIndex as keyof Row]).indexOf(value) > -1).length);
            }
        });
        return result;
    };

    const sortData = (sortObj: ColumnProps, dataSource: Row[]) => {
        const { sortOrder, dataIndex = '' } = sortObj;
        let sorter = sortObj.sorter;
        let result = dataSource;
        if (sorter && sortOrder && typeof sorter !== 'function') {
            sorter = (a, b) => (a[dataIndex] > b[dataIndex] ? 1 : -1);
        }
        if (typeof sorter === 'function') {
            result = result.toSorted(sorter);
            if (sortOrder === 'descend') {
                result = result.reverse();
            }
        }
        return result;
    };

    const fetchData = (pagination: PageState, currentPage = 1, sorter: ColumnProps = {}, filters: ColumnProps[] = []) => {
        const nextPagination = { ...pagination, currentPage };
        return new Promise<FetchResult>((resolve) => {
            setTimeout(() => {
                let data = sortData(sorter, allData);
                data = filterData(filters, data);
                const dataSource = data.slice((currentPage - 1) * nextPagination.pageSize, currentPage * nextPagination.pageSize);
                nextPagination.total = data.length;
                resolve({
                    dataSource,
                    pagination: nextPagination,
                    sorter,
                    filters,
                });
            }, 1500);
        });
    };

    const getSorterColumn = (columns: ColumnProps[]) => columns.filter((column) => !!column.sorter)[0];
    const getFilterColumns = (columns: ColumnProps[]) => columns.filter((column) => Array.isArray(column.filteredValue) && column.filteredValue.length);

    let loading = $state(true);
    let columns = $state.raw(initialColumns);
    let scroll = $state.raw<TableScroll>({});
    let rowSelection = $state.raw<RowSelectionProps | undefined>();
    let expandable = $state(false);
    let expandCellFixed = $state(false);
    let showTitle = $state(false);
    let showFooter = $state(false);
    let expandedRowKeys = $state<(string | number)[]>([]);
    let showHeader = $state(true);
    let bordered = $state(false);
    let resizable = $state(false);
    let pagination = $state.raw<PageState | false>(defaultPagination);
    let dataSource = $state.raw<Row[]>([]);

    const load = (currentPagination: PageState, currentPage?: number, sorter?: ColumnProps, filters?: ColumnProps[]) => {
        fetchData(currentPagination, currentPage, sorter, filters)
            .then((result) => {
                let nextColumns = mergeColumns(result.sorter, columns);
                for (const filterObj of result.filters) {
                    nextColumns = mergeColumns(filterObj, nextColumns);
                }
                columns = nextColumns;
                loading = false;
                pagination = result.pagination;
                dataSource = result.dataSource;
            })
            .catch((err) => {
                console.error(err);
                loading = false;
            });
    };

    const setPage = (page?: number | null, sorter?: ColumnProps | null, filters?: ColumnProps[]) => {
        if (loading) {
            return;
        }
        const currentPage = typeof page === 'number' ? page : (pagination && pagination.currentPage) || 1;
        loading = true;
        load(pagination || defaultPagination, currentPage, sorter || getSorterColumn(columns), filters || getFilterColumns(columns));
    };

    onMount(() => {
        load(defaultPagination, 1, getSorterColumn(initialColumns), []);
    });

    const tablePagination = $derived(pagination && { ...pagination, onPageChange: setPage });

    const toggleFixHeader = (checked: boolean) => {
        scroll = { ...scroll, y: checked ? 300 : undefined };
    };

    const toggleFixColumns = (checked: boolean) => {
        const lastIndex = columns.length - 1;
        if (checked) {
            columns = columns.map((column, index) => {
                if (index === 0) {
                    return { ...column, fixed: true };
                }
                return index === lastIndex ? { ...column, fixed: 'right' } : column;
            });
        } else {
            columns = columns.map((column) => ({ ...column, fixed: false }));
        }
        if (rowSelection) {
            rowSelection = { ...rowSelection, fixed: checked };
        }
        scroll = { ...scroll, x: checked ? '150%' : undefined };
        expandCellFixed = checked;
    };

    const toggleRowSelection = (checked: boolean) => {
        rowSelection = checked
            ? {
                  width: 48,
                  fixed: true,
                  onChange: (selectedRowKeys, selectedRows) => console.log('Selection changed, selectedRowKeys: ', selectedRowKeys, 'selectedRows: ', selectedRows),
              }
            : undefined;
    };

    const toggleShowSorter = (checked: boolean) => {
        columns = columns.map((column) => ({
            ...column,
            sorter: checked ? initialColumns.find((item) => item.dataIndex === column.dataIndex)?.sorter : undefined,
        }));
    };

    const toggleShowFilter = (checked: boolean) => {
        if (checked) {
            columns = columns.map((column) =>
                column.dataIndex === 'name'
                    ? {
                          ...column,
                          filters: [
                              {
                                  text: '姓名中包含 1',
                                  value: '1',
                              },
                              {
                                  text: '姓名中包含 2',
                                  value: '2',
                              },
                              {
                                  text: '姓名中包含 3',
                                  value: '3',
                              },
                          ],
                          filteredValue: [],
                      }
                    : column
            );
        } else {
            columns = columns.map((column) => ({ ...column, filters: undefined, filteredValue: undefined }));
            setPage(null, null, []);
        }
    };

    const onChange = (data: TableChangeInfo) => {
        console.log('Table changed: ', data);
        const { pagination: pageInfo, sorter, filters } = data;
        setPage(pageInfo?.currentPage, sorter, filters);
    };

    const toggleExpandedRowKeys = (checked: boolean) => {
        if (checked) {
            expandedRowKeys = dataSource.filter((data) => data.key).map((data) => data.key);
            expandable = true;
        } else {
            expandedRowKeys = [];
        }
    };

    const toggleResizable = (checked: boolean) => {
        resizable = checked;
        bordered = checked;
    };

    const toggleDataSource = (checked: boolean) => {
        if (checked) {
            dataSource = [];
        } else {
            setPage();
        }
    };

    const switchPagination = (position: TablePaginationPosition | false) => {
        pagination = position ? { ...defaultPagination, ...pagination, position } : false;
    };

    const switches: SwitchItem[] = [
        { text: '固定表头:', checked: () => !!scroll.y, onChange: toggleFixHeader },
        {
            text: '隐藏表头:',
            onChange: (checked) => {
                showHeader = !checked;
            },
        },
        {
            text: '显示标题:',
            onChange: (checked) => {
                showTitle = checked;
            },
        },
        {
            text: '显示底部:',
            onChange: (checked) => {
                showFooter = checked;
            },
        },
        { text: '固定列:', onChange: toggleFixColumns },
        { text: '显示选择列:', onChange: toggleRowSelection },
        {
            text: '显示加载状态:',
            checked: () => loading,
            onChange: (checked) => {
                loading = checked;
            },
        },
        { text: '无数据:', checked: () => !dataSource.length, onChange: toggleDataSource },
        {
            text: '开启排序功能:',
            checked: () => columns.some((column) => column.sorter),
            onChange: toggleShowSorter,
        },
        {
            text: '开启过滤功能:',
            checked: () => columns.some((column) => column.filters),
            onChange: toggleShowFilter,
        },
        {
            text: '开启行展开功能:',
            checked: () => expandable,
            onChange: (checked) => {
                expandable = checked;
            },
        },
        { text: '展开当前所有行:', onChange: toggleExpandedRowKeys },
        {
            text: '显示边框:',
            checked: () => bordered,
            onChange: (checked) => {
                bordered = checked;
            },
        },
        { text: '开启列伸缩功能:', onChange: toggleResizable },
    ];
</script>

{#snippet expandedRow({ record }: { record: Record<string, any> })}
    <p>{record.description}</p>
{/snippet}

{#snippet footer()}
    <p style="margin: 0">This is footer.</p>
{/snippet}

<div>
    <div style={wrapStyle}>
        {#each switches as item (item.text)}
            <span style={switchStyle}>
                <span>{item.text}</span>
                <Switch aria-label={item.text} checked={item.checked?.()} onChange={item.onChange} size="small" />
            </span>
        {/each}
        <span style={switchStyle}>
            <span>分页控件:</span>
            <ButtonGroup>
                <Button onclick={() => switchPagination('bottom')}>Bottom</Button>
                <Button onclick={() => switchPagination('top')}>Top</Button>
                <Button onclick={() => switchPagination('both')}>Both</Button>
                <Button onclick={() => switchPagination(false)}>None</Button>
            </ButtonGroup>
        </span>
    </div>
    <Table
        bind:expandedRowKeys
        {bordered}
        {columns}
        {dataSource}
        {expandCellFixed}
        expandedRow={expandable ? expandedRow : undefined}
        footer={showFooter ? footer : undefined}
        {loading}
        {onChange}
        pagination={tablePagination}
        {resizable}
        {rowSelection}
        {scroll}
        {showHeader}
        title={showTitle ? 'This is title.' : undefined}
    >
        {#snippet cell({ column, record, text })}
            {#if column.dataIndex === 'name'}
                <span>
                    <Avatar alt="" shape="square" size="small" src={figmaIconUrl} style="margin-right: 12px" />
                    {text}
                </span>
            {:else if column.dataIndex === 'size'}
                {text} KB
            {:else if column.dataIndex === 'owner'}
                <div>
                    <Avatar color={record.avatarBg} size="small" style="margin-right: 4px">
                        {typeof text === 'string' && text.slice(0, 1)}
                    </Avatar>
                    {text}
                </div>
            {:else if column.dataIndex === 'updateTime'}
                {format(new Date(text), 'yyyy-MM-dd')}
            {:else}
                {text}
            {/if}
        {/snippet}
    </Table>
</div>

Fully custom rendering

Generally, the cell snippet is enough, but you can also pass Column.useFullRender = true to enable full custom rendering mode. At this time the output of the snippet replaces the whole cell, and the checkbox, expand button and indent nodes are passed as snippets to headerCell and cell, so you can further define the rendering of the header and cell content.

The parameter of headerCell is:

{
    column: ColumnProps,
    title: string | undefined, // column title
    filter: Snippet | undefined, // Filter button
    sorter: Snippet | undefined, // Sort button
    selection: Snippet | undefined, // Select button
}

The parts of the cell parameter that relate to full custom rendering are:

{
    expandIcon: Snippet | undefined, // Expand button
    selection: Snippet | undefined, // Select button
    indentText: Snippet | undefined, // Indentation
}

Render these nodes with {@render selection?.()}.

The example below renders the checkbox and content into the same cell and header.

Name
所有者
Semi Design 设计稿0.fig 0 KB
姜 姜鹏志
2024-05-06
Semi D2C 设计稿1.fig 5 KB
郝 郝宣
2024-05-11
Semi Design 设计稿2.fig 10 KB
姜 姜鹏志
2024-05-16
Semi D2C 设计稿3.fig 15 KB
郝 郝宣
2024-05-21
Semi Design 设计稿4.fig 20 KB
姜 姜鹏志
2024-05-26
Semi D2C 设计稿5.fig 25 KB
郝 郝宣
2024-05-31
Semi Design 设计稿6.fig 30 KB
姜 姜鹏志
2024-06-05
Semi D2C 设计稿7.fig 35 KB
郝 郝宣
2024-06-10
Semi Design 设计稿8.fig 40 KB
姜 姜鹏志
2024-06-15
Semi D2C 设计稿9.fig 45 KB
郝 郝宣
2024-06-20
Semi Design 设计稿10.fig 50 KB
姜 姜鹏志
2024-06-25
Semi D2C 设计稿11.fig 55 KB
郝 郝宣
2024-06-30
显示第 1 条-第 12 条,共 46 条
Code
<script lang="ts">
    import { format } from 'date-fns';
    import { Avatar, Table, type ColumnProps, type RowSelectionProps } from '@fibrae/semi-ui-svelte';

    const DAY = 24 * 60 * 60 * 1000;
    const figmaIconUrl = '/assets/placeholder/app.svg';

    interface Row {
        key: string;
        name: string;
        owner: string;
        size: number;
        updateTime: number;
        avatarBg: 'grey' | 'red';
    }

    const columns: ColumnProps[] = [
        {
            title: 'Name',
            dataIndex: 'name',
            width: 400,
            filters: [
                {
                    text: 'Semi Design 设计稿',
                    value: 'Semi Design 设计稿',
                },
                {
                    text: 'Semi D2C 设计稿',
                    value: 'Semi D2C 设计稿',
                },
            ],
            onFilter: (value, record) => record.name.includes(value),
            useFullRender: true,
        },
        {
            title: '大小',
            dataIndex: 'size',
            sorter: (a, b) => (a.size - b.size > 0 ? 1 : -1),
        },
        {
            title: '所有者',
            dataIndex: 'owner',
        },
        {
            title: '更新日期',
            dataIndex: 'updateTime',
            sorter: (a, b) => (a.updateTime - b.updateTime > 0 ? 1 : -1),
        },
    ];

    const getData = (total: number) => {
        const data: Row[] = [];
        for (let i = 0; i < total; i++) {
            const isSemiDesign = i % 2 === 0;
            const randomNumber = (i * 1000) % 199;
            data.push({
                key: '' + i,
                name: isSemiDesign ? `Semi Design 设计稿${i}.fig` : `Semi D2C 设计稿${i}.fig`,
                owner: isSemiDesign ? '姜鹏志' : '郝宣',
                size: randomNumber,
                updateTime: new Date(2024, 4, 6, 12).valueOf() + randomNumber * DAY,
                avatarBg: isSemiDesign ? 'grey' : 'red',
            });
        }
        return data;
    };

    const dataSource = getData(46);
    const pagination = { pageSize: 12 };
    const rowSelection: RowSelectionProps = {
        hidden: true,
        fixed: 'left',
    };
    const onChange = (changeInfo: unknown) => console.log(changeInfo);
</script>

<Table {columns} {dataSource} {onChange} {pagination} {rowSelection}>
    {#snippet headerCell({ column, title, sorter, filter, selection })}
        {#if column.dataIndex === 'name'}
            <span style="display: inline-flex; align-items: center; padding-inline-start: 20px">
                {@render selection?.()}
                <span style="margin-inline-start: 8px">{title}</span>
                {@render sorter?.()}
                {@render filter?.()}
            </span>
        {:else}
            {title}
        {/if}
    {/snippet}
    {#snippet cell({ column, text, record, expandIcon, selection, indentText })}
        {#if column.dataIndex === 'name'}
            <span style="display: inline-flex; align-items: center; justify-content: center">
                {@render indentText?.()}
                {@render expandIcon?.()}
                {@render selection?.()}
                <span style="margin-inline-start: 8px">
                    <Avatar alt="" shape="square" size="small" src={figmaIconUrl} style="margin-right: 12px" />
                    {text}
                </span>
            </span>
        {:else if column.dataIndex === 'size'}
            {text} KB
        {:else if column.dataIndex === 'owner'}
            <div>
                <Avatar color={record.avatarBg} size="small" style="margin-right: 4px">
                    {typeof text === 'string' && text.slice(0, 1)}
                </Avatar>
                {text}
            </div>
        {:else if column.dataIndex === 'updateTime'}
            {format(new Date(text), 'yyyy-MM-dd')}
        {:else}
            {text}
        {/if}
    {/snippet}
    {#snippet expandedRow({ record })}
        <article>{record.name}</article>
    {/snippet}
</Table>

Header Merge

Users can merge table headers to group them. Header merging can be combined with fixed columns, virtualization, data grouping, column resizing and other features. The Svelte Table only has the configuration-based writing, because there is no Table.Column.

Merged Header Configuration Writing

基本信息 其他信息 更多
标题
所有者
Semi Design 设计稿0.fig 0 KB
姜 姜鹏志
2024-05-06
Semi D2C 设计稿1.fig 5 KB
郝 郝宣
2024-05-11
Semi Design 设计稿2.fig 10 KB
姜 姜鹏志
2024-05-16
Semi D2C 设计稿3.fig 15 KB
郝 郝宣
2024-05-21
Semi Design 设计稿4.fig 20 KB
姜 姜鹏志
2024-05-26
Semi D2C 设计稿5.fig 25 KB
郝 郝宣
2024-05-31
Semi Design 设计稿6.fig 30 KB
姜 姜鹏志
2024-06-05
Semi D2C 设计稿7.fig 35 KB
郝 郝宣
2024-06-10
Semi Design 设计稿8.fig 40 KB
姜 姜鹏志
2024-06-15
Semi D2C 设计稿9.fig 45 KB
郝 郝宣
2024-06-20
显示第 1 条-第 10 条,共 46 条
Code
<script lang="ts">
    import { format } from 'date-fns';
    import { IconMore } from '@fibrae/semi-icons-svelte';
    import { Avatar, Table, type ColumnProps } from '@fibrae/semi-ui-svelte';

    const DAY = 24 * 60 * 60 * 1000;
    const figmaIconUrl = '/assets/placeholder/app.svg';

    interface Row {
        key: string;
        name: string;
        owner: string;
        size: number;
        updateTime: number;
        avatarBg: 'grey' | 'red';
    }

    const columns: ColumnProps[] = [
        {
            title: '基本信息',
            fixed: 'left',
            children: [
                {
                    title: '标题',
                    dataIndex: 'name',
                    width: 300,
                    fixed: true,
                    filters: [
                        {
                            text: 'Semi Design 设计稿',
                            value: 'Semi Design 设计稿',
                        },
                        {
                            text: 'Semi D2C 设计稿',
                            value: 'Semi D2C 设计稿',
                        },
                    ],
                    onFilter: (value, record) => record.name.includes(value),
                },
                {
                    title: '大小',
                    dataIndex: 'size',
                    width: 100,
                    fixed: true,
                    sorter: (a, b) => (a.size - b.size > 0 ? 1 : -1),
                },
            ],
        },
        {
            title: '其他信息',
            children: [
                {
                    title: '所有者',
                    dataIndex: 'owner',
                },
                {
                    title: '更新日期',
                    dataIndex: 'updateTime',
                    sorter: (a, b) => (a.updateTime - b.updateTime > 0 ? 1 : -1),
                },
            ],
        },
        {
            title: '更多',
            fixed: 'right',
            width: 100,
            align: 'center',
            dataIndex: 'operate',
        },
    ];

    const getData = (total: number) => {
        const data: Row[] = [];
        for (let i = 0; i < total; i++) {
            const isSemiDesign = i % 2 === 0;
            const randomNumber = (i * 1000) % 199;
            data.push({
                key: '' + i,
                name: isSemiDesign ? `Semi Design 设计稿${i}.fig` : `Semi D2C 设计稿${i}.fig`,
                owner: isSemiDesign ? '姜鹏志' : '郝宣',
                size: randomNumber,
                updateTime: new Date(2024, 4, 6, 12).valueOf() + randomNumber * DAY,
                avatarBg: isSemiDesign ? 'grey' : 'red',
            });
        }
        return data;
    };

    const data = getData(46);
    const rowSelection = { fixed: true };
    const scroll = { x: 1000, y: 400 };
    const onChange = (changeInfo: unknown) => console.log(changeInfo);
</script>

<Table {columns} dataSource={data} {onChange} {rowSelection} {scroll}>
    {#snippet cell({ column, text, record })}
        {#if column.dataIndex === 'name'}
            <span>
                <Avatar alt="" shape="square" size="small" src={figmaIconUrl} style="margin-right: 12px" />
                {text}
            </span>
        {:else if column.dataIndex === 'size'}
            {text} KB
        {:else if column.dataIndex === 'owner'}
            <div>
                <Avatar color={record.avatarBg} size="small" style="margin-right: 4px">
                    {typeof text === 'string' && text.slice(0, 1)}
                </Avatar>
                {text}
            </div>
        {:else if column.dataIndex === 'updateTime'}
            {format(new Date(text), 'yyyy-MM-dd')}
        {:else if column.dataIndex === 'operate'}
            <IconMore />
        {:else}
            {text}
        {/if}
    {/snippet}
    {#snippet expandedRow({ record })}
        <article>{record.name}</article>
    {/snippet}
</Table>

colSpan and rowSpan

  • Besides merging through children, a header can merge columns by setting column.colSpan.
  • Tables support row/column merging. column.onCell returns the cell attributes colspan or rowspan (lowercase); when set to 0, the cell is not rendered and cell is not called for it.
type OnCell = (record: RecordType, rowIndex: number) => TableCellAttrs;

// TableCellAttrs: the attributes and listeners a td supports, with colspan and rowspan in lowercase
标题 大小 所有者
Semi Design 设计稿0.fig
Semi D2C 设计稿1.fig
5 KB
郝 郝宣
2024-05-11
姜 姜鹏志
Semi D2C 设计稿3.fig
15 KB
郝 郝宣
2024-05-21
Semi Design 设计稿4.fig
20 KB
姜 姜鹏志
2024-05-26
Code
<script lang="ts">
    import { format } from 'date-fns';
    import { Avatar, Table, type ColumnProps } from '@fibrae/semi-ui-svelte';

    const figmaIconUrl = '/assets/placeholder/app.svg';
    const DAY = 24 * 60 * 60 * 1000;

    interface Row {
        key: string;
        name: string;
        owner: string;
        size: number;
        updateTime: number;
        avatarBg: 'grey' | 'red';
    }

    // onCell returns the native lowercase colspan and rowspan; 0 leaves out the cell another cell spans over
    const columns: ColumnProps[] = [
        {
            title: '标题',
            dataIndex: 'name',
            width: 400,
            onCell: (_record, index) => {
                if (index === 0) {
                    return { colspan: 4 };
                }
                if (index === 1) {
                    return { rowspan: 2 };
                }
                if (index === 2) {
                    return { rowspan: 0 };
                }
                return {};
            },
        },
        {
            title: '大小',
            dataIndex: 'size',
            onCell: (_record, index) => {
                if (index === 0) {
                    return { colspan: 0 };
                }
                if (index === 1) {
                    return { rowspan: 2 };
                }
                if (index === 2) {
                    return { rowspan: 0 };
                }
                return {};
            },
        },
        {
            title: '所有者',
            dataIndex: 'owner',
            onCell: (_record, index) => (index === 0 ? { colspan: 0 } : {}),
        },
        {
            title: '更新日期',
            dataIndex: 'updateTime',
            sorter: (a, b) => (a.updateTime - b.updateTime > 0 ? 1 : -1),
            onCell: (_record, index) => {
                if (index === 0) {
                    return { colspan: 0 };
                }
                if (index === 1) {
                    return { rowspan: 2 };
                }
                if (index === 2) {
                    return { rowspan: 0 };
                }
                return {};
            },
        },
    ];

    const dataSource: Row[] = [];
    for (let i = 0; i < 5; i++) {
        const isSemiDesign = i % 2 === 0;
        const randomNumber = (i * 1000) % 199;
        dataSource.push({
            key: '' + i,
            name: isSemiDesign ? `Semi Design 设计稿${i}.fig` : `Semi D2C 设计稿${i}.fig`,
            owner: isSemiDesign ? '姜鹏志' : '郝宣',
            size: randomNumber,
            updateTime: new Date(2024, 4, 6, 12).valueOf() + randomNumber * DAY,
            avatarBg: isSemiDesign ? 'grey' : 'red',
        });
    }
</script>

<Table {columns} {dataSource} pagination={false}>
    {#snippet cell({ column, record, text })}
        {#if column.dataIndex === 'name'}
            <div>
                <Avatar alt="" shape="square" size="small" src={figmaIconUrl} style="margin-right: 12px" />
                {text}
            </div>
        {:else if column.dataIndex === 'size'}
            {text} KB
        {:else if column.dataIndex === 'owner'}
            <div>
                <Avatar color={record.avatarBg} size="small" style="margin-right: 4px">
                    {typeof text === 'string' && text.slice(0, 1)}
                </Avatar>
                {text}
            </div>
        {:else if column.dataIndex === 'updateTime'}
            {format(new Date(text), 'yyyy-MM-dd')}
        {:else}
            {text}
        {/if}
    {/snippet}
</Table>

API Reference

Table

Props

PropertyDescriptionTypeDefault
aria-describedbyaria-describedby of the table.string | null-
aria-labelThe accessible name of the table. Without aria-label and aria-labelledby, a table with a title is named by the title.string | null-
aria-labelledbyaria-labelledby of the table. It wins over title.string | null-
borderedWhether to show the outer border and the column borders.booleanfalse
cellThe content of a data cell, called for every data column, with text, record, index, column, expandIcon, isHovering and more; tell the columns apart with column.dataIndex or column.key. Render text for the columns the snippet leaves alone. With useFullRender on a column the snippet replaces the whole cell, and the selection, indentText and expandIcon arguments place the checkbox, the indent and the expand button.TypeSnippet<[TableCellSnippetProps<R>]>-
childrenRecordNameThe field of a record that holds its child records in a tree table.string"children"
clickGroupedRowToExpandClicking a group row expands or collapses the group.booleanfalse
columnsThe column configuration, see Column. A column holds data only; markup goes to the cell, headerCell, sortIcon, filterIcon, filterDropdown and other snippets of Table.TypeColumnProps<R>[]-
componentsReplaces the elements Table renders, such as the outer, wrapper, row and cell of the header and the body, with a tag name that replaces the default tag of that part.TableComponents-
currentPageThe current page, bound with bind:currentPage and written back when the page changes. It wins over pagination.currentPage. Without currentPage in pagination, Table still pages dataSource itself and a change from the parent turns the page; with it, the pager is controlled and Table does not page dataSource.number-
dataSourceThe data. Give every record a unique key, or name the primary key field with rowKey. Rows added or removed in place (push, splice) are sorted and filtered again; a record field changed in place shows in its cell but does not sort or filter again.R[]-
defaultExpandAllGroupRowsWhether all group rows start expanded. Has no effect on data loaded later.booleanfalse
defaultExpandAllRowsWhether all rows start expanded. Has no effect on data loaded later.booleanfalse
directionThe text direction. Defaults to the direction of ConfigProvider and can be set here per table.'ltr' | 'rtl'-
emptyThe content shown without data. Without the snippet the locale's empty text shows; an empty snippet shows nothing.Snippet-
expandAllGroupRowsWhether all group rows are expanded.booleanfalse
expandAllRowsWhether all rows are expanded.booleanfalse
expandCellFixedWhether the column of the expand button is fixed, with the same values as the fixed field of a column.ColumnFixedfalse
expandedRowThe content of an expanded row, with record, index and expanded. Once given, every expandable row has an expand button; empty output leaves the row out.TypeSnippet<[TableExpandedRowSnippetProps<R>]>-
expandedRowKeysThe keys of the expanded rows, bound with bind:expandedRowKeys, group keys included. Leave it out for uncontrolled expansion.(string | number)[]-
expandIconPass false to hide the expand buttons, or a snippet with expanded to replace the icon of an expand button. A custom icon does not rotate.Typeboolean | Snippet<[TableExpandIconSnippetProps]>-
expandRowByClickWhether clicking a row expands an expandable row.booleanfalse
filterDropdownThe filter panel of a column with filterDropdown set to true, with column, the temporary value tempFilteredValue and onTempFilteredValueChange, onConfirm, onClear, onClose and onReset; tell the columns apart with column.TypeSnippet<[TableFilterDropdownSnippetProps<R>]>-
filterDropdownItemAn item of the filter menu, with text, value, checked, level, onChange and more. Once given, it replaces every item.TypeSnippet<[TableFilterDropdownItemSnippetProps<R>]>-
filterIconThe content of the filter button, with filtered and column.TypeSnippet<[TableFilterIconSnippetProps<R>]>-
footerThe table footer: plain text, or a snippet with the records of the current page as pageData.Typestring | Snippet<[TableTitleSnippetProps<R>]>-
groupByThe grouping rule: a record field name, or a function that returns a string or a number.TypeTableGroupBy<R>-
groupSectionThe content of a group row, with groupKey and group (the row keys of the group).TypeSnippet<[TableGroupSectionSnippetProps]>-
groupSectionColumnThe column configuration of the single cell of a group row. The returned object has no title, children or dataIndex.Type(groupKey: string | number, group: (string | number)[]) => Omit<ColumnProps<R>, 'title' | 'children' | 'dataIndex'>-
headerCellThe content of a column title, with column, title and sorter, filter, selection. Render title for the columns the snippet leaves alone. With useFullRender on a column the snippet replaces the whole header cell, and sorter, filter and selection place the sort, filter and selection controls.TypeSnippet<[TableHeaderCellSnippetProps<R>]>-
headerStyleInline style string of every header cell, fixed headers included.string-
hideExpandedColumnIn an expandable table the expand button shares the first cell by default; set false to render it as its own column.booleantrue
indentSizeThe indent of each tree level, in pixels.number20
keepDOMWhether collapsing a row keeps its DOM.booleanfalse
loadingWhether the table shows its loading state.booleanfalse
onChangeCalled when the page, the sorting or the filters change. The argument holds pagination, filters, sorter and extra.changeType.Type(changeInfo: TableChangeInfo<R>) => void-
onExpandCalled when an expand button is clicked, with the expanded state, the record (an object with groupKey for a group row) and the mouse event.Type(expanded: boolean, record: TableIncludeGroupRecord<R>, event: MouseEvent) => void-
onExpandedRowsChangeCalled when the expanded rows change, with the array of expanded records.Type(expandedRows: TableIncludeGroupRecord<R>[]) => void-
onGroupedRowLike onRow, for group rows. The returned object holds the attributes and lowercase listeners of the tr. index is the group's ordinal, or its position among the virtual list's rows when virtualized is on.Type(record: { groupKey: string | number; records: R[] }, index: number) => TableRowAttrs-
onHeaderRowSets the attributes and lowercase listeners of a header row. The returned object (without style) is merged into the row.Type(columns: ColumnProps<R>[], index: number) => Omit<TableRowAttrs, 'style'>-
onRowSets the attributes and lowercase listeners (such as onclick and onmouseenter) of a data row; the third argument is the row state. The returned listeners run after the row's own handling and the onRowXxx callbacks.Type(record: R, index: number, rowState: { disabled: boolean; selected: boolean }) => TableRowAttrs-
onRowContextMenuCalled when a data row opens the context menu, before the oncontextmenu returned by onRow.Type(record: R, event: MouseEvent) => void-
onRowDoubleClickCalled when a data row is double-clicked, before the ondblclick returned by onRow.Type(record: R, event: MouseEvent) => void-
onRowMouseEnterCalled when the pointer enters a data row, before the onmouseenter returned by onRow.Type(record: R, event: MouseEvent) => void-
onRowMouseLeaveCalled when the pointer leaves a data row, before the onmouseleave returned by onRow.Type(record: R, event: MouseEvent) => void-
onScrollCalled when the vertical offset of a virtualized body changes, with scrollOffset. A horizontal scroll does not call it.Type(args: TableVirtualizedScrollArgs) => void-
paginationThe pagination configuration. Pass false to turn pagination off.boolean | TablePaginationPropstrue
paginationContentReplaces the pagination info and the Pagination. The pagination argument can be spread on a Pagination as is. It is named apart from the pagination prop, which shares the namespace.TypeSnippet<[TablePaginationSnippetProps]>-
prefixClsThe prefix of the class names.string"semi-table"
resizableWhether columns can be resized; a resizable column needs a width. An object configures onResize, onResizeStart, onResizeStop and handlerClassName.Typeboolean | ResizableProps<R>false
rowExpandableTells whether a row can be expanded. A row that returns false has no expand button.TypeTableRowExpandable<R>-
rowKeyThe row key: a record field name, or a function that returns a string or a number.TypeTableRowKey<R>"key"
rowSelectionThe row selection configuration. Pass true for the defaults, see rowSelection.Typeboolean | RowSelectionProps<R>-
rowSpanHoverHovering a cell with rowspan highlights every row the cell spans.booleanfalse
scrollThe scroll area: x is the width and y the height, see scroll.TableScroll-
selectedRowKeysThe keys of the selected rows, bound with bind:selectedRowKeys and written back when the selection changes. It wins over rowSelection.selectedRowKeys. Pass rowSelection as well to show the selection column.(string | number)[]-
selectionCellA cell of the selection column, called for the header and the rows, with selected, inHeader, originNode (the default checkbox) and more. Render originNode to keep the default checkbox.TypeSnippet<[TableSelectionCellSnippetProps<R>]>-
showHeaderWhether to show the header.booleantrue
sizeThe table size, which sets the padding of the cells.TableSize"default"
sortIconThe sort icon, with sortOrder and column.TypeSnippet<[TableSortIconSnippetProps<R>]>-
stickySticks the header to the top of the page. With an object, top is the distance from the scroll container.TableSticky-
titleThe table title: plain text, or a snippet with the records of the current page as pageData.Typestring | Snippet<[TableTitleSnippetProps<R>]>-
virtualizedTurns on virtualization, which needs scroll.y as a number. An object sets the row height itemSize and the extra rows overscanCount.TableVirtualized-

Methods

MethodDescriptionType
getCurrentPageDataReturns a deep copy of the data of the current page, with dataSource and groups.Type() => Pick<BasePageData<AnyRecord>, "dataSource" | "groups">
scrollToScrolls a virtualized table to a vertical offset, in pixels.Type(offset: number) => void
scrollToItemScrolls a virtualized table to a row. The second argument is the alignment.Type(index: number, align?: Parameters<TableHandle['scrollToItem']>[1]) => void

Native attributes such as class, style (a string), id and data-* land on the outermost element; aria-label, aria-labelledby and aria-describedby land on the element with role="table". When the table has a title and the caller passes no name, the table points to the title through aria-labelledby.

TableProps, ColumnProps, TableHandle and the argument types of the snippets can all be imported from @fibrae/semi-ui-svelte. Their generic parameter R (RecordType below) is Record<string, any> by default.

The fields of virtualized when it is an object (TableVirtualizedProps):

FieldInstructionsTypeDefault
itemSizeRow height. When not passed, the default follows size: 53 for default, 45 for middle and 37 for small, and a group header row is as high as a normal rownumber | ((index: number, row: { sectionRow?: boolean, expandedRow?: boolean }) => number)53
overscanCountRows rendered above and below the visible areanumber2

The values of components are tag names, and the tag replaces the default tag of that part. The positions that can be replaced (TableComponents):

PositionInstructions
header.outer, header.wrapper, header.row, header.celltable, thead, tr and th of the header
body.outer, body.wrapper, body.row, body.celltable, tbody, tr and td of the body
body.colgroup.wrapper, body.colgroup.colcolgroup and col

headerStyle Example

headerStyle will be applied to all header <th> elements, including fixed columns.

<script lang="ts">
    import { Table, type ColumnProps } from '@fibrae/semi-ui-svelte';

    const columns: ColumnProps[] = [
        { title: 'Name', dataIndex: 'name', width: 180, fixed: 'left' },
        { title: 'Age', dataIndex: 'age', width: 120 },
        { title: 'Address', dataIndex: 'address', width: 280 },
        { title: 'Company', dataIndex: 'company', width: 280 },
    ];

    const dataSource = Array.from({ length: 6 }).map((_, i) => ({
        key: i,
        name: `Edward ${i}`,
        age: 20 + i,
        address: 'No. 1, Lake Park',
        company: 'Semi Design',
    }));
</script>

<Table {columns} {dataSource} scroll={{ x: 900 }} headerStyle="background-color: #F5F6F7; font-weight: 600" />

headerStyle is a CSS string.

The Svelte Table is not a generic component: the columns, rowSelection, onRow and so on passed to it read records as Record<string, any>. When records have a concrete type, declare dataSource with that type and narrow the record with as in snippets and callbacks:

<script lang="ts">
    import { Table, type ColumnProps } from '@fibrae/semi-ui-svelte';

    interface Row {
        key: string;
        name: string;
        age: number;
        address: string;
    }

    const columns: ColumnProps[] = [
        { title: 'Name', dataIndex: 'name', width: 200 },
        // ...
    ];

    const data: Row[] = [
        { key: '1', name: 'John Brown', age: 32, address: 'New York No. 1 Lake Park' },
        // ...
    ];
</script>

<Table {columns} dataSource={data}>
    {#snippet cell({ column, record, text })}
        {#if column.dataIndex === 'age'}
            {(record as Row).age} years
        {:else}
            {text}
        {/if}
    {/snippet}
</Table>

The pagination configuration TablePaginationProps adds position and formatPageText to the props of Pagination, and the arguments of onChange are (currentPage, pageSize).

onRow/onHeaderRow Usage

Also in column.onCell column.onHeaderCell Properties or events supported by td / th can also be returned.

The third parameter rowStatus of onRow can get the current row's status information, including disabled and selected properties. This is useful when you need to execute different logic based on the row's selected or disabled state, for example, determining whether to allow selection when clicking a row.

The event listeners in the returned object are lowercase native names (onclick, onmouseenter, onmouseleave), use class for the class name, and style is a string. The own handling of Table and callbacks such as onRowMouseEnter run before the listeners returned here.

<script lang="ts">
    import { Table, type TableProps } from '@fibrae/semi-ui-svelte';

    const onRow: TableProps['onRow'] = (record, index, rowStatus) => ({
        onclick: () => {
            // rowStatus.disabled indicates whether the current row is disabled
            // rowStatus.selected indicates whether the current row is selected
            if (rowStatus.disabled) {
                return;
            }
            console.log('Clicked row', index, rowStatus);
        },
        onmouseenter: () => {},
        onmouseleave: () => {},
        class: '',
        // Other attributes or events that can be applied to tr
    });

    const onHeaderRow: TableProps['onHeaderRow'] = (columns, index) => ({
        onclick: () => {},
        class: '',
        // Other attributes or events that can be applied to tr
    });
</script>

<Table {columns} {dataSource} {onRow} {onHeaderRow} />

Column

A column accepts only the properties below, and a misspelled property name is a type error. To carry custom fields on a column, extend the type with an intersection, such as ColumnProps<RecordType> & { tooltip: string }.

ParametersInstructionsTypeDefault
alignSetting the alignment of columns, switched automatically in RTL'left' | 'right' | 'center''left'
childrenSettings for sub-columns when the header is mergedColumn[]
classColumn style namestring
colSpanWhen header columns merge, set to 0, do not rendernumber
dataIndexThe key corresponding to the column data in the data item. It is required when using sorter or filter.string
defaultFilteredValueDefault value of the filter, the filter state of the external control column with a value of the screened value arrayany[]
defaultSortOrderThe default value of sortOrder, one of 'ascend'|'descend'|falseboolean | stringfalse
ellipsisEllipsis Text, table-layout will automatically switch to fixed after it is turned onboolean | { showTitle: boolean }false
filterChildrenRecordWhether the child data needs to be filtered locally. If this function is enabled, if the child meets the filtering criteria, the parent will retain it even if it does not meet the criteria.boolean
filterDropdownShows the filter button when true; the content of the panel is given by the filterDropdown snippet of Table, which needs to write a variety of interactionsboolean
filterDropdownPropsProps passed to Dropdown (without visible, trigger, onVisibleChange and the like); the trigger is always click, see more in Dropdown APIobject
filterDropdownVisibleVisible of Dropdown, see more in Dropdown APIboolean
filterMultipleWhether to choose morebooleantrue
filterConfirmModeFilter confirm mode. immediate means filter immediately when clicking option; confirm means filter after clicking confirm button, and the dropdown panel will show confirm and reset buttons at the bottom'immediate' | 'confirm''immediate'
filteredValueControlled property of the filter, the filter state of the external control column with a value of the screened value arrayany[]
filtersFilter menu items for the headerFilter[]
fixedWhether the column is fixed, optional true (equivalent to left) 'left' 'right', switched automatically in RTLboolean | stringfalse
keyIdentifies the column for selection, sorting, filtering and widths; can be omitted when a unique dataIndex is setstring | number
resizeWhether to enable resize mode, this property will take effect only after Table resizable is enabledboolean
showSortTipWhether to display sorting tips, If sortOrder is set and sorting is controlled, this parameter will not take effectbooleanfalse
sortChildrenRecordWhether to sort child data locallyboolean
sortOrderThe controlled property of the sorting, the sorting of this control column can be set to 'ascend'|'descend'|falseboolean | stringfalse
sorterSorting function, local sorting uses a function (refer to the compareFunction of Array.sort), requiring a server-side sorting can be set to true. An independent dataIndex must be set for the sort column, and an independent key must be set for each data item in the dataSourceboolean | (r1: RecordType, r2: RecordType, sortOrder: 'ascend' | 'descend') => numbertrue
titlePlain text shown in the column header, also the content of the ellipsis Tooltip and the name of the sort button. Use the headerCell snippet of Table for custom contentstring
useFullRenderWhether to completely customize the rendering, see Fully custom rendering for usage details, enabling this feature will cause a certain performance lossbooleanfalse
widthColumn widthstring | number
onCellSet cell properties; colspan and rowspan in the returned object are lowercase(record: RecordType, rowIndex: number) => object
onFilterDetermine the running function of the filter in local mode. An independent dataIndex must be set for the filter column, and an independent key must be set for each data item in the dataSource(filteredValue: any, record: RecordType) => boolean
onFilterDropdownVisibleChangeA callback when a custom filter menu is visible(visible: boolean) => void
onHeaderCellSet the head cell property; index is the index of the header row(column: Column, columnIndex: number, index: number) => object

A column is data only: there is no Table.Column component, and no render, renderFilterDropdown, renderFilterDropdownItem or shouldCellUpdate. Customize cells, headers, sort and filter icons and filter items with the snippets of Table: cell, headerCell, sortIcon, filterIcon, filterDropdown and filterDropdownItem; the column argument tells the columns apart, and the arguments are listed in the props table of Table above.

Some of the type definitions used above:

type Filter = {
    value: any;
    text: string;
    children?: Filter[];
};

rowSelection

ParametersInstructionsTypeDefault
classStyle name listedstring
checkRelationParent-child node selection association mode. When set to 'related', selecting a parent node automatically selects all child nodes, and selecting a child node affects the parent node's state (checked/half-checked/unchecked). Default is 'unRelated', meaning parent and child node selections are independent.'related' | 'unRelated''unRelated'
clickRowWhether to enable click row to select. When enabled, clicking anywhere on the row will trigger selection/deselection, including fixed columns. Disabled rows (via getCheckboxProps) cannot be selected by clicking; when disabled is true, clicking a row does not select it either.booleanfalse
defaultSelectedRowKeysThe keys of the rows selected initially; read only on mount and ignored when selectedRowKeys is set(string | number)[]
disabledDisabled Checkbox in Table header or not; when true, clickRow selection does not take effect eitherbooleanfalse
fixedSecure the selection box column to the left.booleanfalse
getCheckboxPropsDefault property configuration for the selection box(record: RecordType) => object
hiddenHide selection column or notbooleanfalse
selectedRowKeysSpecifies the key array of the selected item, which needs to work with onChange(string | number)[]
widthCustom list selection box widthstring | number
onChangeA callback in the event of a change in the selected item. The first parameter will save the row keys selected last time, even if you do paging control or update the dataSource FAQ(selectedRowKeys: (string | number)[], selectedRows: RecordType[]) => void
onCellSet the attributes of the data cells of the selection column(record: RecordType, rowIndex: number) => object
onHeaderCellSet the head cell property; index is the index of the header row(column: Column, columnIndex: number, index: number) => object
onSelectCallback when the user manually selects a row. event is the selection box's change event, or the mouse event when clickRow selects on a row click(record: RecordType, selected: boolean, selectedRows: RecordType[], event: Event) => void
onSelectAllThe user manually clicks the callback of the header selection box, and all selectable rows in the current filter result (disabled rows excluded) will be selected/unselected. changedRows contains only the rows whose selection state changed(selected: boolean, selectedRows: RecordType[], changedRows: RecordType[], event: Event) => void

Customize the selection box with the selectionCell snippet of Table; there are no renderCell or shouldCellUpdate.

scroll

ParametersInstructionsTypeDefault
scrollToFirstRowOnChangeWhether to automatically scroll to the top of the table after paging, sorting, and filtering changes. When scroll.y is set, it scrolls the table body to the top; when scroll.y is not set, it scrolls the page to the table header positionbooleanfalse
xSet the width of the horizontal scroll area, which can be pixel value, percentage, or 'max-content'string|number
ySet the height of the vertical scroll area, which can be a pixel valuenumber

pagination

Page-turning component configuration. Pagination suggests not to use literal value.

Note: After pagination.onChange is set, Table onChange no longer responds to pagination changes.

ParametersInstructionsTypeDefault
currentPageCurrent page number; once given, paging is controllednumber-
formatPageTextPage-turning area copywriting custom formatting, pass false to close copywriting display; This item affects the copy display on the left of the page turning area of the form. It is different from the showTotal parameter of thePagination component.boolean | ({ currentStart: number, currentEnd: number, total: number }) => stringtrue
pageSizeNumber of entries per pagenumber10
positionLocation'bottom' | 'top' | 'both''bottom'
totalTotal number of entriesnumber0
preventPageChangeOnPageSizeChangeWhether to prevent automatic adjustment of currentPage when pageSize changes. By default, when pageSize changes, the component automatically calculates the new currentPage to maintain the current data position. When set to true, the user controls the page changebooleanfalse
onChangeCalled when the page number or the page size changes; once set, the onChange of Table no longer responds to pagination changes(currentPage: number, pageSize: number) => void
onPageChangeCalled when the page number changes(currentPage: number) => void
onPageSizeChangeCalled when the page size changes(pageSize: number) => void

For other configurations, see Pagination

Resizable

The parameters of the resizable object type, which mainly include event methods when the table column is scaled. These event methods can return an object that merges with the final column.

ParametersInstructionsTypeDefault
handlerClassNameThe class name added to the column while it is being resizedstring'resizing'
onResizeTriggers when the table column changes its width(column: Column) => Column | void
onResizeStartTriggers when the table column starts to change the width.(column: Column) => Column | void
onResizeStopTriggers when the table column stops changing the width(column: Column) => Column | void

Methods

The methods exposed by Table can be accessed through bind:this, typed as TableHandle:

<script lang="ts">
    import { onMount } from 'svelte';
    import { Table, type TableHandle } from '@fibrae/semi-ui-svelte';

    let table = $state<TableHandle>();

    onMount(() => {
        table?.getCurrentPageData(); // => { dataSource: [/*...*/], groups: /*...*/ }
    });
</script>

<Table bind:this={table} {columns} {dataSource} />
ParametersInstructions
getCurrentPageData()Returns the data object of the current page: { dataSource: RecordType[], groups: Map<string | number, Set<string | number>> }, keyed by group key with the set of row keys in that group
scrollTo(offset)Scrolls a virtualized table to the given pixel offset
scrollToItem(index, align?)Scrolls a virtualized table to the given row, align is 'auto' | 'smart' | 'center' | 'start' | 'end'

Accessibility

ARIA

Table follows the WAI-ARIA Table pattern: it is a static table, arrow keys do not move between cells, and every interactive part (sort, filter, expand, select) is a native control reachable with Tab.

  • The table's role is table, rows are row, header cells are columnheader and cells are cell. A tree table is a table too and does not expose row levels
  • The sort button is a native button in the header cell, named by the column title. The sort direction is aria-sort on the header cell (ascending or descending); unsorted columns do not carry it. The showSortTip tip shows when the button is focused
  • The expand button is a native button named "Expand this row", with aria-expanded telling whether the row is expanded
  • The row selection checkboxes have accessible names such as "Select this row" and "Select all rows"
  • With a fixed header (scroll.y, sticky) or virtualization, the header and the body render as two table elements; their common container has the table role and the inner tables have role none, so cells stay associated with their column headers
  • In a virtualized table aria-rowcount is the header row count plus the total data row count, and data rows' aria-rowindex continues after the header rows, so screen readers announce rows that are not in the DOM
  • The column filter button is a native button: Tab focuses it, Enter, Space or Down Arrow opens the filter menu, and it carries aria-haspopup="menu" and aria-expanded. Its name is "Filter data with this column", or "Filter this column, filter active" when a filter value is set. The filterIcon snippet only replaces the icon inside the button, and the name and keyboard behavior of the button stay the same

Known limitations:

  • In a sortable column the title renders inside the sort button, so do not put buttons, links or other interactive content in it

RTL/LTR

Content Guidelines

  • Table title
    • The title of the table should clearly make the user perceive the purpose of the table;
    • Add descriptions to complex tables to provide users with more contextual information about the table;
    • use sentence case;
  • Column headers
    • Keep column headings concise, it is recommended to use 1-2 words as column headings;
    • When the column header is long, it is recommended to display it in 2 lines, and the remaining text is abbreviated and displayed completely in the Tooltip;
    • Adopt the capitalization rules of Sentence case;
    • Use sentence case for column headings;
  • Table operation area

Design Tokens

These are the component's internal Sass variables and cannot be overridden. To customize styles, override the --semi-* CSS variables (see the Design Token page) or write unlayered CSS rules.
Sass variableValueUsage
$transition_duration-table_body-bgvar(--semi-transition_duration-none)表格-背景颜色-动画持续时间
$transition_function-table_body-bgvar(--semi-transition_function-easeOut)表格-背景颜色-过渡曲线
$transition_delay-table_body-bg0ms表格-背景颜色-延迟时间
$transition_duration-table_row-head-bgvar(--semi-transition_duration-none)表格-行头-背景颜色-动画持续时间
$transition_function-table_row-head-bglinear表格-行头-背景颜色-过渡曲线
$spacing-table-paddingY$spacing-base表格空数据占位及虚拟列表分组垂直内边距
$spacing-table-paddingX$spacing-base-tight表格空数据占位及虚拟列表分组水平内边距
$spacing-table_middle-paddingY$spacing-base-tight中尺寸表格单元格垂直内边距
$spacing-table_small-paddingY$spacing-tight小尺寸表格单元格垂直内边距
$spacing-table_title-paddingY$spacing-base表格标题垂直内边距
$spacing-table_title-paddingX0表格标题水平内边距
$spacing-table_footer-padding$spacing-base表格 footer 内边距
$spacing-table_expand_row-paddingLeft16px表格展开行左侧内边距
$spacing-table_expand_row-paddingRight16px表格展开行右侧内边距
$spacing-table_resizable-offset-y4px可拖动拉伸操作垂直偏移量
$spacing-table_resizable-bottom4px可拖动拉伸操作底部距离
$spacing-table_row_head-paddingY$spacing-base-tight表头垂直内边距
$spacing-table_row_head-paddingX$spacing-base表头水平内边距
$spacing-table_react_resizable_handle-right-1px可拖动拉伸操作向右偏移量
$spacing-table_tbody_rowCell-padding$spacing-base表格体单元格内边距(默认尺寸)
$spacing-table_tbody_rowSelection_rowCell_notSelection-paddingX16px表格选择状态行(非复选框列)单元格水平内边距
$spacing-table_tbody_rowSelection_rowCell_notSelection-paddingY10px表格选择状态行(非复选框列)垂直内边距
$spacing-table_column_sorter-marginLeft$spacing-extra-tight表头标题与排序按钮间距
$spacing-table_column_filter-marginLeft$spacing-extra-tight列过滤器按钮左侧外边距
$spacing-table_bordered_titler-paddingLeft$spacing-base带边框表格标题左侧内边距
$spacing-table_bordered_titler-paddingRight$spacing-base带边框表格标题右侧内边距
$spacing-table_expand_icon-marginRight8px行展开按钮右侧外边距
$spacing-table_panel_operation-paddingX$spacing-base表格操作栏水平内边距
$spacing-table_panel_operation-paddingY$spacing-tight表格操作栏垂直内边距
$width-table_base_border1px表格单元格分割线宽度
$width-table_header_border1px表格表头分割线宽度
$width-table_resizer_border2px表格拉伸列标示线宽度
$width-table_column_selection48px表格选择列及展开列宽度
$width-table_column_sorter-icon16px表格排序按钮宽度
$height-table_column_sorter-icon16px表格排序按钮高度
$width-table_column_filter-icon16px表格过滤按钮宽度
$height-table_column_filter-icon16px表格过滤按钮高度
$width-table_cell_fixed_left_last1px表格左侧固定列右侧分割线宽度
$width-table_cell_fixed_right_first1px表格右侧固定列左侧分割线宽度
$width-table_react_resizable_handle9px表格伸缩列调节热区宽度
$height-table_pagination_outer_min60px表格分页器高度
$height-table_column_filter_dropdown290px表格过滤筛选列表最大高度
$color-table_panel-bg-defaultvar(--semi-color-primary)操作区域样式默认背景颜色
$color-table_panel-text-defaultvar(--semi-color-primary-light-active)表格操作栏选中项文本颜色
$color-table-bg-defaultvar(--semi-color-bg-1)表格固定列及吸顶表头背景颜色
$color-table-text-defaultvar(--semi-color-text-0)单元格默认文字颜色
$color-table-border-defaultvar(--semi-color-border)表格描边颜色
$color-table_shadow-bg-defaultrgb(from var(--semi-black) r g b / 0.08)表格滚动后阴影颜色
$color-table_shadow-bg-darkrgb(from var(--semi-black) r g b / 0.4)表格滚动后阴影颜色 - 暗色模式
$color-table_shadow-border-defaultvar(--semi-color-border)表格拟阴影 描边颜色
$color-table_th-bg-defaultvar(--semi-color-bg-1)表头背景底色,须不透明以遮住滚动到固定列和吸顶表头下的内容
$color-table_th-fill-defaultvar(--semi-color-fill-0)表头背景叠加的填充色
$color-table_th-border-defaultvar(--semi-color-border)表头底部分割线颜色
$color-table_th-text-defaultvar(--semi-color-text-2)表头文字颜色
$color-table_th-clickSort-bg-hovervar(--semi-color-fill-1)点击表头触发排序背景色 - 悬浮
$color-table_pl-bg-defaulttransparent
$color-table_body-bg-defaultvar(--semi-color-bg-1)表格行背景颜色 - 默认态
$color-table_body-bg-hovervar(--semi-color-fill-0)表格行背景颜色 - 悬浮态
$color-table_footer-bg-defaultvar(--semi-color-fill-0)表格 footer 背景颜色 - 默认
$color-table_row_expanded-bg-defaultvar(--semi-color-fill-0)表格展开行背景颜色 - 默认
$color-table_expanded_icon-defaultvar(--semi-color-text-2)表格展开行图标颜色 - 默认
$color-table_expanded-bg-defaulttransparent表格展开行图标容器背景颜色 - 默认
$color-table_filter_on-text-defaultvar(--semi-color-primary)表格过滤按钮颜色 - 激活态
$color-table_filter-text-defaultvar(--semi-color-text-2)表格过滤按钮颜色 - 默认态
$color-table_sorter_on-text-defaultvar(--semi-color-primary)表格排序按钮颜色 - 激活态
$color-table_sorter-text-defaultvar(--semi-color-text-2)表格排序按钮颜色 - 默认态
$color-table_sorter-text-hovervar(--semi-color-text-2)表格排序按钮颜色 - 悬浮态
$color-table_page-text-defaultvar(--semi-color-text-2)表格翻页器文本颜色
$color-table_resizer-bg-defaultvar(--semi-color-primary)表格拉伸标示线颜色
$color-table_selection-bg-defaultvar(--semi-grey-0)表格分组背景色
$color-table_placeholder-text-defaultvar(--semi-color-text-2)表格空数据文本颜色
$color-table_cell-bg-hovervar(--semi-color-bg-0)让表格在 hover 时正确显示 $color-table_body-bg-hover 颜色,如无必要不要修改
$font-table_base-fontSize14px表格默认文本字号
$border-table_base-borderStylesolid表格描边样式
$width-table_fixed_shadow6px表格固定列滚动阴影宽度
$z-table_fixed_column$z-table-fixedfixed列的zIndex值

FAQ

  • Clicking the row selection button on the second page will jump to the first page?

    After the Table's dataSource is updated, the page number will be reset to the initial state. Please check if the data source changed when the component was rendered.

    The Table of Svelte compares the old and new dataSource item by item: it is not reset when every item is still the original record, and it is reset when records are added or removed. Keep dataSource in a $state.raw and replace it as a whole only when needed; do not regenerate the record objects on every evaluation.

    <script lang="ts">
        let dataSource = $state.raw<Record<string, any>[]>([]);
    
        // replace the whole array only when needed
        const load = async () => {
            dataSource = await fetchData();
        };
    </script>
    
    <!-- Wrong: every evaluation creates new record objects -->
    <Table dataSource={list.map((item) => ({ ...item }))} {columns} />
  • The number of filtered data is wrong?

    Please check that your filter columns and data sources are configured correctly.

    The filter column needs to set an independent dataIndex, and the dataSource needs to set an independent key. Please refer to the dataSource API. Otherwise the filtering function will not work properly.

  • Why is the table data not updated?

    Svelte tracks changes with runes: keep dataSource in a $state or $state.raw, and replacing the whole array or calling push, splice and similar methods triggers an update; with $state.raw, changing a field of a record directly does not, so replace the whole array. The same goes for columns: use $derived when it depends on reactive data, otherwise define it at the top level of <script>.

  • Why can't my form line be selected and expanded?

    Please specify a rowKey or set a different "key" attribute for each item of the dataSource. All rows related operations in the table need to be used.

  • How to implement custom sorting or pass parameters to the server for sorting when clicking the sort button?

    The input parameters of the onChange method include pagination, filters, and sorter. Users can customize the sorting of the dataSource according to the sorter.

  • How to add className to a row?

    Use onRow or onHeaderRow.

  • How to style the table cell?

    It can be controlled by column.onHeaderCell and column.onCell.

  • Why cache the previously selected keys for the first parameter of rowSelection onChange?

    This is for the scenario where the selected row keys are lost when data is selected on the first page during paging, and then the data is selected on the second page. If you don't want to use the cached keys, you can filter it from the current dataSource or use the second parameter of rowSelection onChange.

  • Does it support single row selection?

    Table currently does not support single-row selection function, and users can implement single selection in a custom way.