解决Streamlit中点击编辑/删除按钮后界面延迟更新问题
问题修复方案
问题根源
- Edit按钮需两次点击:原代码中
st.stop()的使用时机导致页面提前终止渲染,edit_id的更新无法即时触发表单区域重新加载 - Delete后物料不即时消失:同样因
st.stop()中断脚本执行,删除操作后的物料列表无法即时刷新
修复后的完整代码
from datetime import datetime as dt import streamlit as st import pandas as pd from io import BytesIO import uuid st.set_page_config(layout="wide") st.title("Material Creation") # ------------------------------------------------- # Session state initialization # ------------------------------------------------- if "materials_df" not in st.session_state: st.session_state.materials_df = pd.DataFrame() if "edit_id" not in st.session_state: st.session_state.edit_id = None if "custom_inputs" not in st.session_state: st.session_state.custom_inputs = {} if "delete_uid" not in st.session_state: st.session_state.delete_uid = None if "edit_uid" not in st.session_state: st.session_state.edit_uid = None # ------------------------------------------------- # Load source Excel # ------------------------------------------------- @st.cache_data def load_data(path="Sample.xlsx"): return pd.read_excel(path) df = load_data("Sample.xlsx") # ------------------------------------------------- # Menu headers # ------------------------------------------------- menu_config_headers = { 'Polymer': ['Manufacturer', 'Subtype', 'Product Name', 'Grade', 'Color'], 'Coating': ['Manufacturer', 'Subtype', 'Product Name', 'Grade', 'Color'], 'Misc': ['Manufacturer', 'Subtype', 'Product Name', 'Grade', 'Color'], 'Solder Paste': ['Manufacturer', 'Grade', 'Flux', 'Metal Load', 'Powder Size', 'Recycled Content', 'Solder Alloy'], 'Solder Alloy': ['Manufacturer', 'Grade', 'Form', 'Recycled Content', 'Perform Size', 'Solder Alloy'], 'Shaped Metal': ['Manufacturer', 'Subtype', 'Product Name', 'Grade', 'Form', 'Metal/Alloy', 'Recycled Content', 'Standard and Standard Designating Body', 'Thickness'], 'Bulk Metal': ['Manufacturer', 'Grade', 'Form', 'Metal/Alloy', 'Purity/Concentration', 'Recycled Content', 'Standard and Standard Designating Body'], 'Plating': ['Manufacturer', 'Form', 'Manufacturer Performing Plating', 'Metal/Alloy', 'Purity/Concentration', 'Recycled Content'], 'Chemical': ['Manufacturer', 'Chemical Name / Description', 'Purity/Concentration'], 'Magnet': ['Manufacturer', 'Subtype', 'Grade', 'Recycled Content'], 'Process': ['Manufacturer', 'Subtype', 'Product Name', 'Grade'], 'Semiconductor': ['Manufacturer', 'Grade', 'Semiconductor Description', 'Semiconductor Type'], 'Surface Treatment Process': ['Manufacturer', 'Subtype', 'Color', 'Composition of Coating / "Target" Material', 'Purity/Concentration', 'Substrate: Base Metal', 'Surface Treatment Type / Specification'], 'Anodization': ['Manufacturer', 'Color', 'Anodization Details', 'Metal + "Anodizing"', 'Substrate: Base Metal'] } @st.cache_data def build_menu_config(df, headers): menu = {} for mat, cols in headers.items(): sub = df[df["Material"] == mat] menu[mat] = { c: sub[c].dropna().unique().tolist() if c in sub.columns else [] for c in cols } return menu menu_config = build_menu_config(df, menu_config_headers) # ------------------------------------------------- # 优先处理删除/编辑状态,确保渲染前完成状态更新 # ------------------------------------------------- if st.session_state.delete_uid: st.session_state.materials_df = st.session_state.materials_df[ st.session_state.materials_df["ID"] != st.session_state.delete_uid ] if st.session_state.edit_id == st.session_state.delete_uid: st.session_state.edit_id = None st.success("Material deleted") st.session_state.delete_uid = None if st.session_state.edit_uid: st.session_state.edit_id = st.session_state.edit_uid st.session_state.edit_uid = None # ------------------------------------------------- # Layout # ------------------------------------------------- form_col, preview_col = st.columns([1.2, 1]) # ================================================= # FORM # ================================================= with form_col: existing_row = None if st.session_state.edit_id: existing_row = st.session_state.materials_df[ st.session_state.materials_df["ID"] == st.session_state.edit_id ].squeeze() category = st.selectbox( "Type", [""] + list(menu_config.keys()), index=0 if existing_row is None else list(menu_config.keys()).index(existing_row["Type"]) + 1 ) selected_values = {} if category: for label, options in menu_config[category].items(): options_with_add = ["", "➕ Add new"] + options existing_value = existing_row[label] if existing_row is not None and label in existing_row else None choice = st.selectbox( label, options_with_add, index=options_with_add.index(existing_value) if existing_value in options_with_add else 0, key=f"select-{label}" ) if choice == "➕ Add new": custom_value = st.text_input( f"Enter new {label}", value=existing_value or "", key=f"input-{label}" ) selected_values[label] = custom_value else: selected_values[label] = choice or None record = {"Type": category, **selected_values} col1, col2 = st.columns(2) with col1: if st.button("Create Material"): if not st.session_state.edit_id: record["ID"] = str(uuid.uuid4()) st.session_state.materials_df = pd.concat( [st.session_state.materials_df, pd.DataFrame([record])], ignore_index=True ) else: row_idx = st.session_state.materials_df.index[ st.session_state.materials_df["ID"] == st.session_state.edit_id ][0] for k, v in record.items(): st.session_state.materials_df.loc[row_idx, k] = v st.session_state.edit_id = None st.success("Material saved") with col2: if not st.session_state.materials_df.empty: output = BytesIO() with pd.ExcelWriter(output, engine="xlsxwriter") as writer: st.session_state.materials_df.drop(columns=["ID"], errors="ignore").to_excel( writer, index=False, sheet_name="Materials" ) timestamp = dt.now().strftime("%Y%m%d") st.download_button( "Download materials", data=output.getvalue(), file_name=f"Materials_{timestamp}.xlsx", mime="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" ) # ================================================= # PREVIEW / EDIT / DELETE # ================================================= with preview_col: st.subheader("Created Materials") if st.session_state.materials_df.empty: st.info("No materials created yet.") else: for _, row in st.session_state.materials_df.iterrows(): uid = row["ID"] with st.container(): for field, value in row.dropna().items(): if field != "ID": st.markdown(f"**{field}:** {value}") btn_col1, btn_col2 = st.columns([1, 1]) with btn_col1: if st.button("Edit", key=f"edit-{uid}"): st.session_state.edit_uid = uid st.rerun() with btn_col2: if st.button("Delete", key=f"delete-{uid}"): st.session_state.delete_uid = uid st.rerun()
关键修改说明
- 调整状态处理顺序:将删除/编辑的状态更新逻辑移到页面渲染前,确保状态变更后再加载界面
- 替换
st.stop()为st.rerun():点击按钮后立即触发页面重新渲染,让状态变化即时显示 - 临时状态存储:用独立的
delete_uid/edit_uid临时存储操作目标ID,避免循环内直接修改状态导致的渲染异常
内容的提问来源于stack exchange,提问作者Maryna Longnickel
相关产品推荐
相关产品推荐

