| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586 |
- import streamlit as st
- import pandas as pd
- def editable_dataframe_add():
- st.title("🛠️ 可编辑DataFrame演示, num_rows='dynamic'")
- st.write("下面的表格可以直接编辑,尝试修改评分并查看你最喜欢的命令!")
- # 示例数据
- df = pd.DataFrame(
- [
- {"command": "st.selectbox", "rating": 4, "is_widget": True},
- {"command": "st.balloons", "rating": 5, "is_widget": False},
- {"command": "st.time_input", "rating": 3, "is_widget": True},
- ]
- )
- edited_df = st.data_editor(df, num_rows="dynamic")
- favorite_command = edited_df.loc[edited_df["rating"].idxmax()]["command"]
- st.markdown(f"Your favorite command is **{favorite_command}** 🎈")
- def editable_dataframe_add_fixed():
- df = pd.DataFrame(
- [
- {"command": "st.selectbox", "rating": 4, "is_widget": True},
- {"command": "st.balloons", "rating": 5, "is_widget": False},
- {"command": "st.time_input", "rating": 3, "is_widget": True},
- ]
- )
- edited_df = st.data_editor(
- df,
- column_config={
- "command": "Streamlit Command",
- "rating": st.column_config.NumberColumn(
- "Your rating",
- help="How much do you like this command (1-5)?",
- min_value=1,
- max_value=5,
- step=1,
- format="%d ⭐",
- ),
- "is_widget": "Widget ?",
- },
- disabled=["command", "is_widget"],
- hide_index=True,
- )
- favorite_command = edited_df.loc[edited_df["rating"].idxmax()]["command"]
- st.markdown(f"Your favorite command is **{favorite_command}** 🎈")
- def editable_dataframe_basic():
- st.title("✏️ 基础可编辑DataFrame")
-
- # 初始化数据
- if 'edit_df' not in st.session_state:
- st.session_state.edit_df = pd.DataFrame({
- 'Name': ['Alice', 'Bob', 'Charlie', 'Diana'],
- 'Age': [25, 30, 35, 28],
- 'City': ['New York', 'London', 'Tokyo', 'Paris'],
- 'Salary': [50000, 60000, 70000, 55000],
- 'Active': [True, False, True, True]
- })
-
- st.write("📝 双击单元格即可编辑数据:")
-
- # 使用st.data_editor进行编辑
- edited_df = st.data_editor(
- st.session_state.edit_df,
- height=300,
- use_container_width=True,
- num_rows="dynamic", # 允许添加/删除行
- column_config={
- "Name": st.column_config.TextColumn(
- "姓名",
- help="输入姓名",
- max_chars=50,
- validate="^[A-Za-z ]+$" # 只允许字母和空格
- ),
- "Age": st.column_config.NumberColumn(
- "年龄",
- help="输入年龄 (18-100)",
- min_value=18,
- max_value=100,
- step=1,
- format="%d岁"
- ),
- "City": st.column_config.SelectboxColumn(
- "城市",
- help="选择城市",
- options=[
- "New York",
- "London",
- "Tokyo",
- "Paris",
- "Beijing",
- "Sydney"
- ],
- required=True
- ),
- "Salary": st.column_config.NumberColumn(
- "薪资",
- help="输入薪资",
- min_value=0,
- max_value=1000000,
- step=1000,
- format="$%d"
- ),
- "Active": st.column_config.CheckboxColumn(
- "是否活跃",
- help="勾选表示活跃用户",
- default=True
- )
- },
- disabled=["Name"], # 禁用Name列的编辑
- key="basic_editor"
- )
-
- # 显示更改
- if not edited_df.equals(st.session_state.edit_df):
- st.write("🔄 **数据已更改:**")
-
- col1, col2 = st.columns(2)
- with col1:
- st.write("**原始数据:**")
- st.dataframe(st.session_state.edit_df, use_container_width=True)
-
- with col2:
- st.write("**修改后数据:**")
- st.dataframe(edited_df, use_container_width=True)
-
- # 保存更改按钮
- if st.button("💾 保存更改", type="primary"):
- st.session_state.edit_df = edited_df.copy()
- st.success("✅ 数据已保存!")
- st.rerun()
-
- if st.button("↩️ 撤销更改"):
- st.warning("❌ 已撤销更改")
- st.rerun()
- def editable_dataframe_advanced():
- st.title("🔧 高级可编辑DataFrame")
-
- # 初始化复杂数据
- if 'advanced_df' not in st.session_state:
- st.session_state.advanced_df = pd.DataFrame({
- 'ID': range(1, 11),
- 'Product': [f'Product {i}' for i in range(1, 11)],
- 'Category': ['Electronics', 'Clothing', 'Food', 'Books', 'Toys'] * 2,
- 'Price': [19.99, 29.99, 9.99, 15.99, 25.99] * 2,
- 'Stock': [100, 50, 200, 75, 30, 120, 80, 150, 60, 90],
- 'Rating': [4.5, 3.8, 4.2, 4.7, 3.9, 4.1, 4.6, 4.3, 3.7, 4.4],
- 'Launch_Date': pd.date_range('2023-01-01', periods=10, freq='30D'),
- 'Image_URL': ['https://via.placeholder.com/50x50'] * 10,
- 'Available': [True, False, True, True, False, True, True, False, True, True]
- })
-
- st.write("🛍️ 产品管理系统 - 高级编辑功能:")
-
- # 筛选选项
- col1, col2, col3 = st.columns(3)
- with col1:
- category_filter = st.multiselect(
- "筛选类别",
- options=st.session_state.advanced_df['Category'].unique(),
- default=st.session_state.advanced_df['Category'].unique()
- )
-
- with col2:
- price_range = st.slider(
- "价格范围",
- min_value=0.0,
- max_value=50.0,
- value=(0.0, 50.0),
- step=0.01
- )
-
- with col3:
- show_available_only = st.checkbox("只显示可用商品", value=False)
-
- # 应用筛选
- filtered_df = st.session_state.advanced_df[
- (st.session_state.advanced_df['Category'].isin(category_filter)) &
- (st.session_state.advanced_df['Price'] >= price_range[0]) &
- (st.session_state.advanced_df['Price'] <= price_range[1])
- ]
-
- if show_available_only:
- filtered_df = filtered_df[filtered_df['Available'] == True]
-
- # 添加筛选提示
- if len(filtered_df) < len(st.session_state.advanced_df):
- st.info(f"📊 当前显示 {len(filtered_df)} 行(共 {len(st.session_state.advanced_df)} 行)。在筛选状态下,新增行将添加到筛选结果的末尾。")
-
- # 选项:是否编辑完整数据
- edit_mode = st.radio(
- "编辑模式",
- options=["筛选后数据", "完整数据"],
- help="选择'完整数据'可以在任意位置插入新行"
- )
-
- if edit_mode == "筛选后数据":
- # 编辑筛选后的数据
- display_df = filtered_df.copy()
- data_key = "filtered_editor"
- else:
- # 编辑完整数据
- display_df = st.session_state.advanced_df.copy()
- data_key = "full_editor"
-
- # 高级编辑器
- edited_df = st.data_editor(
- display_df,
- height=400,
- use_container_width=True,
- num_rows="dynamic",
- column_config={
- "ID": st.column_config.NumberColumn(
- "ID",
- help="产品ID",
- disabled=True
- ),
- "Product": st.column_config.TextColumn(
- "产品名称",
- help="输入产品名称",
- pinned=True, # 固定列
- max_chars=100
- ),
- "Category": st.column_config.SelectboxColumn(
- "类别",
- help="选择产品类别",
- options=['Electronics', 'Clothing', 'Food', 'Books', 'Toys', 'Sports', 'Home'],
- required=True
- ),
- "Price": st.column_config.NumberColumn(
- "价格",
- help="输入价格",
- min_value=0.01,
- max_value=9999.99,
- step=0.01,
- format="$%.2f"
- ),
- "Stock": st.column_config.NumberColumn(
- "库存",
- help="输入库存数量",
- min_value=0,
- max_value=10000,
- step=1,
- format="%d件"
- ),
- "Rating": st.column_config.NumberColumn(
- "评分",
- help="产品评分 (1-5)",
- min_value=1.0,
- max_value=5.0,
- step=0.1,
- format="%.1f⭐"
- ),
- "Launch_Date": st.column_config.DateColumn(
- "上架日期",
- help="选择上架日期",
- min_value=pd.Timestamp('2020-01-01'),
- max_value=pd.Timestamp('2030-12-31'),
- format="YYYY-MM-DD"
- ),
- "Image_URL": st.column_config.ImageColumn(
- "产品图片",
- help="产品图片URL"
- ),
- "Available": st.column_config.CheckboxColumn(
- "可用",
- help="产品是否可用",
- default=True
- )
- },
- key=data_key
- )
-
- # 处理数据变化
- if not edited_df.equals(display_df):
- st.write("🔄 **检测到数据变化**")
-
- # 保存按钮
- col_save, col_cancel = st.columns(2)
- with col_save:
- if st.button("💾 保存更改", type="primary"):
- if edit_mode == "完整数据":
- # 直接更新完整数据
- st.session_state.advanced_df = edited_df.copy()
- else:
- # 筛选模式下的保存逻辑
- # 需要将筛选后的更改合并回原始数据
- # 这里简化处理:用户需要切换到完整数据模式进行编辑
- st.warning("⚠️ 在筛选模式下只能添加新行,无法在指定位置插入。请切换到'完整数据'模式进行精确编辑。")
-
- # 只处理新增的行
- new_rows = edited_df[~edited_df.index.isin(display_df.index)]
- if len(new_rows) > 0:
- # 为新行分配新的ID
- max_id = st.session_state.advanced_df['ID'].max()
- new_rows = new_rows.copy()
- new_rows['ID'] = range(max_id + 1, max_id + 1 + len(new_rows))
-
- # 添加到原始数据末尾
- st.session_state.advanced_df = pd.concat([st.session_state.advanced_df, new_rows], ignore_index=True)
- st.success(f"✅ 已添加 {len(new_rows)} 行新数据")
- else:
- st.session_state.advanced_df = edited_df.copy()
- st.success("✅ 数据已保存")
-
- st.rerun()
-
- with col_cancel:
- if st.button("↩️ 取消更改"):
- st.rerun()
-
- # 快速插入工具
- st.subheader("⚡ 快速插入工具")
-
- with st.expander("🔧 精确位置插入", expanded=False):
- col1, col2, col3 = st.columns(3)
-
- with col1:
- insert_position = st.number_input(
- "插入位置(行号)",
- min_value=0,
- max_value=len(st.session_state.advanced_df),
- value=2,
- help="0=开头,2=第2行后"
- )
-
- with col2:
- new_product_name = st.text_input("产品名称", value="New Product")
- new_category = st.selectbox("类别", ['Electronics', 'Clothing', 'Food', 'Books', 'Toys', 'Sports', 'Home'])
-
- with col3:
- new_price = st.number_input("价格", min_value=0.01, value=29.99)
- new_stock = st.number_input("库存", min_value=0, value=100)
-
- # 插入按钮
- col_btn1, col_btn2, col_btn3 = st.columns(3)
-
- with col_btn1:
- if st.button("➕ 在指定位置插入"):
- new_row = pd.DataFrame({
- 'ID': [st.session_state.advanced_df['ID'].max() + 1],
- 'Product': [new_product_name],
- 'Category': [new_category],
- 'Price': [new_price],
- 'Stock': [new_stock],
- 'Rating': [4.0],
- 'Launch_Date': [pd.Timestamp.now().date()],
- 'Image_URL': ['https://via.placeholder.com/50x50'],
- 'Available': [True]
- })
-
- # 在指定位置插入
- if insert_position == 0:
- st.session_state.advanced_df = pd.concat([new_row, st.session_state.advanced_df], ignore_index=True)
- elif insert_position >= len(st.session_state.advanced_df):
- st.session_state.advanced_df = pd.concat([st.session_state.advanced_df, new_row], ignore_index=True)
- else:
- df_before = st.session_state.advanced_df.iloc[:insert_position]
- df_after = st.session_state.advanced_df.iloc[insert_position:]
- st.session_state.advanced_df = pd.concat([df_before, new_row, df_after], ignore_index=True)
-
- st.success(f"✅ 已在位置 {insert_position} 插入新产品")
- st.rerun()
-
- with col_btn2:
- if st.button("📝 在第2-3行间插入"):
- new_row = pd.DataFrame({
- 'ID': [st.session_state.advanced_df['ID'].max() + 1],
- 'Product': ['插入产品'],
- 'Category': ['Electronics'],
- 'Price': [35.99],
- 'Stock': [80],
- 'Rating': [4.2],
- 'Launch_Date': [pd.Timestamp.now().date()],
- 'Image_URL': ['https://via.placeholder.com/50x50'],
- 'Available': [True]
- })
-
- # 在第2行后插入(索引2的位置)
- df_before = st.session_state.advanced_df.iloc[:2]
- df_after = st.session_state.advanced_df.iloc[2:]
- st.session_state.advanced_df = pd.concat([df_before, new_row, df_after], ignore_index=True)
-
- st.success("✅ 已在第2-3行间插入新产品")
- st.rerun()
-
- with col_btn3:
- if st.button("🔄 重新排序ID"):
- # 重新分配连续的ID
- st.session_state.advanced_df['ID'] = range(1, len(st.session_state.advanced_df) + 1)
- st.success("✅ ID已重新排序")
- st.rerun()
- # 批量操作
- st.subheader("📋 批量操作")
- col1, col2, col3, col4 = st.columns([1,1,1,1])
-
- with col1:
- if st.button("🛒 全部设为可用"):
- st.session_state.advanced_df['Available'] = True
- st.success("✅ 所有产品已设为可用")
- st.rerun()
-
- with col2:
- if st.button("❌ 全部设为不可用"):
- st.session_state.advanced_df['Available'] = False
- st.success("✅ 所有产品已设为不可用")
- st.rerun()
-
- with col3:
- discount_rate = st.number_input("折扣率 (%)", min_value=0, max_value=50, value=10, label_visibility="collapsed")
- if st.button("💰 应用折扣"):
- st.session_state.advanced_df['Price'] *= (1 - discount_rate / 100)
- st.success(f"✅ 已应用 {discount_rate}% 折扣")
- st.rerun()
-
- with col4:
- # # 修复方案1:使用session_state管理重置确认状态
- # if 'reset_confirm' not in st.session_state:
- # st.session_state.reset_confirm = False
-
- # if st.button("📊 重置数据"):
- # st.session_state.reset_confirm = True
-
- # if st.session_state.reset_confirm:
- # st.warning("⚠️ 确认要重置所有数据吗?此操作不可撤销!")
-
- # if st.button("✅ 确认重置", type="primary"):
- # del st.session_state.advanced_df
- # st.session_state.reset_confirm = False
- # st.success("✅ 数据已重置")
- # st.rerun()
-
- # if st.button("❌ 取消"):
- # st.session_state.reset_confirm = False
- # st.rerun()
- # 方案2:使用expander + checkbox + button的组合
- with st.expander("🗑️ 危险操作", expanded=False):
- if 'reset_confirm' not in st.session_state:
- st.session_state.reset_confirm = False
- st.warning("⚠️ 重置数据将删除所有当前数据!")
-
- confirm_reset = st.checkbox("我确认要重置所有数据", key="confirm_reset_checkbox")
-
- if st.button("📊 执行重置", disabled=not confirm_reset, type="primary"):
- if confirm_reset:
- # 重置数据
- del st.session_state.advanced_df
- # 清除确认状态
- st.session_state.reset_confirm = False
- st.success("✅ 数据已重置")
- st.rerun()
- else:
- st.session_state.reset_confirm = False
- st.error("❌ 请先确认重置操作")
- # 统计信息
- st.subheader("📈 统计信息")
- col1, col2, col3, col4 = st.columns(4)
-
- with col1:
- st.metric("总产品数", len(st.session_state.advanced_df))
-
- with col2:
- available_count = len(st.session_state.advanced_df[st.session_state.advanced_df['Available']])
- st.metric("可用产品", available_count)
-
- with col3:
- avg_price = st.session_state.advanced_df['Price'].mean()
- st.metric("平均价格", f"${avg_price:.2f}")
-
- with col4:
- total_stock = st.session_state.advanced_df['Stock'].sum()
- st.metric("总库存", f"{total_stock:,}件")
- def editable_dataframe_with_validation():
- st.title("✅ 带数据验证的可编辑DataFrame")
-
- # 初始化数据
- if 'validation_df' not in st.session_state:
- st.session_state.validation_df = pd.DataFrame({
- 'Email': ['user1@example.com', 'user2@example.com', 'user3@example.com'],
- 'Phone': ['+1-555-0123', '+1-555-0124', '+1-555-0125'],
- 'Website': ['https://www.example1.com', 'https://www.example2.com', 'https://www.example3.com'],
- 'Score': [85, 92, 78],
- 'Grade': ['B', 'A', 'C']
- })
-
- st.write("🔒 此表格包含数据验证规则:")
-
- # 显示验证规则
- with st.expander("📋 查看验证规则"):
- st.markdown("""
- - **Email**: 必须是有效的邮箱格式
- - **Phone**: 必须是 +X-XXX-XXXX 格式
- - **Website**: 必须是有效的URL格式
- - **Score**: 必须在 0-100 之间
- - **Grade**: 只能是 A, B, C, D, F
- """)
-
- # 带验证的编辑器
- edited_df = st.data_editor(
- st.session_state.validation_df,
- height=300,
- use_container_width=True,
- column_config={
- "Email": st.column_config.TextColumn(
- "邮箱",
- help="输入有效的邮箱地址",
- validate=r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$"
- ),
- "Phone": st.column_config.TextColumn(
- "电话",
- help="格式: +1-555-0123",
- validate=r"^\+\d{1,3}-\d{3}-\d{4}$"
- ),
- "Website": st.column_config.LinkColumn(
- "网站",
- help="输入完整的URL",
- validate=r"^https?://[^\s/$.?#].[^\s]*$"
- ),
- "Score": st.column_config.NumberColumn(
- "分数",
- help="输入 0-100 之间的分数",
- min_value=0,
- max_value=100,
- step=1
- ),
- "Grade": st.column_config.SelectboxColumn(
- "等级",
- help="选择等级",
- options=['A', 'B', 'C', 'D', 'F'],
- required=True
- )
- },
- key="validation_editor"
- )
-
- # 验证结果
- if st.button("🔍 验证数据"):
- errors = []
-
- # 邮箱验证
- import re
- email_pattern = r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$"
- for idx, email in enumerate(edited_df['Email']):
- if not re.match(email_pattern, str(email)):
- errors.append(f"第{idx+1}行: 无效的邮箱格式")
-
- # 电话验证
- phone_pattern = r"^\+\d{1,3}-\d{3}-\d{4}$"
- for idx, phone in enumerate(edited_df['Phone']):
- if not re.match(phone_pattern, str(phone)):
- errors.append(f"第{idx+1}行: 无效的电话格式")
-
- if errors:
- st.error("❌ 发现以下错误:")
- for error in errors:
- st.error(f"• {error}")
- else:
- st.success("✅ 所有数据验证通过!")
- page = st.radio("Select Page", [
- "🔧 Editable DataFrame - add rows",
- "🔧 Editable DataFrame - add rows (fixed)",
- "🔧 Editable DataFrame - Basic",
- "🔧 Editable DataFrame - Advanced",
- "✅ Editable DataFrame - With Validation"
- ])
- if page == "🔧 Editable DataFrame - add rows":
- editable_dataframe_add()
- elif page == "🔧 Editable DataFrame - add rows (fixed)":
- editable_dataframe_add_fixed()
- elif page == "🔧 Editable DataFrame - Basic":
- editable_dataframe_basic()
- elif page == "🔧 Editable DataFrame - Advanced":
- editable_dataframe_advanced()
- elif page == "✅ Editable DataFrame - With Validation":
- editable_dataframe_with_validation()
|