组件工坊:Reflex 组件库 100 例
·
课程概览
布局
·
Box
·
Flex
·
Stack
·
Grid
·
Container
·
Section
·
Center
·
Spacer
·
Separator
·
Card
·
Inset
·
Aspect Ratio
·
Fragment
排版
·
Text
·
Heading
·
Link
·
Code
·
Markdown
·
Blockquote
·
Quote
·
Strong
·
Em
·
Kbd
表单
·
Button
·
Input
·
Text Area
·
Checkbox
·
Switch
·
Radio Group
·
Select
·
Slider
·
Form
·
Upload
数据展示
·
Badge
·
Avatar
·
Icon
·
Callout
·
List
·
Data List
·
Code Block
·
Progress
·
Spinner
·
Scroll Area
·
Moment
展开与切换
·
Tabs
·
Accordion
·
Segmented Control
浮层
·
Tooltip
·
Popover
·
Hover Card
·
Dialog
·
Alert Dialog
·
Drawer
·
Dropdown Menu
·
Context Menu
·
Toast
动态渲染
·
Cond
·
Match
·
Foreach
·
Auto Scroll
媒体
·
Image
·
Audio
·
Video
表格与数据网格
·
Table
·
Data Table
·
Data Editor
图表
·
Area Chart
·
Bar Chart
·
Line Chart
·
Composed Chart
·
Pie Chart
·
Radar Chart
·
Radial Bar Chart
·
Scatter Chart
·
Funnel Chart
·
Treemap
·
Sankey Chart
·
Error Bar
图表部件
·
Axis
·
Cartesian Grid
·
Chart Tooltip
·
Legend
·
Label
·
Reference
·
Brush
其他图表库
·
Plotly
·
Pyplot
HTML 元素
·
HTML
·
HTML Text
·
HTML Layout
·
HTML Forms
·
HTML Media
·
HTML Tables
·
SVG
其他工具
·
Theme
·
Skeleton
·
Clipboard
·
Html Embed
·
Script
·
Memo
工坊订单
把咖啡馆的库存表做成像 Excel 一样点一下单元格就能直接修改,并且每次修改后都重新计算库存总额。
rx.data_editor 是像电子表格一样可以选中单元格并修改的数据网格。它用 Glide Data Grid 渲染,所以即使行和列非常多也很快。只读的表格用 Data Table 就够了;需要让用户修改数值时,才用这个组件。
columns 是每列一个 dict 组成的列表。
title:列标题。id:列的 id。省略时取 title 转成小写后的值。width:列宽(px)。type:单元格的类型。可用 "str"(默认)、"int"、"float"、"bool"、"image"。group:在值相同的几列上方,再画一个分组标题。data 是每行一个列表组成的列表。每个值都要与所在列的 type 相符。
rx.data_editor(
columns=[
{"title": "Code", "type": "str"},
{"title": "Value", "type": "int"},
{"title": "Activated", "type": "bool"},
],
data=[
["A", 1, True],
["B", 2, False],
["C", 3, False],
["D", 4, True],
["E", 5, True],
["F", 6, False],
],
)点击网格,试着选中单元格、行和列。双击单元格会打开编辑框(这个实验台不保存数值,所以改过的值很快会恢复原样)。
试着改属性
row_markers
column_select
row_select
range_select
vertical_border
show_search
rx.data_editor(
columns=[
{"title": "Code", "type": "str"},
{"title": "Value", "type": "int"},
{"title": "Activated", "type": "bool"},
],
data=[["A", 1, True], ["B", 2, False], ["C", 3, False], ["D", 4, True]],
row_markers="number",
column_select="single",
row_select="multi",
range_select="rect",
vertical_border=True,
show_search=False,
height="220px",
)| 属性 | 取值 | 用途 |
|---|---|---|
columns · data | 列表 | 列设置、行数据 |
rows | 整数 | 行数。省略时取 data 的长度 |
row_height · header_height · group_header_height | 整数(px) | 行、列标题、分组标题的高度 |
row_markers | "none" "number" "checkbox" "both" "checkbox-visible" "clickable-number" | 行前标记 |
row_marker_start_index · row_marker_width | 整数 | 行号起始值、标记列宽度 |
freeze_columns | 整数 | 横向滚动时固定在左侧的列数 |
min_column_width · max_column_width · max_column_auto_width | 整数(px) | 列宽的上下限 |
column_select · row_select | "none" "single" "multi" | 选择列、选择行 |
range_select | "none" "cell" "rect" "multi-cell" "multi-rect" | 选择单元格范围 |
*_selection_blending · row_selection_mode · span_range_behavior | 字符串 | 是否允许多种选择并存等细节行为 |
smooth_scroll_x · smooth_scroll_y | True / False | 平滑滚动 |
vertical_border · draw_focus_ring · fixed_shadow_x · fixed_shadow_y | True / False | 竖线、焦点边框、固定区域阴影 |
fill_handle | True / False | 拖动单元格角落填充数值的手柄 |
get_cells_for_selection | True / False | 允许复制选中的单元格(默认 True) |
overscroll_x · overscroll_y · scroll_offset_x · scroll_offset_y | 整数 | 超出末尾的滚动余量、初始滚动位置 |
prevent_diagonal_scrolling · show_search | True / False | 禁止斜向滚动、搜索框 |
theme | rx.data_editor_theme(...) 或 dict | 颜色与字体 |
grid_selection | 选择 dict | 当前选择。使用 on_grid_selection_change 时必须一并绑定 |
| 事件 | 接收的值 | 触发时机 |
|---|---|---|
on_cell_clicked · on_cell_activated · on_cell_context_menu | [列, 行] | 点击单元格、用 Enter 或双击激活、右键点击时 |
on_cell_edited | [列, 行]、新单元格 dict | 修改单元格值时(新值在 dict["data"] 中) |
on_finished_editing | 新单元格 dict、移动方向 | 编辑框关闭时 |
on_header_clicked · on_header_context_menu | [列, 行] | 点击、右键点击列标题时 |
on_header_menu_click | 列、位置 | 点击列标题的菜单按钮时 |
on_group_header_clicked · on_group_header_context_menu · on_group_header_renamed | - | 点击、右键点击分组标题,或重命名分组标题时 |
on_item_hovered | [列, 行] | 鼠标移到单元格上时 |
on_grid_selection_change · on_selection_cleared | 选择 dict · - | 选择改变或被清除时 |
on_delete | 选择 dict | 在选中的单元格上按 Delete 键时 |
on_row_appended · on_column_resize · on_search_close | - · 列、宽度 · - | 添加行、调整列宽、关闭搜索框时 |
type="image" 的列会把图片地址显示成小图。值可以是一个地址,也可以是包含多张图片的地址列表。为了让图片看得清楚,要调大 row_height。点击图片单元格会打开放大查看的窗口。
rx.data_editor(
columns=[
{"title": "Preview", "type": "image", "width": 120},
{"title": "Name", "type": "str"},
],
data=[
["https://picsum.photos/id/237/160/100", "Puppy"],
["https://picsum.photos/id/1025/160/100", "Pug"],
[["https://picsum.photos/id/1084/160/100", "https://picsum.photos/id/1074/160/100"], "Two animals"],
],
row_height=90,
height="320px",
)on_cell_clicked 以 [列, 行] 的形式传入被点击单元格的位置(都从 0 开始)。下面的列设置里有 group: "Data",所以除 Title 外的五列上方会多出一个名为「Data」的分组标题。
from typing import Any
class DataEditorClickState(rx.State):
clicked_data: str = "Cell clicked: "
cols: list[dict[str, Any]] = [
{"title": "Title", "type": "str"},
{"title": "Name", "type": "str", "group": "Data", "width": 220},
{"title": "Birth", "type": "str", "group": "Data", "width": 150},
{"title": "Human", "type": "bool", "group": "Data", "width": 80},
{"title": "House", "type": "str", "group": "Data"},
{"title": "Patronus", "type": "str", "group": "Data"},
]
data: list[list[Any]] = [
["1", "Harry James Potter", "31 July 1980", True, "Gryffindor", "Stag"],
["2", "Ronald Bilius Weasley", "1 March 1980", True, "Gryffindor", "Jack Russell terrier"],
["3", "Hermione Jean Granger", "19 September 1979", True, "Gryffindor", "Otter"],
["4", "Rubeus Hagrid", "6 December 1928", False, "Gryffindor", "None"],
]
@rx.event
def click_cell(self, pos: tuple[int, int]):
col, row = pos
self.clicked_data = f"Cell clicked: column {col}, row {row}"
rx.vstack(
rx.heading(DataEditorClickState.clicked_data, size="3"),
rx.data_editor(
columns=DataEditorClickState.cols,
data=DataEditorClickState.data,
on_cell_clicked=DataEditorClickState.click_cell,
height="220px",
),
width="100%",
)Data Editor 只负责让单元格可以编辑,并不会自动保存修改后的值。必须由 on_cell_edited 事件处理器接收位置和新单元格的信息,修改 State 中的 data,改动才会留在页面上。新值在 new_value["data"] 中。试着双击单元格修改数值,或者点一下 Activated 复选框。
官方文档没有给出这个事件的示例,但要做可编辑的网格,它必不可少。
Last edit: nothing yet
from typing import Any
class DataEditorEditState(rx.State):
cols: list[dict[str, Any]] = [
{"title": "Code", "type": "str"},
{"title": "Value", "type": "int"},
{"title": "Activated", "type": "bool"},
]
data: list[list[Any]] = [["A", 1, True], ["B", 2, False], ["C", 3, False]]
last_edit: str = "nothing yet"
@rx.event
def edit_cell(self, pos: tuple[int, int], new_value: dict[str, Any]):
col, row = pos
rows = [list(r) for r in self.data]
rows[row][col] = new_value.get("data")
self.data = rows
self.last_edit = f"row {row}, column {col} -> {new_value.get('data')}"
@rx.event
def add_row(self):
self.data = self.data + [["New", 0, False]]
rx.vstack(
rx.data_editor(
columns=DataEditorEditState.cols,
data=DataEditorEditState.data,
on_cell_edited=DataEditorEditState.edit_cell,
row_markers="number",
height="200px",
),
rx.hstack(
rx.button("Add row", size="1", on_click=DataEditorEditState.add_row),
rx.text("Last edit: ", DataEditorEditState.last_edit, size="2"),
align="center",
),
width="100%",
)把颜色和字体传给 theme。可以给 rx.data_editor_theme(...)(官方文档中是 from reflex.components.datadisplay.dataeditor import DataEditorTheme)传关键字参数,也可以直接传 dict。键名既可以用 accent_color 这样的下划线写法,也可以用 accentColor 这样的驼峰写法。下面的示例在深色主题上加了 row_height=50、平滑滚动和单列选择。
dataeditor_dark_theme = {
"accent_color": "#8c96ff",
"accent_light": "rgba(202, 206, 255, 0.253)",
"text_dark": "#ffffff",
"text_medium": "#b8b8b8",
"text_light": "#a0a0a0",
"text_bubble": "#ffffff",
"bg_icon_header": "#b8b8b8",
"fg_icon_header": "#000000",
"text_header": "#a1a1a1",
"text_header_selected": "#000000",
"bg_cell": "#16161b",
"bg_cell_medium": "#202027",
"bg_header": "#212121",
"bg_header_has_focus": "#474747",
"bg_header_hovered": "#404040",
"bg_bubble": "#212121",
"bg_bubble_selected": "#000000",
"bg_search_result": "#423c24",
"border_color": "rgba(225,225,225,0.2)",
"drilldown_border": "rgba(225,225,225,0.4)",
"link_color": "#4F5DFF",
"header_font_style": "bold 14px",
"base_font_style": "13px",
}
rx.data_editor(
columns=[
{"title": "Code", "type": "str", "width": 120},
{"title": "Value", "type": "int", "width": 120},
{"title": "Activated", "type": "bool", "width": 120},
],
data=[["A", 1, True], ["B", 2, False], ["C", 3, False], ["D", 4, True], ["E", 5, True]],
row_height=50,
smooth_scroll_x=True,
smooth_scroll_y=True,
column_select="single",
theme=rx.data_editor_theme(**dataeditor_dark_theme),
height="260px",
)库存表通过 on_cell_edited 保存修改,库存总额和需补货的数量写成计算变量。修改数量或单价后,下方的徽章会立刻更新。品名列用 freeze_columns=1 固定住。
from typing import Any
class DataEditorStockState(rx.State):
cols: list[dict[str, Any]] = [
{"title": "Item", "type": "str", "width": 160},
{"title": "Qty", "type": "int", "width": 80},
{"title": "Unit price", "type": "float", "width": 110},
{"title": "Reorder", "type": "bool", "width": 90},
]
data: list[list[Any]] = [
["Coffee beans", 12, 18.5, False],
["Paper cups", 300, 0.1, False],
["Oat milk", 4, 2.9, True],
]
@rx.event
def edit_cell(self, pos: tuple[int, int], new_value: dict[str, Any]):
col, row = pos
rows = [list(r) for r in self.data]
rows[row][col] = new_value.get("data")
self.data = rows
@rx.var
def total(self) -> str:
value = sum(float(r[1] or 0) * float(r[2] or 0) for r in self.data)
return f"${value:,.2f}"
@rx.var
def reorder_count(self) -> int:
return sum(1 for r in self.data if r[3])
rx.vstack(
rx.data_editor(
columns=DataEditorStockState.cols,
data=DataEditorStockState.data,
on_cell_edited=DataEditorStockState.edit_cell,
row_markers="number",
freeze_columns=1,
height="180px",
),
rx.hstack(
rx.badge("Stock value: ", DataEditorStockState.total, size="2"),
rx.badge("To reorder: ", DataEditorStockState.reorder_count, color_scheme="orange", size="2"),
),
width="100%",
)工坊笔记
columns 是 {title, type, width, id, group} 的 dict 列表,data 是行的列表。值要与列的 type 相符。
type:str · int · float · bool · image。
修改后的值必须在 on_cell_edited((列, 行), 新单元格) 中更新 State 的 data,才会保留下来。
选择范围用 column_select · row_select · range_select,行前标记用 row_markers。
theme=rx.data_editor_theme(...) 或 dict;大小用 height 和 row_height 设定。
订阅新课时和新课程通知
本课程新增课时或推出后续课程时,我们会发邮件通知你。
问答
关于本课有任何疑问都可以在这里留言。所有语言版本页面的提问会汇集在一起。