浏览代码

Add multi-page functionality with user authentication and various report tools

- Implemented a login/logout system using Streamlit session state.
- Created a navigation structure for different pages including a dashboard, data editor, and a new table report.
- Developed a scrollable DataFrame example with multiple variations, including custom CSS styling and Plotly integration.
- Added a history tracking page that simulates user actions and displays activity logs with filtering options.
- Introduced a search tool with advanced filtering capabilities for various categories and tags.
- Included a theme testing page demonstrating various text formatting and callback examples.
zhch158_admin 2 月之前
父节点
当前提交
fe7eb177c3

+ 322 - 0
streamlit_test/container滚动条.md

@@ -0,0 +1,322 @@
+是的,[`st.container`](/opt/miniconda3/envs/py312/lib/python3.12/site-packages/streamlit/__init__.py )默认情况下不支持横向滚动。它只支持垂直滚动。让我为您演示几种解决方案:
+
+````python
+import streamlit as st
+
+def container_default():
+    st.title("📦 默认Container - 只有垂直滚动")
+    
+    long_text = "Lorem ipsum. 12345678901234567890123456789012345678901234567890123456789012345678901234567890" * 1000
+
+    with st.container(height=300, width=600, border=True):
+        st.markdown(long_text)
+    
+    st.write("☝️ 上面的容器只能垂直滚动,长文本会自动换行")
+
+def container_with_horizontal_scroll():
+    st.title("↔️ 使用CSS实现横向滚动")
+    
+    # 方案1:真正的双向滚动(修复版)
+    st.subheader("🔄 双向滚动 - 修复版")
+    
+    # 创建既宽又高的内容
+    wide_content = """
+    <div style="height: 300px; width: 600px; overflow: auto; border: 2px solid #4CAF50; border-radius: 10px; padding: 15px; background-color: #f9f9f9;">
+        <div style="min-width: 1200px; min-height: 800px;">
+            <h3>这个容器支持横向和纵向滚动</h3>
+            <p>横向内容:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA</p>
+            <p>正常换行内容:Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>
+            <table style="border-collapse: collapse; margin: 20px 0; min-width: 1000px;">
+                <tr style="background-color: #e6f3ff;">
+                    <th style="border: 1px solid #ddd; padding: 8px;">超长列名1超长列名1超长列名1</th>
+                    <th style="border: 1px solid #ddd; padding: 8px;">超长列名2超长列名2超长列名2</th>
+                    <th style="border: 1px solid #ddd; padding: 8px;">超长列名3超长列名3超长列名3</th>
+                    <th style="border: 1px solid #ddd; padding: 8px;">超长列名4超长列名4超长列名4</th>
+                    <th style="border: 1px solid #ddd; padding: 8px;">超长列名5超长列名5超长列名5</th>
+                </tr>
+    """
+    
+    # 添加多行表格数据
+    for i in range(50):
+        wide_content += f"""
+                <tr>
+                    <td style="border: 1px solid #ddd; padding: 8px;">数据{i}-1数据{i}-1数据{i}-1</td>
+                    <td style="border: 1px solid #ddd; padding: 8px;">数据{i}-2数据{i}-2数据{i}-2</td>
+                    <td style="border: 1px solid #ddd; padding: 8px;">数据{i}-3数据{i}-3数据{i}-3</td>
+                    <td style="border: 1px solid #ddd; padding: 8px;">数据{i}-4数据{i}-4数据{i}-4</td>
+                    <td style="border: 1px solid #ddd; padding: 8px;">数据{i}-5数据{i}-5数据{i}-5</td>
+                </tr>
+        """
+    
+    wide_content += """
+            </table>
+            <p>更多垂直内容...</p>
+            <div style="height: 200px; background: linear-gradient(45deg, #ff9a9e 0%, #fecfef 50%, #fecfef 100%); border-radius: 10px; margin: 20px 0; display: flex; align-items: center; justify-content: center;">
+                <h2>这是一个高度为200px的彩色区域</h2>
+            </div>
+        </div>
+    </div>
+    """
+    
+    # 添加滚动条样式
+    st.markdown("""
+    <style>
+    .dual-scroll {
+        height: 300px;
+        width: 600px;
+        overflow: auto;
+        border: 2px solid #4CAF50;
+        border-radius: 10px;
+        padding: 15px;
+        background-color: #f9f9f9;
+    }
+    
+    .dual-scroll::-webkit-scrollbar {
+        width: 12px;
+        height: 12px;
+    }
+    
+    .dual-scroll::-webkit-scrollbar-track {
+        background: #f1f1f1;
+        border-radius: 10px;
+    }
+    
+    .dual-scroll::-webkit-scrollbar-thumb {
+        background: #888;
+        border-radius: 10px;
+    }
+    
+    .dual-scroll::-webkit-scrollbar-thumb:hover {
+        background: #555;
+    }
+    
+    .dual-scroll::-webkit-scrollbar-corner {
+        background: #f1f1f1;
+    }
+    </style>
+    """, unsafe_allow_html=True)
+    
+    st.markdown(wide_content, unsafe_allow_html=True)
+    
+    st.write("☝️ 上面的容器支持真正的横向和纵向双向滚动")
+    
+    # 方案2:只有横向滚动的版本(原来的)
+    st.subheader("↔️ 只有横向滚动版本")
+    
+    long_text_no_wrap = "Lorem ipsum dolor sit amet consectetur adipiscing elit " * 50
+    
+    st.markdown("""
+    <style>
+    .horizontal-only {
+        height: 100px;
+        width: 600px;
+        overflow-x: auto;
+        overflow-y: hidden;
+        border: 2px solid #FF6B6B;
+        border-radius: 10px;
+        padding: 15px;
+        background-color: #fff5f5;
+        white-space: nowrap;
+    }
+    
+    .horizontal-only::-webkit-scrollbar {
+        height: 12px;
+    }
+    
+    .horizontal-only::-webkit-scrollbar-track {
+        background: #f1f1f1;
+        border-radius: 10px;
+    }
+    
+    .horizontal-only::-webkit-scrollbar-thumb {
+        background: #ff6b6b;
+        border-radius: 10px;
+    }
+    </style>
+    """, unsafe_allow_html=True)
+    
+    st.markdown(f'<div class="horizontal-only">{long_text_no_wrap}</div>', unsafe_allow_html=True)
+    
+    st.write("☝️ 上面的容器只支持横向滚动")
+    
+    # 方案3:垂直滚动版本
+    st.subheader("↕️ 只有垂直滚动版本")
+    
+    vertical_content = """
+    <div style="height: 200px; width: 400px; overflow-y: auto; overflow-x: hidden; border: 2px solid #9B59B6; border-radius: 10px; padding: 15px; background-color: #f8f4ff;">
+    """
+    
+    for i in range(20):
+        vertical_content += f"<p>这是第{i+1}行文本内容,用于测试垂直滚动功能。</p>"
+    
+    vertical_content += "</div>"
+    
+    st.markdown(vertical_content, unsafe_allow_html=True)
+    
+    st.write("☝️ 上面的容器只支持垂直滚动")
+
+def container_code_block():
+    st.title("🔤 代码块容器 - 自带横向滚动")
+    
+    # 长代码行
+    long_code = """
+def very_long_function_name_that_exceeds_normal_width():
+    very_long_variable_name = "this is a very long string that will definitely exceed the container width and require horizontal scrolling to view completely"
+    another_very_long_variable = {"key1": "very_long_value_1", "key2": "very_long_value_2", "key3": "very_long_value_3"}
+    return very_long_variable_name + str(another_very_long_variable)
+
+# 更多长代码行
+print("This is a very long print statement that contains lots of text and will definitely require horizontal scrolling to see the complete content")
+"""
+    
+    with st.container(height=300, border=True):
+        st.code(long_code, language="python")
+    
+    st.write("☝️ 代码块自动支持横向滚动")
+
+def container_dataframe():
+    st.title("📊 DataFrame容器 - 自带横向滚动")
+    
+    import pandas as pd
+    
+    # 创建宽DataFrame
+    wide_df = pd.DataFrame({
+        f"很长的列名_{i}": [f"很长的数据内容_{j}_{i}" for j in range(20)] 
+        for i in range(15)
+    })
+    
+    with st.container(height=300, border=True):
+        st.dataframe(wide_df, use_container_width=True)
+    
+    st.write("☝️ DataFrame自动支持横向滚动")
+
+def container_custom_html():
+    st.title("🎨 自定义HTML容器")
+    
+    # 创建表格数据
+    table_html = """
+    <div style="height: 300px; width: 600px; overflow: auto; border: 2px solid #FF6B6B; border-radius: 10px; padding: 10px;">
+        <table style="min-width: 1200px; border-collapse: collapse;">
+            <tr style="background-color: #f2f2f2;">
+                <th style="border: 1px solid #ddd; padding: 8px; white-space: nowrap;">Very Long Column Header 1</th>
+                <th style="border: 1px solid #ddd; padding: 8px; white-space: nowrap;">Very Long Column Header 2</th>
+                <th style="border: 1px solid #ddd; padding: 8px; white-space: nowrap;">Very Long Column Header 3</th>
+                <th style="border: 1px solid #ddd; padding: 8px; white-space: nowrap;">Very Long Column Header 4</th>
+                <th style="border: 1px solid #ddd; padding: 8px; white-space: nowrap;">Very Long Column Header 5</th>
+                <th style="border: 1px solid #ddd; padding: 8px; white-space: nowrap;">Very Long Column Header 6</th>
+            </tr>
+    """
+    
+    # 添加表格行
+    for i in range(20):
+        table_html += f"""
+            <tr>
+                <td style="border: 1px solid #ddd; padding: 8px; white-space: nowrap;">Very long data content {i}-1</td>
+                <td style="border: 1px solid #ddd; padding: 8px; white-space: nowrap;">Very long data content {i}-2</td>
+                <td style="border: 1px solid #ddd; padding: 8px; white-space: nowrap;">Very long data content {i}-3</td>
+                <td style="border: 1px solid #ddd; padding: 8px; white-space: nowrap;">Very long data content {i}-4</td>
+                <td style="border: 1px solid #ddd; padding: 8px; white-space: nowrap;">Very long data content {i}-5</td>
+                <td style="border: 1px solid #ddd; padding: 8px; white-space: nowrap;">Very long data content {i}-6</td>
+            </tr>
+        """
+    
+    table_html += """
+        </table>
+    </div>
+    """
+    
+    st.markdown(table_html, unsafe_allow_html=True)
+    st.write("☝️ 自定义HTML表格支持横向和纵向滚动")
+
+def container_plotly_chart():
+    st.title("📈 Plotly图表 - 自带横向滚动")
+    
+    import plotly.graph_objects as go
+    import pandas as pd
+    
+    # 创建宽图表数据
+    categories = [f"Category_{i}" for i in range(30)]
+    values = [i * 10 + 50 for i in range(30)]
+    
+    fig = go.Figure(data=[
+        go.Bar(x=categories, y=values, name="Long Bar Chart")
+    ])
+    
+    fig.update_layout(
+        title="Very Wide Bar Chart with Many Categories",
+        width=1500,  # 设置很宽的图表
+        height=400,
+        xaxis_title="Categories (Very Long Names)",
+        yaxis_title="Values"
+    )
+    
+    with st.container(height=450, border=True):
+        st.plotly_chart(fig, use_container_width=False)
+    
+    st.write("☝️ Plotly图表在容器中自动支持横向滚动")
+
+def container_iframe():
+    st.title("🌐 iframe容器")
+    
+    iframe_html = """
+    <iframe 
+        src="https://www.openstreetmap.org/export/embed.html?bbox=-0.004017949104309082%2C51.47612752641776%2C0.00030577182769775%2C51.478569861898606&layer=mapnik" 
+        style="width: 800px; height: 300px; border: 2px solid #4CAF50; border-radius: 10px;"
+        scrolling="yes">
+    </iframe>
+    """
+    
+    with st.container(height=350, border=True):
+        st.markdown(iframe_html, unsafe_allow_html=True)
+    
+    st.write("☝️ iframe内容可以有自己的滚动条")
+
+# 页面选择
+page = st.radio("Select Container Type", [
+    "📦 Default Container (Vertical Only)",
+    "↔️ CSS Horizontal Scroll",
+    "🔤 Code Block Container",
+    "📊 DataFrame Container", 
+    "🎨 Custom HTML Container",
+    "📈 Plotly Chart Container",
+    "🌐 Iframe Container"
+])
+
+if page == "📦 Default Container (Vertical Only)":
+    container_default()
+elif page == "↔️ CSS Horizontal Scroll":
+    container_with_horizontal_scroll()
+elif page == "🔤 Code Block Container":
+    container_code_block()
+elif page == "📊 DataFrame Container":
+    container_dataframe()
+elif page == "🎨 Custom HTML Container":
+    container_custom_html()
+elif page == "📈 Plotly Chart Container":
+    container_plotly_chart()
+elif page == "🌐 Iframe Container":
+    container_iframe()
+````
+
+## 总结:
+
+### 🚫 **Streamlit Container的限制:**
+- [`st.container`](/opt/miniconda3/envs/py312/lib/python3.12/site-packages/streamlit/__init__.py )默认只支持垂直滚动
+- 不提供内置的横向滚动功能
+- 文本内容会自动换行
+
+### ✅ **实现横向滚动的方案:**
+
+1. **CSS方案**:使用`overflow-x: auto`和`white-space: nowrap`
+2. **代码块**:[`st.code()`](/opt/miniconda3/envs/py312/lib/python3.12/site-packages/streamlit/__init__.py )自带横向滚动
+3. **DataFrame**:[`st.dataframe()`](/opt/miniconda3/envs/py312/lib/python3.12/site-packages/streamlit/__init__.py )自带横向滚动
+4. **自定义HTML**:完全控制滚动行为
+5. **Plotly图表**:支持宽图表的横向滚动
+
+### 📝 **推荐方案:**
+- **文本内容**:使用CSS方案
+- **数据展示**:使用DataFrame或自定义HTML表格
+- **代码展示**:使用[`st.code()`](/opt/miniconda3/envs/py312/lib/python3.12/site-packages/streamlit/__init__.py )
+- **图表展示**:使用Plotly等支持滚动的图表库
+
+如果您需要在容器中显示特定类型的内容并需要横向滚动,请告诉我具体的使用场景,我可以为您提供更针对性的解决方案!

+ 297 - 0
streamlit_test/reports/container.py

@@ -0,0 +1,297 @@
+import streamlit as st
+
+def container_default():
+    st.title("📦 默认Container - 只有垂直滚动")
+    
+    long_text = "Lorem ipsum. 12345678901234567890123456789012345678901234567890123456789012345678901234567890" * 1000
+
+    with st.container(height=300, width=600, border=True):
+        st.markdown(long_text)
+    
+    st.write("☝️ 上面的容器只能垂直滚动,长文本会自动换行")
+
+def container_with_horizontal_scroll():
+    st.title("↔️ 使用CSS实现横向滚动")
+    
+    # 方案1:真正的双向滚动(修复版)
+    st.subheader("🔄 双向滚动 - 修复版")
+    
+    # 创建既宽又高的内容
+    long_text = "Lorem ipsum. 12345678901234567890123456789012345678901234567890123456789012345678901234567890" * 1000
+    wide_content = """
+    <div style="height: 300px; width: 600px; overflow: auto; border: 2px solid #4CAF50; border-radius: 10px; padding: 15px; background-color: #f9f9f9;">
+        <div style="min-width: 1200px; min-height: 800px;">
+            <h3>这个容器支持横向和纵向滚动</h3>
+            <p>横向内容:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA</p>
+            <p>正常换行内容:Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>
+            <table style="border-collapse: collapse; margin: 20px 0; min-width: 1000px;">
+                <tr style="background-color: #e6f3ff;">
+                    <th style="border: 1px solid #ddd; padding: 8px;">超长列名1超长列名1超长列名1</th>
+                    <th style="border: 1px solid #ddd; padding: 8px;">超长列名2超长列名2超长列名2</th>
+                    <th style="border: 1px solid #ddd; padding: 8px;">超长列名3超长列名3超长列名3</th>
+                    <th style="border: 1px solid #ddd; padding: 8px;">超长列名4超长列名4超长列名4</th>
+                    <th style="border: 1px solid #ddd; padding: 8px;">超长列名5超长列名5超长列名5</th>
+                </tr>
+    """
+    
+    # 添加多行表格数据
+    for i in range(50):
+        wide_content += f"""
+                <tr>
+                    <td style="border: 1px solid #ddd; padding: 8px;">数据{i}-1数据{i}-1数据{i}-1</td>
+                    <td style="border: 1px solid #ddd; padding: 8px;">数据{i}-2数据{i}-2数据{i}-2</td>
+                    <td style="border: 1px solid #ddd; padding: 8px;">数据{i}-3数据{i}-3数据{i}-3</td>
+                    <td style="border: 1px solid #ddd; padding: 8px;">数据{i}-4数据{i}-4数据{i}-4</td>
+                    <td style="border: 1px solid #ddd; padding: 8px;">数据{i}-5数据{i}-5数据{i}-5</td>
+                </tr>
+        """
+    
+    wide_content += """
+            </table>
+            <p>更多垂直内容...</p>
+            <div style="height: 200px; background: linear-gradient(45deg, #ff9a9e 0%, #fecfef 50%, #fecfef 100%); border-radius: 10px; margin: 20px 0; display: flex; align-items: center; justify-content: center;">
+                <h2>这是一个高度为200px的彩色区域</h2>
+            </div>
+        </div>
+    </div>
+    """
+    
+    # 添加滚动条样式
+    st.markdown("""
+    <style>
+    .dual-scroll {
+        height: 300px;
+        width: 600px;
+        overflow: auto;
+        border: 2px solid #4CAF50;
+        border-radius: 10px;
+        padding: 15px;
+        background-color: #f9f9f9;
+    }
+    
+    .dual-scroll::-webkit-scrollbar {
+        width: 12px;
+        height: 12px;
+    }
+    
+    .dual-scroll::-webkit-scrollbar-track {
+        background: #f1f1f1;
+        border-radius: 10px;
+    }
+    
+    .dual-scroll::-webkit-scrollbar-thumb {
+        background: #888;
+        border-radius: 10px;
+    }
+    
+    .dual-scroll::-webkit-scrollbar-thumb:hover {
+        background: #555;
+    }
+    
+    .dual-scroll::-webkit-scrollbar-corner {
+        background: #f1f1f1;
+    }
+    </style>
+    """, unsafe_allow_html=True)
+    
+    st.markdown(wide_content, unsafe_allow_html=True)
+    
+    st.write("☝️ 上面的容器支持真正的横向和纵向双向滚动")
+    
+    # 方案2:只有横向滚动的版本(原来的)
+    st.subheader("↔️ 只有横向滚动版本")
+    
+    long_text_no_wrap = "Lorem ipsum dolor sit amet consectetur adipiscing elit " * 50
+    
+    st.markdown("""
+    <style>
+    .horizontal-only {
+        height: 100px;
+        width: 600px;
+        overflow-x: auto;
+        overflow-y: hidden;
+        border: 2px solid #FF6B6B;
+        border-radius: 10px;
+        padding: 15px;
+        background-color: #fff5f5;
+        white-space: nowrap;
+    }
+    
+    .horizontal-only::-webkit-scrollbar {
+        height: 12px;
+    }
+    
+    .horizontal-only::-webkit-scrollbar-track {
+        background: #f1f1f1;
+        border-radius: 10px;
+    }
+    
+    .horizontal-only::-webkit-scrollbar-thumb {
+        background: #ff6b6b;
+        border-radius: 10px;
+    }
+    </style>
+    """, unsafe_allow_html=True)
+    
+    st.markdown(f'<div class="horizontal-only">{long_text_no_wrap}</div>', unsafe_allow_html=True)
+    
+    st.write("☝️ 上面的容器只支持横向滚动")
+    
+    # 方案3:垂直滚动版本
+    st.subheader("↕️ 只有垂直滚动版本")
+    
+    vertical_content = """
+    <div style="height: 200px; width: 400px; overflow-y: auto; overflow-x: hidden; border: 2px solid #9B59B6; border-radius: 10px; padding: 15px; background-color: #f8f4ff;">
+    """
+    
+    for i in range(20):
+        vertical_content += f"<p>这是第{i+1}行文本内容,用于测试垂直滚动功能。</p>"
+    
+    vertical_content += "</div>"
+    
+    st.markdown(vertical_content, unsafe_allow_html=True)
+    
+    st.write("☝️ 上面的容器只支持垂直滚动")
+
+
+def container_code_block():
+    st.title("🔤 代码块容器 - 自带横向滚动")
+    
+    # 长代码行
+    long_code = """
+def very_long_function_name_that_exceeds_normal_width():
+    very_long_variable_name = "this is a very long string that will definitely exceed the container width and require horizontal scrolling to view completely"
+    another_very_long_variable = {"key1": "very_long_value_1", "key2": "very_long_value_2", "key3": "very_long_value_3"}
+    return very_long_variable_name + str(another_very_long_variable)
+
+# 更多长代码行
+print("This is a very long print statement that contains lots of text and will definitely require horizontal scrolling to see the complete content")
+"""
+    
+    with st.container(height=300, border=True):
+        st.code(long_code, language="python")
+    
+    st.write("☝️ 代码块自动支持横向滚动")
+
+def container_dataframe():
+    st.title("📊 DataFrame容器 - 自带横向滚动")
+    
+    import pandas as pd
+    
+    # 创建宽DataFrame
+    wide_df = pd.DataFrame({
+        f"很长的列名_{i}": [f"很长的数据内容_{j}_{i}" for j in range(20)] 
+        for i in range(15)
+    })
+    
+    with st.container(height=300, border=True):
+        st.dataframe(wide_df, use_container_width=True)
+    
+    st.write("☝️ DataFrame自动支持横向滚动")
+
+def container_custom_html():
+    st.title("🎨 自定义HTML容器")
+    
+    # 创建表格数据
+    table_html = """
+    <div style="height: 300px; width: 600px; overflow: auto; border: 2px solid #FF6B6B; border-radius: 10px; padding: 10px;">
+        <table style="min-width: 1200px; border-collapse: collapse;">
+            <tr style="background-color: #f2f2f2;">
+                <th style="border: 1px solid #ddd; padding: 8px; white-space: nowrap;">Very Long Column Header 1</th>
+                <th style="border: 1px solid #ddd; padding: 8px; white-space: nowrap;">Very Long Column Header 2</th>
+                <th style="border: 1px solid #ddd; padding: 8px; white-space: nowrap;">Very Long Column Header 3</th>
+                <th style="border: 1px solid #ddd; padding: 8px; white-space: nowrap;">Very Long Column Header 4</th>
+                <th style="border: 1px solid #ddd; padding: 8px; white-space: nowrap;">Very Long Column Header 5</th>
+                <th style="border: 1px solid #ddd; padding: 8px; white-space: nowrap;">Very Long Column Header 6</th>
+            </tr>
+    """
+    
+    # 添加表格行
+    for i in range(20):
+        table_html += f"""
+            <tr>
+                <td style="border: 1px solid #ddd; padding: 8px; white-space: nowrap;">Very long data content {i}-1</td>
+                <td style="border: 1px solid #ddd; padding: 8px; white-space: nowrap;">Very long data content {i}-2</td>
+                <td style="border: 1px solid #ddd; padding: 8px; white-space: nowrap;">Very long data content {i}-3</td>
+                <td style="border: 1px solid #ddd; padding: 8px; white-space: nowrap;">Very long data content {i}-4</td>
+                <td style="border: 1px solid #ddd; padding: 8px; white-space: nowrap;">Very long data content {i}-5</td>
+                <td style="border: 1px solid #ddd; padding: 8px; white-space: nowrap;">Very long data content {i}-6</td>
+            </tr>
+        """
+    
+    table_html += """
+        </table>
+    </div>
+    """
+    
+    st.markdown(table_html, unsafe_allow_html=True)
+    st.write("☝️ 自定义HTML表格支持横向和纵向滚动")
+
+def container_plotly_chart():
+    st.title("📈 Plotly图表 - 自带横向滚动")
+    
+    import plotly.graph_objects as go
+    import pandas as pd
+    
+    # 创建宽图表数据
+    categories = [f"Category_{i}" for i in range(30)]
+    values = [i * 10 + 50 for i in range(30)]
+    
+    fig = go.Figure(data=[
+        go.Bar(x=categories, y=values, name="Long Bar Chart")
+    ])
+    
+    fig.update_layout(
+        title="Very Wide Bar Chart with Many Categories",
+        width=1500,  # 设置很宽的图表
+        height=400,
+        xaxis_title="Categories (Very Long Names)",
+        yaxis_title="Values"
+    )
+    
+    with st.container(height=450, border=True):
+        st.plotly_chart(fig, use_container_width=False)
+    
+    st.write("☝️ Plotly图表在容器中自动支持横向滚动")
+
+def container_iframe():
+    st.title("🌐 iframe容器")
+    
+    iframe_html = """
+    <iframe 
+        src="https://www.openstreetmap.org/export/embed.html?bbox=-0.004017949104309082%2C51.47612752641776%2C0.00030577182769775%2C51.478569861898606&layer=mapnik" 
+        style="width: 800px; height: 300px; border: 2px solid #4CAF50; border-radius: 10px;"
+        scrolling="yes">
+    </iframe>
+    """
+    
+    with st.container(height=350, border=True):
+        st.markdown(iframe_html, unsafe_allow_html=True)
+    
+    st.write("☝️ iframe内容可以有自己的滚动条")
+
+# 页面选择
+page = st.radio("Select Container Type", [
+    "📦 Default Container (Vertical Only)",
+    "↔️ CSS Horizontal Scroll",
+    "🔤 Code Block Container",
+    "📊 DataFrame Container", 
+    "🎨 Custom HTML Container",
+    "📈 Plotly Chart Container",
+    "🌐 Iframe Container"
+])
+
+if page == "📦 Default Container (Vertical Only)":
+    container_default()
+elif page == "↔️ CSS Horizontal Scroll":
+    container_with_horizontal_scroll()
+elif page == "🔤 Code Block Container":
+    container_code_block()
+elif page == "📊 DataFrame Container":
+    container_dataframe()
+elif page == "🎨 Custom HTML Container":
+    container_custom_html()
+elif page == "📈 Plotly Chart Container":
+    container_plotly_chart()
+elif page == "🌐 Iframe Container":
+    container_iframe()

+ 75 - 0
streamlit_test/reports/dashboard.py

@@ -0,0 +1,75 @@
+import streamlit as st
+import pandas as pd
+import numpy as np
+import plotly.express as px
+from datetime import datetime, timedelta
+
+st.title("📊 Dashboard")
+st.markdown("欢迎来到系统仪表板")
+
+# 模拟数据
+@st.cache_data
+def load_dashboard_data():
+    dates = pd.date_range(start=datetime.now() - timedelta(days=30), periods=30)
+    data = {
+        'date': dates,
+        'users': np.random.randint(100, 1000, 30),
+        'revenue': np.random.uniform(1000, 5000, 30),
+        'errors': np.random.randint(0, 50, 30)
+    }
+    return pd.DataFrame(data)
+
+# 加载数据
+df = load_dashboard_data()
+
+# 关键指标
+col1, col2, col3, col4 = st.columns(4)
+
+with col1:
+    st.metric(
+        label="总用户数",
+        value=f"{df['users'].sum():,}",
+        delta=f"{df['users'].iloc[-1] - df['users'].iloc[-2]:+d}"
+    )
+
+with col2:
+    st.metric(
+        label="总收入",
+        value=f"${df['revenue'].sum():,.2f}",
+        delta=f"${df['revenue'].iloc[-1] - df['revenue'].iloc[-2]:+.2f}"
+    )
+
+with col3:
+    st.metric(
+        label="平均错误数",
+        value=f"{df['errors'].mean():.1f}",
+        delta=f"{df['errors'].iloc[-1] - df['errors'].mean():.1f}"
+    )
+
+with col4:
+    st.metric(
+        label="系统状态",
+        value="正常",
+        delta="99.9% 正常运行时间"
+    )
+
+# 图表
+st.subheader("📈 趋势分析")
+
+tab1, tab2, tab3 = st.tabs(["用户趋势", "收入趋势", "错误趋势"])
+
+with tab1:
+    fig_users = px.line(df, x='date', y='users', title='用户数量趋势')
+    st.plotly_chart(fig_users, use_container_width=True)
+
+with tab2:
+    fig_revenue = px.bar(df, x='date', y='revenue', title='收入趋势')
+    st.plotly_chart(fig_revenue, use_container_width=True)
+
+with tab3:
+    fig_errors = px.scatter(df, x='date', y='errors', title='错误数量趋势')
+    st.plotly_chart(fig_errors, use_container_width=True)
+
+# 数据表格
+st.subheader("📋 详细数据")
+st.dataframe(df, use_container_width=True)

+ 586 - 0
streamlit_test/reports/data_editor.py

@@ -0,0 +1,586 @@
+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()

+ 744 - 0
streamlit_test/reports/dataframe_diff.py

@@ -0,0 +1,744 @@
+import streamlit as st
+import pandas as pd
+import numpy as np
+from typing import Dict, List, Tuple, Optional, Set
+import difflib
+from dataclasses import dataclass
+from enum import Enum
+
+class ChangeType(Enum):
+    UNCHANGED = "unchanged"
+    MODIFIED = "modified"
+    ADDED = "added"
+    REMOVED = "removed"
+
+@dataclass
+class CellDiff:
+    row_left: Optional[int]
+    row_right: Optional[int] 
+    column: str
+    change_type: ChangeType
+    old_value: any = None
+    new_value: any = None
+    similarity: float = 0.0
+
+@dataclass
+class RowDiff:
+    row_left: Optional[int]
+    row_right: Optional[int]
+    change_type: ChangeType
+    similarity: float = 0.0
+    cell_diffs: List[CellDiff] = None
+
+class DataFrameDiffAlgorithm:
+    """类似VSCode的DataFrame差异算法"""
+    
+    def __init__(self, similarity_threshold: float = 0.7):
+        self.similarity_threshold = similarity_threshold
+    
+    def compute_diff(self, df_left: pd.DataFrame, df_right: pd.DataFrame) -> List[RowDiff]:
+        """计算两个DataFrame的差异,从上到下寻找最匹配的行"""
+        
+        # 确保列对齐
+        all_columns = list(set(df_left.columns) | set(df_right.columns))
+        df_left_aligned = self._align_columns(df_left, all_columns)
+        df_right_aligned = self._align_columns(df_right, all_columns)
+        
+        # 计算行相似度矩阵
+        similarity_matrix = self._compute_similarity_matrix(df_left_aligned, df_right_aligned)
+        
+        # 从上到下匹配行
+        row_mappings = self._match_rows_top_down(similarity_matrix)
+        
+        # 生成差异结果
+        return self._generate_diff_result(df_left_aligned, df_right_aligned, row_mappings, all_columns)
+    
+    def _align_columns(self, df: pd.DataFrame, all_columns: List[str]) -> pd.DataFrame:
+        """对齐DataFrame列"""
+        aligned_df = df.copy()
+        for col in all_columns:
+            if col not in aligned_df.columns:
+                aligned_df[col] = None
+        return aligned_df[all_columns]
+    
+    def _compute_similarity_matrix(self, df_left: pd.DataFrame, df_right: pd.DataFrame) -> np.ndarray:
+        """计算行之间的相似度矩阵"""
+        matrix = np.zeros((len(df_left), len(df_right)))
+        
+        for i in range(len(df_left)):
+            for j in range(len(df_right)):
+                matrix[i, j] = self._compute_row_similarity(
+                    df_left.iloc[i], df_right.iloc[j]
+                )
+        
+        return matrix
+    
+    def _compute_row_similarity(self, row1: pd.Series, row2: pd.Series) -> float:
+        """计算两行的相似度"""
+        total_cols = len(row1)
+        if total_cols == 0:
+            return 1.0
+        
+        matches = 0
+        for col in row1.index:
+            val1, val2 = row1[col], row2[col]
+            
+            # 处理NaN值
+            if pd.isna(val1) and pd.isna(val2):
+                matches += 1
+            elif pd.isna(val1) or pd.isna(val2):
+                continue
+            else:
+                # 字符串相似度计算
+                str1, str2 = str(val1), str(val2)
+                if str1 == str2:
+                    matches += 1
+                else:
+                    # 使用difflib计算字符串相似度
+                    similarity = difflib.SequenceMatcher(None, str1, str2).ratio()
+                    matches += similarity * 0.5  # 部分匹配给予部分分数
+        
+        return matches / total_cols
+    
+    def _match_rows_top_down(self, similarity_matrix: np.ndarray) -> Dict[int, Optional[int]]:
+        """从上到下匹配行,优先匹配相似度高的行"""
+        left_rows, right_rows = similarity_matrix.shape
+        matched_right = set()
+        row_mappings = {}
+        
+        # 从上到下处理左侧每一行
+        for left_idx in range(left_rows):
+            best_right_idx = None
+            best_similarity = 0.0
+            
+            # 在未匹配的右侧行中寻找最佳匹配
+            for right_idx in range(right_rows):
+                if right_idx not in matched_right:
+                    similarity = similarity_matrix[left_idx, right_idx]
+                    if similarity > best_similarity and similarity >= self.similarity_threshold:
+                        best_similarity = similarity
+                        best_right_idx = right_idx
+            
+            if best_right_idx is not None:
+                row_mappings[left_idx] = best_right_idx
+                matched_right.add(best_right_idx)
+            else:
+                row_mappings[left_idx] = None  # 左侧行被删除
+        
+        return row_mappings
+    
+    def _generate_diff_result(self, df_left: pd.DataFrame, df_right: pd.DataFrame, 
+                            row_mappings: Dict[int, Optional[int]], all_columns: List[str]) -> List[RowDiff]:
+        """生成差异结果"""
+        result = []
+        matched_right_rows = set(row_mappings.values()) - {None}
+        
+        # 处理匹配的行和删除的行
+        for left_idx, right_idx in row_mappings.items():
+            if right_idx is None:
+                # 删除的行
+                result.append(RowDiff(
+                    row_left=left_idx,
+                    row_right=None,
+                    change_type=ChangeType.REMOVED,
+                    similarity=0.0
+                ))
+            else:
+                # 匹配的行 - 检查单元格差异
+                cell_diffs = self._compare_cells(
+                    df_left.iloc[left_idx], df_right.iloc[right_idx], 
+                    left_idx, right_idx, all_columns
+                )
+                
+                change_type = ChangeType.UNCHANGED
+                if any(cell.change_type != ChangeType.UNCHANGED for cell in cell_diffs):
+                    change_type = ChangeType.MODIFIED
+                
+                similarity = self._compute_row_similarity(df_left.iloc[left_idx], df_right.iloc[right_idx])
+                
+                result.append(RowDiff(
+                    row_left=left_idx,
+                    row_right=right_idx,
+                    change_type=change_type,
+                    similarity=similarity,
+                    cell_diffs=cell_diffs
+                ))
+        
+        # 处理新增的行(右侧未匹配的行)
+        for right_idx in range(len(df_right)):
+            if right_idx not in matched_right_rows:
+                result.append(RowDiff(
+                    row_left=None,
+                    row_right=right_idx,
+                    change_type=ChangeType.ADDED,
+                    similarity=0.0
+                ))
+        
+        return result
+    
+    def _compare_cells(self, row_left: pd.Series, row_right: pd.Series, 
+                      left_idx: int, right_idx: int, columns: List[str]) -> List[CellDiff]:
+        """比较单元格差异"""
+        cell_diffs = []
+        
+        for col in columns:
+            val_left = row_left[col] if col in row_left.index else None
+            val_right = row_right[col] if col in row_right.index else None
+            
+            # 处理NaN值
+            if pd.isna(val_left) and pd.isna(val_right):
+                change_type = ChangeType.UNCHANGED
+            elif pd.isna(val_left):
+                change_type = ChangeType.ADDED
+            elif pd.isna(val_right):
+                change_type = ChangeType.REMOVED
+            elif str(val_left) == str(val_right):
+                change_type = ChangeType.UNCHANGED
+            else:
+                change_type = ChangeType.MODIFIED
+            
+            cell_diffs.append(CellDiff(
+                row_left=left_idx,
+                row_right=right_idx,
+                column=col,
+                change_type=change_type,
+                old_value=val_left,
+                new_value=val_right
+            ))
+        
+        return cell_diffs
+
+class VSCodeStyleDataFrameDiff:
+    """类似VSCode样式的DataFrame差异展示"""
+    
+    def __init__(self):
+        self.diff_algorithm = DataFrameDiffAlgorithm()
+        self._inject_css()
+    
+    def _inject_css(self):
+        """注入VSCode风格的CSS样式 + data_editor样式"""
+        st.markdown("""
+        <style>
+        /* VSCode风格的差异显示 */
+        .vscode-diff-container {
+            font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
+            font-size: 12px;
+            border: 1px solid #3c3c3c;
+            border-radius: 6px;
+            overflow: hidden;
+        }
+        
+        .diff-header {
+            background-color: #2d2d30;
+            color: #cccccc;
+            padding: 8px 12px;
+            font-weight: bold;
+            border-bottom: 1px solid #3c3c3c;
+        }
+        
+        .diff-content {
+            height: 500px;
+            overflow: auto;
+            background-color: #1e1e1e;
+        }
+        
+        .diff-table {
+            width: 100%;
+            border-collapse: collapse;
+            color: #cccccc;
+        }
+        
+        .diff-table th {
+            background-color: #2d2d30;
+            border: 1px solid #3c3c3c;
+            padding: 6px 8px;
+            text-align: left;
+            position: sticky;
+            top: 0;
+            z-index: 10;
+        }
+        
+        .diff-table td {
+            border: 1px solid #3c3c3c;
+            padding: 4px 8px;
+            white-space: nowrap;
+            position: relative;
+        }
+        
+        /* 差异颜色 - VSCode风格 */
+        .diff-added {
+            background-color: rgba(22, 160, 133, 0.2) !important;
+            border-left: 3px solid #16a085 !important;
+        }
+        
+        .diff-removed {
+            background-color: rgba(231, 76, 60, 0.2) !important;
+            border-left: 3px solid #e74c3c !important;
+        }
+        
+        .diff-modified {
+            background-color: rgba(241, 196, 15, 0.2) !important;
+            border-left: 3px solid #f1c40f !important;
+        }
+        
+        .diff-unchanged {
+            background-color: transparent;
+        }
+        
+        /* 行号样式 */
+        .line-number {
+            background-color: #2d2d30;
+            color: #858585;
+            text-align: right;
+            padding: 4px 8px;
+            border-right: 1px solid #3c3c3c;
+            user-select: none;
+            min-width: 40px;
+        }
+        
+        /* 悬停效果 */
+        .diff-table tbody tr:hover {
+            background-color: rgba(255, 255, 255, 0.05);
+        }
+        
+        /* 尝试通过CSS选择器为data_editor添加样式 */
+        div[data-testid="stDataFrame"] {
+            height: 500px;
+        }
+        
+        /* 针对特定行的data_editor样式 - 通过行索引 */
+        div[data-testid="stDataFrame"] tbody tr:nth-child(1) {
+            background-color: rgba(22, 160, 133, 0.1);
+        }
+        
+        div[data-testid="stDataFrame"] tbody tr:nth-child(2) {
+            background-color: rgba(241, 196, 15, 0.1);
+        }
+        
+        div[data-testid="stDataFrame"] tbody tr:nth-child(5) {
+            background-color: rgba(231, 76, 60, 0.1);
+        }
+        
+        /* 为data_editor添加差异指示器 */
+        .data-editor-wrapper {
+            position: relative;
+        }
+        
+        .diff-indicator-overlay {
+            position: absolute;
+            top: 0;
+            left: 0;
+            width: 4px;
+            height: 100%;
+            pointer-events: none;
+            z-index: 1000;
+        }
+        
+        .indicator-added { background-color: #16a085; }
+        .indicator-removed { background-color: #e74c3c; }
+        .indicator-modified { background-color: #f1c40f; }
+        
+        /* 滚动条样式 */
+        .diff-content::-webkit-scrollbar,
+        div[data-testid="stDataFrame"] ::-webkit-scrollbar {
+            width: 12px;
+            height: 12px;
+        }
+        
+        .diff-content::-webkit-scrollbar-track,
+        div[data-testid="stDataFrame"] ::-webkit-scrollbar-track {
+            background: #2d2d30;
+        }
+        
+        .diff-content::-webkit-scrollbar-thumb,
+        div[data-testid="stDataFrame"] ::-webkit-scrollbar-thumb {
+            background: #555555;
+            border-radius: 6px;
+        }
+        
+        .diff-content::-webkit-scrollbar-thumb:hover,
+        div[data-testid="stDataFrame"] ::-webkit-scrollbar-thumb:hover {
+            background: #777777;
+        }
+        </style>
+        """, unsafe_allow_html=True)
+    
+    def _create_editable_diff_view(self, row_diffs: List[RowDiff]):
+        """创建可编辑的差异视图"""
+        st.subheader("🔍 并排对比")
+        
+        left_col, right_col = st.columns(2)
+        
+        with left_col:
+            st.markdown("### ✏️ 可编辑版本 (左侧)")
+            
+            # 方案1: 使用标记版本的DataFrame
+            self._create_marked_data_editor(st.session_state.df_edited, row_diffs)
+        
+        with right_col:
+            st.markdown("### 📝 原始版本 (右侧)")
+            self._create_diff_table(row_diffs, 'right')
+    
+    def _create_marked_data_editor(self, df: pd.DataFrame, row_diffs: List[RowDiff]):
+        """创建带标记的data_editor(方案1:在数据中添加标记)"""
+        
+        # 创建带差异标记的DataFrame
+        marked_df = self._create_marked_dataframe(df, row_diffs)
+        
+        # 创建列配置
+        column_config = self._create_marked_column_config(marked_df, row_diffs)
+        
+        # 显示data_editor
+        edited_df = st.data_editor(
+            marked_df,
+            height=500,
+            use_container_width=True,
+            num_rows="dynamic",
+            column_config=column_config,
+            key="marked_diff_editor",
+            hide_index=False
+        )
+        
+        # 移除标记列,恢复原始数据格式
+        cleaned_df = self._clean_marked_dataframe(edited_df)
+        
+        # 检测数据变化并更新
+        if not cleaned_df.equals(st.session_state.df_edited):
+            st.session_state.df_edited = cleaned_df.copy()
+            st.rerun()
+    
+    def _create_marked_dataframe(self, df: pd.DataFrame, row_diffs: List[RowDiff]) -> pd.DataFrame:
+        """创建带差异标记的DataFrame"""
+        marked_df = df.copy()
+        
+        # 添加差异标记列
+        marked_df.insert(0, '🎨 差异类型', '')
+        
+        # 根据差异类型为每行添加标记
+        for row_diff in row_diffs:
+            if row_diff.row_right is not None and row_diff.row_right < len(marked_df):
+                if row_diff.change_type == ChangeType.ADDED:
+                    marked_df.iloc[row_diff.row_right, 0] = '🟢 新增'
+                elif row_diff.change_type == ChangeType.REMOVED:
+                    marked_df.iloc[row_diff.row_right, 0] = '🔴 删除'
+                elif row_diff.change_type == ChangeType.MODIFIED:
+                    marked_df.iloc[row_diff.row_right, 0] = '🟡 修改'
+                else:
+                    marked_df.iloc[row_diff.row_right, 0] = '⚪ 未变'
+        
+        return marked_df
+    
+    def _create_marked_column_config(self, marked_df: pd.DataFrame, row_diffs: List[RowDiff]) -> Dict:
+        """为带标记的DataFrame创建列配置"""
+        config = {}
+        
+        # 差异类型列配置
+        config['🎨 差异类型'] = st.column_config.SelectboxColumn(
+            '🎨 差异类型',
+            help="行的差异类型",
+            options=['⚪ 未变', '🟡 修改', '🟢 新增', '🔴 删除'],
+            disabled=True,  # 只读
+            width="small"
+        )
+        
+        # 其他列配置
+        for col in marked_df.columns[1:]:  # 跳过差异标记列
+            if marked_df[col].dtype in ['int64', 'float64']:
+                config[col] = st.column_config.NumberColumn(
+                    col,
+                    help=f"数值列: {col}",
+                    format="%.2f" if marked_df[col].dtype == 'float64' else "%d"
+                )
+            elif marked_df[col].dtype == 'bool':
+                config[col] = st.column_config.CheckboxColumn(
+                    col,
+                    help=f"布尔列: {col}"
+                )
+            else:
+                config[col] = st.column_config.TextColumn(
+                    col,
+                    help=f"文本列: {col}",
+                    max_chars=100
+                )
+        
+        return config
+    
+    def _clean_marked_dataframe(self, marked_df: pd.DataFrame) -> pd.DataFrame:
+        """移除标记列,恢复原始DataFrame格式"""
+        return marked_df.drop(columns=['🎨 差异类型'])
+    
+    def create_diff_view(self):
+        """创建差异对比视图"""
+        st.title("📊 VSCode风格 DataFrame 差异对比")
+        st.markdown("---")
+        
+        # 初始化数据
+        if 'df_original' not in st.session_state:
+            st.session_state.df_original = self._create_sample_data()
+        
+        if 'df_edited' not in st.session_state:
+            st.session_state.df_edited = st.session_state.df_original.copy()
+        
+        # 控制面板
+        self._create_control_panel()
+        
+        # 计算差异
+        row_diffs = self.diff_algorithm.compute_diff(
+            st.session_state.df_original, 
+            st.session_state.df_edited
+        )
+        
+        # 显示统计信息
+        self._display_diff_statistics(row_diffs)
+        
+        # 主要对比区域
+        self._create_main_diff_view(row_diffs)
+        
+        # 详细差异列表
+        self._create_detailed_diff_view(row_diffs)
+    
+    def _create_sample_data(self) -> pd.DataFrame:
+        """创建示例数据"""
+        return pd.DataFrame({
+            'ID': [1, 2, 3, 4, 5],
+            'Name': ['Alice', 'Bob', 'Charlie', 'David', 'Eve'],
+            'Age': [25, 30, 35, 40, 45],
+            'City': ['New York', 'London', 'Paris', 'Tokyo', 'Sydney'],
+            'Salary': [50000, 60000, 70000, 80000, 90000]
+        })
+    
+    def _create_control_panel(self):
+        """创建控制面板"""
+        with st.expander("🎛️ 控制面板", expanded=True):
+            col1, col2, col3, col4 = st.columns(4)
+            
+            with col1:
+                if st.button("🔄 重置数据"):
+                    st.session_state.df_original = self._create_sample_data()
+                    st.session_state.df_edited = st.session_state.df_original.copy()
+                    st.rerun()
+            
+            with col2:
+                if st.button("🎲 生成随机差异"):
+                    st.session_state.df_edited = self._create_random_diff()
+                    st.rerun()
+            
+            with col3:
+                similarity_threshold = st.slider(
+                    "相似度阈值", 
+                    min_value=0.1, 
+                    max_value=1.0, 
+                    value=0.7, 
+                    step=0.1
+                )
+                self.diff_algorithm.similarity_threshold = similarity_threshold
+            
+            with col4:
+                auto_scroll = st.checkbox("🔗 同步滚动", value=True)
+    
+    def _create_random_diff(self) -> pd.DataFrame:
+        """创建随机差异用于演示"""
+        df = st.session_state.df_original.copy()
+        
+        # 修改一些值
+        df.loc[1, 'Name'] = 'Robert'
+        df.loc[2, 'Age'] = 36
+        df.loc[3, 'City'] = 'Berlin'
+        
+        # 删除一行
+        df = df.drop(index=4)
+        
+        # 新增一行
+        new_row = pd.DataFrame({
+            'ID': [6], 'Name': ['Frank'], 'Age': [28], 
+            'City': ['Madrid'], 'Salary': [55000]
+        })
+        df = pd.concat([df, new_row], ignore_index=True)
+        
+        return df
+    
+    def _display_diff_statistics(self, row_diffs: List[RowDiff]):
+        """显示差异统计"""
+        stats = self._compute_diff_stats(row_diffs)
+        
+        col1, col2, col3, col4, col5 = st.columns(5)
+        
+        with col1:
+            st.metric("总行数", stats['total_rows'])
+        with col2:
+            st.metric("🟢 新增", stats['added_rows'], delta=stats['added_rows'])
+        with col3:
+            st.metric("🔴 删除", stats['removed_rows'], delta=-stats['removed_rows'])
+        with col4:
+            st.metric("🟡 修改", stats['modified_rows'])
+        with col5:
+            st.metric("⚪ 未变", stats['unchanged_rows'])
+    
+    def _compute_diff_stats(self, row_diffs: List[RowDiff]) -> Dict:
+        """计算差异统计"""
+        stats = {
+            'total_rows': len(row_diffs),
+            'added_rows': 0,
+            'removed_rows': 0,
+            'modified_rows': 0,
+            'unchanged_rows': 0
+        }
+        
+        for row_diff in row_diffs:
+            if row_diff.change_type == ChangeType.ADDED:
+                stats['added_rows'] += 1
+            elif row_diff.change_type == ChangeType.REMOVED:
+                stats['removed_rows'] += 1
+            elif row_diff.change_type == ChangeType.MODIFIED:
+                stats['modified_rows'] += 1
+            else:
+                stats['unchanged_rows'] += 1
+        
+        return stats
+    
+    def _create_main_diff_view(self, row_diffs: List[RowDiff]):
+        """创建主要差异视图"""
+        self._create_editable_diff_view(row_diffs)
+
+    def _create_diff_table(self, row_diffs: List[RowDiff], side: str):
+        """创建带差异高亮的表格"""
+        df = st.session_state.df_original if side == 'left' else st.session_state.df_edited
+        
+        # 构建HTML表格
+        html_table = self._build_diff_html_table(row_diffs, side, df)
+        
+        # 显示表格
+        st.markdown(f"""
+        <div class="vscode-diff-container">
+            <div class="diff-header">
+                {"原始版本" if side == 'left' else "编辑版本"} 
+                ({len(df)} 行)
+            </div>
+            <div class="diff-content">
+                {html_table}
+            </div>
+        </div>
+        """, unsafe_allow_html=True)
+    
+    def _build_diff_html_table(self, row_diffs: List[RowDiff], side: str, df: pd.DataFrame) -> str:
+        """构建差异HTML表格"""
+        if df.empty:
+            return "<p>数据为空</p>"
+        
+        html = '<table class="diff-table">'
+        
+        # 表头
+        html += '<thead><tr><th class="line-number">#</th>'
+        for col in df.columns:
+            html += f'<th>{col}</th>'
+        html += '</tr></thead><tbody>'
+        
+        # 根据差异类型构建行
+        for i, row_diff in enumerate(row_diffs):
+            row_idx = row_diff.row_left if side == 'left' else row_diff.row_right
+            
+            if row_idx is None:
+                continue  # 该侧没有对应行
+            
+            if row_idx >= len(df):
+                continue  # 超出范围
+                
+            # 确定行样式
+            row_class = self._get_row_css_class(row_diff.change_type)
+            
+            html += f'<tr class="{row_class}">'
+            html += f'<td class="line-number">{row_idx + 1}</td>'
+            
+            # 构建单元格
+            for col in df.columns:
+                cell_class = row_class
+                cell_value = df.iloc[row_idx][col]
+                
+                # 如果是修改的行,检查单元格级别的差异
+                if row_diff.change_type == ChangeType.MODIFIED and row_diff.cell_diffs:
+                    cell_diff = next((cd for cd in row_diff.cell_diffs if cd.column == col), None)
+                    if cell_diff:
+                        cell_class = self._get_cell_css_class(cell_diff.change_type)
+                
+                display_value = str(cell_value) if not pd.isna(cell_value) else ""
+                html += f'<td class="{cell_class}">{display_value}</td>'
+            
+            html += '</tr>'
+        
+        html += '</tbody></table>'
+        return html
+    
+    def _get_row_css_class(self, change_type: ChangeType) -> str:
+        """获取行的CSS类"""
+        mapping = {
+            ChangeType.ADDED: "diff-added",
+            ChangeType.REMOVED: "diff-removed", 
+            ChangeType.MODIFIED: "diff-modified",
+            ChangeType.UNCHANGED: "diff-unchanged"
+        }
+        return mapping.get(change_type, "diff-unchanged")
+    
+    def _get_cell_css_class(self, change_type: ChangeType) -> str:
+        """获取单元格的CSS类"""
+        return self._get_row_css_class(change_type)
+    
+    def _create_detailed_diff_view(self, row_diffs: List[RowDiff]):
+        """创建详细差异视图"""
+        st.markdown("---")
+        st.subheader("📋 详细差异列表")
+        
+        # 筛选选项
+        change_types = st.multiselect(
+            "显示的变更类型",
+            [ct.value for ct in ChangeType],
+            default=[ChangeType.ADDED.value, ChangeType.REMOVED.value, ChangeType.MODIFIED.value]
+        )
+        
+        filtered_diffs = [
+            rd for rd in row_diffs 
+            if rd.change_type.value in change_types
+        ]
+        
+        if not filtered_diffs:
+            st.info("没有符合条件的差异")
+            return
+        
+        # 显示差异详情
+        for i, row_diff in enumerate(filtered_diffs):
+            with st.expander(f"差异 {i+1}: {row_diff.change_type.value.upper()}", expanded=False):
+                self._display_row_diff_details(row_diff)
+    
+    def _display_row_diff_details(self, row_diff: RowDiff):
+        """显示行差异详情"""
+        col1, col2 = st.columns(2)
+        
+        with col1:
+            st.write("**位置信息:**")
+            st.write(f"- 左侧行: {row_diff.row_left}")
+            st.write(f"- 右侧行: {row_diff.row_right}")
+            st.write(f"- 变更类型: {row_diff.change_type.value}")
+            st.write(f"- 相似度: {row_diff.similarity:.2%}")
+        
+        with col2:
+            if row_diff.cell_diffs:
+                st.write("**单元格差异:**")
+                cell_diff_data = []
+                for cell_diff in row_diff.cell_diffs:
+                    if cell_diff.change_type != ChangeType.UNCHANGED:
+                        cell_diff_data.append({
+                            '列': cell_diff.column,
+                            '变更类型': cell_diff.change_type.value,
+                            '原值': str(cell_diff.old_value),
+                            '新值': str(cell_diff.new_value)
+                        })
+                
+                if cell_diff_data:
+                    st.dataframe(pd.DataFrame(cell_diff_data), use_container_width=True)
+
+def main():
+    """主函数"""
+    diff_viewer = VSCodeStyleDataFrameDiff()
+    diff_viewer.create_diff_view()
+
+if __name__ == "__main__":
+    main()

+ 566 - 0
streamlit_test/reports/dataframe_diff_v0.1.py

@@ -0,0 +1,566 @@
+import streamlit as st
+import pandas as pd
+import numpy as np
+from typing import Dict, List, Tuple, Optional
+import plotly.graph_objects as go
+from plotly.subplots import make_subplots
+import plotly.express as px
+
+def create_dataframe_diff_visualizer():
+    st.title("📊 DataFrame可视化比对工具")
+    st.markdown("---")
+    
+    # 初始化数据
+    if 'original_df' not in st.session_state:
+        st.session_state.original_df = create_sample_data()
+    
+    if 'edited_df' not in st.session_state:
+        st.session_state.edited_df = st.session_state.original_df.copy()
+    
+    # 控制面板
+    with st.expander("🎛️ 控制面板", expanded=True):
+        col1, col2, col3, col4 = st.columns(4)
+        
+        with col1:
+            if st.button("🔄 重置数据", type="secondary"):
+                st.session_state.original_df = create_sample_data()
+                st.session_state.edited_df = st.session_state.original_df.copy()
+                st.rerun()
+        
+        with col2:
+            if st.button("🎲 生成随机差异", type="secondary"):
+                st.session_state.edited_df = create_random_differences(st.session_state.original_df)
+                st.rerun()
+        
+        with col3:
+            sync_mode = st.checkbox("🔗 同步滚动", value=True)
+        
+        with col4:
+            show_stats = st.checkbox("📈 显示统计", value=True)
+    
+    # 分析差异
+    diff_analysis = analyze_dataframe_differences(
+        st.session_state.original_df, 
+        st.session_state.edited_df
+    )
+    
+    # 显示差异统计
+    if show_stats:
+        display_diff_statistics(diff_analysis)
+    
+    # 主要比对区域
+    st.subheader("📝 数据比对")
+    
+    # 使用两列布局
+    left_col, right_col = st.columns(2)
+    
+    with left_col:
+        st.markdown("### 📝 可编辑版本 (左侧)")
+        
+        # 可编辑的数据编辑器
+        edited_df = st.data_editor(
+            st.session_state.edited_df,
+            height=500,
+            use_container_width=True,
+            num_rows="dynamic",
+            key="left_editor",
+            column_config=create_column_config(st.session_state.edited_df)
+        )
+        
+        # 更新编辑后的数据
+        if not edited_df.equals(st.session_state.edited_df):
+            st.session_state.edited_df = edited_df.copy()
+            st.rerun()
+    
+    with right_col:
+        st.markdown("### 📊 原始版本 (右侧)")
+        
+        # 显示带差异高亮的原始数据
+        display_dataframe_with_diff_highlighting(
+            st.session_state.original_df,
+            diff_analysis,
+            "original"
+        )
+    
+    # 详细差异视图
+    st.markdown("---")
+    create_detailed_diff_view(diff_analysis)
+
+def create_sample_data() -> pd.DataFrame:
+    """创建示例数据"""
+    np.random.seed(42)
+    data = {
+        'ID': range(1, 21),
+        'Name': [f'Product_{i}' for i in range(1, 21)],
+        'Category': np.random.choice(['Electronics', 'Clothing', 'Food', 'Books'], 20),
+        'Price': np.round(np.random.uniform(10, 100, 20), 2),
+        'Stock': np.random.randint(0, 200, 20),
+        'Rating': np.round(np.random.uniform(1, 5, 20), 1),
+        'Active': np.random.choice([True, False], 20)
+    }
+    return pd.DataFrame(data)
+
+def create_random_differences(df: pd.DataFrame) -> pd.DataFrame:
+    """创建随机差异用于演示"""
+    modified_df = df.copy()
+    
+    # 随机修改一些单元格
+    num_changes = np.random.randint(5, 15)
+    
+    for _ in range(num_changes):
+        row_idx = np.random.randint(0, len(modified_df))
+        col_idx = np.random.randint(1, len(modified_df.columns))  # 跳过ID列
+        col_name = modified_df.columns[col_idx]
+        
+        if col_name == 'Name':
+            modified_df.loc[row_idx, col_name] = f'Modified_{row_idx}'
+        elif col_name == 'Category':
+            modified_df.loc[row_idx, col_name] = np.random.choice(['Modified_Cat', 'New_Category'])
+        elif col_name == 'Price':
+            modified_df.loc[row_idx, col_name] = np.round(np.random.uniform(10, 150), 2)
+        elif col_name == 'Stock':
+            modified_df.loc[row_idx, col_name] = np.random.randint(0, 300)
+        elif col_name == 'Rating':
+            modified_df.loc[row_idx, col_name] = np.round(np.random.uniform(1, 5), 1)
+        elif col_name == 'Active':
+            modified_df.loc[row_idx, col_name] = not modified_df.loc[row_idx, col_name]
+    
+    return modified_df
+
+def analyze_dataframe_differences(df1: pd.DataFrame, df2: pd.DataFrame) -> Dict:
+    """分析两个DataFrame之间的差异"""
+    
+    # 确保两个DataFrame具有相同的形状和列
+    if df1.shape != df2.shape:
+        st.warning("⚠️ 两个DataFrame的形状不匹配!")
+    
+    common_columns = list(set(df1.columns) & set(df2.columns))
+    
+    differences = {
+        'cell_differences': [],
+        'added_rows': [],
+        'removed_rows': [],
+        'column_differences': {
+            'added_columns': list(set(df2.columns) - set(df1.columns)),
+            'removed_columns': list(set(df1.columns) - set(df2.columns))
+        },
+        'summary': {
+            'total_differences': 0,
+            'modified_cells': 0,
+            'modified_rows': set(),
+            'modified_columns': set()
+        }
+    }
+    
+    # 比较相同大小的DataFrame
+    min_rows = min(len(df1), len(df2))
+    
+    for row_idx in range(min_rows):
+        for col in common_columns:
+            try:
+                val1 = df1.iloc[row_idx][col]
+                val2 = df2.iloc[row_idx][col]
+                
+                # 处理NaN值
+                if pd.isna(val1) and pd.isna(val2):
+                    continue
+                
+                if pd.isna(val1) or pd.isna(val2) or val1 != val2:
+                    differences['cell_differences'].append({
+                        'row': row_idx,
+                        'column': col,
+                        'original_value': val1,
+                        'new_value': val2,
+                        'change_type': determine_change_type(val1, val2)
+                    })
+                    
+                    differences['summary']['modified_cells'] += 1
+                    differences['summary']['modified_rows'].add(row_idx)
+                    differences['summary']['modified_columns'].add(col)
+                    
+            except Exception as e:
+                st.warning(f"比较时出错 (行{row_idx}, 列{col}): {e}")
+    
+    # 检查行数差异
+    if len(df1) > len(df2):
+        differences['removed_rows'] = list(range(len(df2), len(df1)))
+    elif len(df2) > len(df1):
+        differences['added_rows'] = list(range(len(df1), len(df2)))
+    
+    differences['summary']['total_differences'] = (
+        differences['summary']['modified_cells'] +
+        len(differences['added_rows']) +
+        len(differences['removed_rows']) +
+        len(differences['column_differences']['added_columns']) +
+        len(differences['column_differences']['removed_columns'])
+    )
+    
+    return differences
+
+def determine_change_type(val1, val2) -> str:
+    """确定变更类型"""
+    if pd.isna(val1):
+        return "added"
+    elif pd.isna(val2):
+        return "removed"
+    else:
+        return "modified"
+
+def create_column_config(df: pd.DataFrame) -> Dict:
+    """为data_editor创建列配置"""
+    config = {}
+    
+    for col in df.columns:
+        if df[col].dtype in ['int64', 'float64']:
+            config[col] = st.column_config.NumberColumn(
+                col,
+                help=f"数值列: {col}",
+                format="%.2f" if df[col].dtype == 'float64' else "%d"
+            )
+        elif df[col].dtype == 'bool':
+            config[col] = st.column_config.CheckboxColumn(
+                col,
+                help=f"布尔列: {col}"
+            )
+        else:
+            config[col] = st.column_config.TextColumn(
+                col,
+                help=f"文本列: {col}",
+                max_chars=100
+            )
+    
+    return config
+
+def display_dataframe_with_diff_highlighting(df: pd.DataFrame, diff_analysis: Dict, view_type: str):
+    """显示带差异高亮的DataFrame"""
+    
+    # 创建样式化的HTML表格
+    html_table = create_styled_diff_table(df, diff_analysis, view_type)
+    
+    # 自定义CSS样式
+    st.markdown("""
+    <style>
+    .diff-table {
+        height: 500px;
+        overflow: auto;
+        border: 1px solid #ddd;
+        border-radius: 5px;
+        font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
+        font-size: 12px;
+    }
+    
+    .diff-table table {
+        width: 100%;
+        border-collapse: collapse;
+        margin: 0;
+    }
+    
+    .diff-table th {
+        background-color: #f5f5f5;
+        border: 1px solid #ddd;
+        padding: 8px;
+        text-align: left;
+        position: sticky;
+        top: 0;
+        z-index: 10;
+    }
+    
+    .diff-table td {
+        border: 1px solid #ddd;
+        padding: 8px;
+        white-space: nowrap;
+    }
+    
+    /* 差异高亮样式 */
+    .cell-modified {
+        background-color: #fff3cd !important;
+        border: 2px solid #ffc107 !important;
+        position: relative;
+    }
+    
+    .cell-added {
+        background-color: #d4edda !important;
+        border: 2px solid #28a745 !important;
+    }
+    
+    .cell-removed {
+        background-color: #f8d7da !important;
+        border: 2px solid #dc3545 !important;
+    }
+    
+    .row-highlight {
+        background-color: #f8f9fa !important;
+    }
+    
+    /* 悬停效果 */
+    .diff-table td:hover {
+        background-color: #e3f2fd !important;
+        cursor: pointer;
+    }
+    
+    /* 差异标记 */
+    .diff-marker {
+        position: absolute;
+        top: 2px;
+        right: 2px;
+        width: 8px;
+        height: 8px;
+        border-radius: 50%;
+    }
+    
+    .marker-modified { background-color: #ffc107; }
+    .marker-added { background-color: #28a745; }
+    .marker-removed { background-color: #dc3545; }
+    </style>
+    """, unsafe_allow_html=True)
+    
+    # 显示表格
+    st.markdown(f'<div class="diff-table">{html_table}</div>', unsafe_allow_html=True)
+
+def create_styled_diff_table(df: pd.DataFrame, diff_analysis: Dict, view_type: str) -> str:
+    """创建带样式的差异表格HTML"""
+    
+    # 创建差异映射
+    diff_map = {}
+    for diff in diff_analysis['cell_differences']:
+        key = (diff['row'], diff['column'])
+        diff_map[key] = diff
+    
+    # 开始构建HTML
+    html = '<table>'
+    
+    # 表头
+    html += '<tr>'
+    for col in df.columns:
+        html += f'<th>{col}</th>'
+    html += '</tr>'
+    
+    # 表格行
+    for row_idx in range(len(df)):
+        row_class = "row-highlight" if row_idx in diff_analysis['summary']['modified_rows'] else ""
+        html += f'<tr class="{row_class}">'
+        
+        for col in df.columns:
+            value = df.iloc[row_idx][col]
+            cell_key = (row_idx, col)
+            
+            # 确定单元格样式
+            cell_class = ""
+            marker_class = ""
+            
+            if cell_key in diff_map:
+                diff_info = diff_map[cell_key]
+                change_type = diff_info['change_type']
+                
+                if change_type == "modified":
+                    cell_class = "cell-modified"
+                    marker_class = "marker-modified"
+                elif change_type == "added":
+                    cell_class = "cell-added"
+                    marker_class = "marker-added"
+                elif change_type == "removed":
+                    cell_class = "cell-removed"
+                    marker_class = "marker-removed"
+            
+            # 处理值显示
+            display_value = str(value) if not pd.isna(value) else ""
+            
+            # 构建单元格HTML
+            cell_html = f'<td class="{cell_class}" title="行{row_idx}, 列{col}: {display_value}">'
+            
+            if marker_class:
+                cell_html += f'<div class="diff-marker {marker_class}"></div>'
+            
+            cell_html += display_value
+            cell_html += '</td>'
+            
+            html += cell_html
+        
+        html += '</tr>'
+    
+    html += '</table>'
+    return html
+
+def display_diff_statistics(diff_analysis: Dict):
+    """显示差异统计信息"""
+    st.subheader("📈 差异统计")
+    
+    col1, col2, col3, col4, col5 = st.columns(5)
+    
+    with col1:
+        st.metric(
+            "总差异数", 
+            diff_analysis['summary']['total_differences'],
+            help="所有类型的差异总数"
+        )
+    
+    with col2:
+        st.metric(
+            "修改的单元格", 
+            diff_analysis['summary']['modified_cells'],
+            help="被修改的单元格数量"
+        )
+    
+    with col3:
+        st.metric(
+            "影响的行数", 
+            len(diff_analysis['summary']['modified_rows']),
+            help="包含差异的行数"
+        )
+    
+    with col4:
+        st.metric(
+            "影响的列数", 
+            len(diff_analysis['summary']['modified_columns']),
+            help="包含差异的列数"
+        )
+    
+    with col5:
+        added_rows = len(diff_analysis['added_rows'])
+        removed_rows = len(diff_analysis['removed_rows'])
+        row_diff = added_rows - removed_rows
+        st.metric(
+            "行数变化", 
+            f"+{added_rows}/-{removed_rows}",
+            delta=row_diff if row_diff != 0 else None
+        )
+
+def create_detailed_diff_view(diff_analysis: Dict):
+    """创建详细的差异视图"""
+    st.subheader("🔍 详细差异分析")
+    
+    if diff_analysis['summary']['total_differences'] == 0:
+        st.success("✅ 没有发现任何差异!")
+        return
+    
+    # 差异类型选择器
+    diff_types = []
+    if diff_analysis['cell_differences']:
+        diff_types.append("单元格差异")
+    if diff_analysis['added_rows']:
+        diff_types.append("新增行")
+    if diff_analysis['removed_rows']:
+        diff_types.append("删除行")
+    if diff_analysis['column_differences']['added_columns']:
+        diff_types.append("新增列")
+    if diff_analysis['column_differences']['removed_columns']:
+        diff_types.append("删除列")
+    
+    selected_diff_type = st.selectbox("选择要查看的差异类型", diff_types)
+    
+    # 显示相应的差异详情
+    if selected_diff_type == "单元格差异":
+        display_cell_differences(diff_analysis['cell_differences'])
+    elif selected_diff_type == "新增行":
+        st.info(f"新增了 {len(diff_analysis['added_rows'])} 行: {diff_analysis['added_rows']}")
+    elif selected_diff_type == "删除行":
+        st.warning(f"删除了 {len(diff_analysis['removed_rows'])} 行: {diff_analysis['removed_rows']}")
+    elif selected_diff_type == "新增列":
+        st.info(f"新增了列: {diff_analysis['column_differences']['added_columns']}")
+    elif selected_diff_type == "删除列":
+        st.warning(f"删除了列: {diff_analysis['column_differences']['removed_columns']}")
+
+def display_cell_differences(cell_differences: List[Dict]):
+    """显示单元格差异详情"""
+    if not cell_differences:
+        return
+    
+    st.write(f"共发现 {len(cell_differences)} 个单元格差异:")
+    
+    # 创建差异DataFrame用于显示
+    diff_data = []
+    for diff in cell_differences:
+        diff_data.append({
+            '位置': f"行{diff['row']}, 列{diff['column']}",
+            '列名': diff['column'],
+            '原始值': diff['original_value'],
+            '新值': diff['new_value'],
+            '变更类型': diff['change_type']
+        })
+    
+    diff_df = pd.DataFrame(diff_data)
+    
+    # 使用颜色编码的表格
+    st.dataframe(
+        diff_df,
+        use_container_width=True,
+        height=300,
+        column_config={
+            '位置': st.column_config.TextColumn('位置', help='差异的具体位置'),
+            '列名': st.column_config.TextColumn('列名'),
+            '原始值': st.column_config.TextColumn('原始值'),
+            '新值': st.column_config.TextColumn('新值'),
+            '变更类型': st.column_config.TextColumn('变更类型')
+        }
+    )
+    
+    # 导出差异报告
+    if st.button("📥 导出差异报告"):
+        csv_data = diff_df.to_csv(index=False)
+        st.download_button(
+            label="下载CSV格式差异报告",
+            data=csv_data,
+            file_name="dataframe_diff_report.csv",
+            mime="text/csv"
+        )
+
+def create_plotly_diff_heatmap(diff_analysis: Dict, df_shape: Tuple[int, int]):
+    """创建差异热力图"""
+    if not diff_analysis['cell_differences']:
+        return None
+    
+    # 创建差异矩阵
+    diff_matrix = np.zeros(df_shape)
+    
+    for diff in diff_analysis['cell_differences']:
+        row, col = diff['row'], df_shape[1] - 1  # 简化处理
+        if diff['change_type'] == 'modified':
+            diff_matrix[row, col] = 1
+        elif diff['change_type'] == 'added':
+            diff_matrix[row, col] = 2
+        elif diff['change_type'] == 'removed':
+            diff_matrix[row, col] = 3
+    
+    fig = go.Figure(data=go.Heatmap(
+        z=diff_matrix,
+        colorscale=[[0, 'white'], [0.33, 'yellow'], [0.66, 'green'], [1, 'red']],
+        showscale=True,
+        colorbar=dict(
+            title="差异类型",
+            tickmode="array",
+            tickvals=[0, 1, 2, 3],
+            ticktext=["无差异", "修改", "新增", "删除"]
+        )
+    ))
+    
+    fig.update_layout(
+        title="DataFrame差异热力图",
+        xaxis_title="列",
+        yaxis_title="行",
+        height=400
+    )
+    
+    return fig
+
+# 主函数
+def main():
+    create_dataframe_diff_visualizer()
+    
+    # 可选:添加热力图视图
+    if st.checkbox("🔥 显示差异热力图"):
+        if 'original_df' in st.session_state and 'edited_df' in st.session_state:
+            diff_analysis = analyze_dataframe_differences(
+                st.session_state.original_df, 
+                st.session_state.edited_df
+            )
+            
+            heatmap_fig = create_plotly_diff_heatmap(
+                diff_analysis, 
+                st.session_state.original_df.shape
+            )
+            
+            if heatmap_fig:
+                st.plotly_chart(heatmap_fig, use_container_width=True)
+
+if __name__ == "__main__":
+    main()

+ 623 - 0
streamlit_test/reports/dataframe_diff_v0.2.py

@@ -0,0 +1,623 @@
+import streamlit as st
+import pandas as pd
+import numpy as np
+from typing import Dict, List, Tuple, Optional, Set
+import difflib
+from dataclasses import dataclass
+from enum import Enum
+import hashlib
+
+class ChangeType(Enum):
+    UNCHANGED = "unchanged"
+    MODIFIED = "modified"
+    ADDED = "added"
+    REMOVED = "removed"
+
+@dataclass
+class CellDiff:
+    row_left: Optional[int]
+    row_right: Optional[int] 
+    column: str
+    change_type: ChangeType
+    old_value: any = None
+    new_value: any = None
+    similarity: float = 0.0
+
+@dataclass
+class RowDiff:
+    row_left: Optional[int]
+    row_right: Optional[int]
+    change_type: ChangeType
+    similarity: float = 0.0
+    cell_diffs: List[CellDiff] = None
+
+class DataFrameDiffAlgorithm:
+    """类似VSCode的DataFrame差异算法"""
+    
+    def __init__(self, similarity_threshold: float = 0.7):
+        self.similarity_threshold = similarity_threshold
+    
+    def compute_diff(self, df_left: pd.DataFrame, df_right: pd.DataFrame) -> List[RowDiff]:
+        """计算两个DataFrame的差异,从上到下寻找最匹配的行"""
+        
+        # 确保列对齐
+        all_columns = list(set(df_left.columns) | set(df_right.columns))
+        df_left_aligned = self._align_columns(df_left, all_columns)
+        df_right_aligned = self._align_columns(df_right, all_columns)
+        
+        # 计算行相似度矩阵
+        similarity_matrix = self._compute_similarity_matrix(df_left_aligned, df_right_aligned)
+        
+        # 从上到下匹配行
+        row_mappings = self._match_rows_top_down(similarity_matrix)
+        
+        # 生成差异结果
+        return self._generate_diff_result(df_left_aligned, df_right_aligned, row_mappings, all_columns)
+    
+    def _align_columns(self, df: pd.DataFrame, all_columns: List[str]) -> pd.DataFrame:
+        """对齐DataFrame列"""
+        aligned_df = df.copy()
+        for col in all_columns:
+            if col not in aligned_df.columns:
+                aligned_df[col] = None
+        return aligned_df[all_columns]
+    
+    def _compute_similarity_matrix(self, df_left: pd.DataFrame, df_right: pd.DataFrame) -> np.ndarray:
+        """计算行之间的相似度矩阵"""
+        matrix = np.zeros((len(df_left), len(df_right)))
+        
+        for i in range(len(df_left)):
+            for j in range(len(df_right)):
+                matrix[i, j] = self._compute_row_similarity(
+                    df_left.iloc[i], df_right.iloc[j]
+                )
+        
+        return matrix
+    
+    def _compute_row_similarity(self, row1: pd.Series, row2: pd.Series) -> float:
+        """计算两行的相似度"""
+        total_cols = len(row1)
+        if total_cols == 0:
+            return 1.0
+        
+        matches = 0
+        for col in row1.index:
+            val1, val2 = row1[col], row2[col]
+            
+            # 处理NaN值
+            if pd.isna(val1) and pd.isna(val2):
+                matches += 1
+            elif pd.isna(val1) or pd.isna(val2):
+                continue
+            else:
+                # 字符串相似度计算
+                str1, str2 = str(val1), str(val2)
+                if str1 == str2:
+                    matches += 1
+                else:
+                    # 使用difflib计算字符串相似度
+                    similarity = difflib.SequenceMatcher(None, str1, str2).ratio()
+                    matches += similarity * 0.5  # 部分匹配给予部分分数
+        
+        return matches / total_cols
+    
+    def _match_rows_top_down(self, similarity_matrix: np.ndarray) -> Dict[int, Optional[int]]:
+        """从上到下匹配行,优先匹配相似度高的行"""
+        left_rows, right_rows = similarity_matrix.shape
+        matched_right = set()
+        row_mappings = {}
+        
+        # 从上到下处理左侧每一行
+        for left_idx in range(left_rows):
+            best_right_idx = None
+            best_similarity = 0.0
+            
+            # 在未匹配的右侧行中寻找最佳匹配
+            for right_idx in range(right_rows):
+                if right_idx not in matched_right:
+                    similarity = similarity_matrix[left_idx, right_idx]
+                    if similarity > best_similarity and similarity >= self.similarity_threshold:
+                        best_similarity = similarity
+                        best_right_idx = right_idx
+            
+            if best_right_idx is not None:
+                row_mappings[left_idx] = best_right_idx
+                matched_right.add(best_right_idx)
+            else:
+                row_mappings[left_idx] = None  # 左侧行被删除
+        
+        return row_mappings
+    
+    def _generate_diff_result(self, df_left: pd.DataFrame, df_right: pd.DataFrame, 
+                            row_mappings: Dict[int, Optional[int]], all_columns: List[str]) -> List[RowDiff]:
+        """生成差异结果"""
+        result = []
+        matched_right_rows = set(row_mappings.values()) - {None}
+        
+        # 处理匹配的行和删除的行
+        for left_idx, right_idx in row_mappings.items():
+            if right_idx is None:
+                # 删除的行
+                result.append(RowDiff(
+                    row_left=left_idx,
+                    row_right=None,
+                    change_type=ChangeType.REMOVED,
+                    similarity=0.0
+                ))
+            else:
+                # 匹配的行 - 检查单元格差异
+                cell_diffs = self._compare_cells(
+                    df_left.iloc[left_idx], df_right.iloc[right_idx], 
+                    left_idx, right_idx, all_columns
+                )
+                
+                change_type = ChangeType.UNCHANGED
+                if any(cell.change_type != ChangeType.UNCHANGED for cell in cell_diffs):
+                    change_type = ChangeType.MODIFIED
+                
+                similarity = self._compute_row_similarity(df_left.iloc[left_idx], df_right.iloc[right_idx])
+                
+                result.append(RowDiff(
+                    row_left=left_idx,
+                    row_right=right_idx,
+                    change_type=change_type,
+                    similarity=similarity,
+                    cell_diffs=cell_diffs
+                ))
+        
+        # 处理新增的行(右侧未匹配的行)
+        for right_idx in range(len(df_right)):
+            if right_idx not in matched_right_rows:
+                result.append(RowDiff(
+                    row_left=None,
+                    row_right=right_idx,
+                    change_type=ChangeType.ADDED,
+                    similarity=0.0
+                ))
+        
+        return result
+    
+    def _compare_cells(self, row_left: pd.Series, row_right: pd.Series, 
+                      left_idx: int, right_idx: int, columns: List[str]) -> List[CellDiff]:
+        """比较单元格差异"""
+        cell_diffs = []
+        
+        for col in columns:
+            val_left = row_left[col] if col in row_left.index else None
+            val_right = row_right[col] if col in row_right.index else None
+            
+            # 处理NaN值
+            if pd.isna(val_left) and pd.isna(val_right):
+                change_type = ChangeType.UNCHANGED
+            elif pd.isna(val_left):
+                change_type = ChangeType.ADDED
+            elif pd.isna(val_right):
+                change_type = ChangeType.REMOVED
+            elif str(val_left) == str(val_right):
+                change_type = ChangeType.UNCHANGED
+            else:
+                change_type = ChangeType.MODIFIED
+            
+            cell_diffs.append(CellDiff(
+                row_left=left_idx,
+                row_right=right_idx,
+                column=col,
+                change_type=change_type,
+                old_value=val_left,
+                new_value=val_right
+            ))
+        
+        return cell_diffs
+
+class VSCodeStyleDataFrameDiff:
+    """类似VSCode样式的DataFrame差异展示"""
+    
+    def __init__(self):
+        self.diff_algorithm = DataFrameDiffAlgorithm()
+        self._inject_css()
+    
+    def _inject_css(self):
+        """注入VSCode风格的CSS样式"""
+        st.markdown("""
+        <style>
+        /* VSCode风格的差异显示 */
+        .vscode-diff-container {
+            font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
+            font-size: 12px;
+            border: 1px solid #3c3c3c;
+            border-radius: 6px;
+            overflow: hidden;
+        }
+        
+        .diff-header {
+            background-color: #2d2d30;
+            color: #cccccc;
+            padding: 8px 12px;
+            font-weight: bold;
+            border-bottom: 1px solid #3c3c3c;
+        }
+        
+        .diff-content {
+            height: 500px;
+            overflow: auto;
+            background-color: #1e1e1e;
+        }
+        
+        .diff-table {
+            width: 100%;
+            border-collapse: collapse;
+            color: #cccccc;
+        }
+        
+        .diff-table th {
+            background-color: #2d2d30;
+            border: 1px solid #3c3c3c;
+            padding: 6px 8px;
+            text-align: left;
+            position: sticky;
+            top: 0;
+            z-index: 10;
+        }
+        
+        .diff-table td {
+            border: 1px solid #3c3c3c;
+            padding: 4px 8px;
+            white-space: nowrap;
+            position: relative;
+        }
+        
+        /* 差异颜色 - VSCode风格 */
+        .diff-added {
+            background-color: rgba(22, 160, 133, 0.2) !important;
+            border-left: 3px solid #16a085 !important;
+        }
+        
+        .diff-removed {
+            background-color: rgba(231, 76, 60, 0.2) !important;
+            border-left: 3px solid #e74c3c !important;
+        }
+        
+        .diff-modified {
+            background-color: rgba(241, 196, 15, 0.2) !important;
+            border-left: 3px solid #f1c40f !important;
+        }
+        
+        .diff-unchanged {
+            background-color: transparent;
+        }
+        
+        /* 行号样式 */
+        .line-number {
+            background-color: #2d2d30;
+            color: #858585;
+            text-align: right;
+            padding: 4px 8px;
+            border-right: 1px solid #3c3c3c;
+            user-select: none;
+            min-width: 40px;
+        }
+        
+        /* 悬停效果 */
+        .diff-table tbody tr:hover {
+            background-color: rgba(255, 255, 255, 0.05);
+        }
+        
+        /* 差异指示器 */
+        .diff-indicator {
+            position: absolute;
+            left: -3px;
+            top: 0;
+            bottom: 0;
+            width: 3px;
+        }
+        
+        .indicator-added { background-color: #16a085; }
+        .indicator-removed { background-color: #e74c3c; }
+        .indicator-modified { background-color: #f1c40f; }
+        
+        /* 滚动条样式 */
+        .diff-content::-webkit-scrollbar {
+            width: 12px;
+            height: 12px;
+        }
+        
+        .diff-content::-webkit-scrollbar-track {
+            background: #2d2d30;
+        }
+        
+        .diff-content::-webkit-scrollbar-thumb {
+            background: #555555;
+            border-radius: 6px;
+        }
+        
+        .diff-content::-webkit-scrollbar-thumb:hover {
+            background: #777777;
+        }
+        </style>
+        """, unsafe_allow_html=True)
+    
+    def create_diff_view(self):
+        """创建差异对比视图"""
+        st.title("📊 VSCode风格 DataFrame 差异对比")
+        st.markdown("---")
+        
+        # 初始化数据
+        if 'df_original' not in st.session_state:
+            st.session_state.df_original = self._create_sample_data()
+        
+        if 'df_edited' not in st.session_state:
+            st.session_state.df_edited = st.session_state.df_original.copy()
+        
+        # 控制面板
+        self._create_control_panel()
+        
+        # 计算差异
+        row_diffs = self.diff_algorithm.compute_diff(
+            st.session_state.df_original, 
+            st.session_state.df_edited
+        )
+        
+        # 显示统计信息
+        self._display_diff_statistics(row_diffs)
+        
+        # 主要对比区域
+        self._create_main_diff_view(row_diffs)
+        
+        # 详细差异列表
+        self._create_detailed_diff_view(row_diffs)
+    
+    def _create_sample_data(self) -> pd.DataFrame:
+        """创建示例数据"""
+        return pd.DataFrame({
+            'ID': [1, 2, 3, 4, 5],
+            'Name': ['Alice', 'Bob', 'Charlie', 'David', 'Eve'],
+            'Age': [25, 30, 35, 40, 45],
+            'City': ['New York', 'London', 'Paris', 'Tokyo', 'Sydney'],
+            'Salary': [50000, 60000, 70000, 80000, 90000]
+        })
+    
+    def _create_control_panel(self):
+        """创建控制面板"""
+        with st.expander("🎛️ 控制面板", expanded=True):
+            col1, col2, col3, col4 = st.columns(4)
+            
+            with col1:
+                if st.button("🔄 重置数据"):
+                    st.session_state.df_original = self._create_sample_data()
+                    st.session_state.df_edited = st.session_state.df_original.copy()
+                    st.rerun()
+            
+            with col2:
+                if st.button("🎲 生成随机差异"):
+                    st.session_state.df_edited = self._create_random_diff()
+                    st.rerun()
+            
+            with col3:
+                similarity_threshold = st.slider(
+                    "相似度阈值", 
+                    min_value=0.1, 
+                    max_value=1.0, 
+                    value=0.7, 
+                    step=0.1
+                )
+                self.diff_algorithm.similarity_threshold = similarity_threshold
+            
+            with col4:
+                auto_scroll = st.checkbox("🔗 同步滚动", value=True)
+    
+    def _create_random_diff(self) -> pd.DataFrame:
+        """创建随机差异用于演示"""
+        df = st.session_state.df_original.copy()
+        
+        # 修改一些值
+        df.loc[1, 'Name'] = 'Robert'
+        df.loc[2, 'Age'] = 36
+        df.loc[3, 'City'] = 'Berlin'
+        
+        # 删除一行
+        df = df.drop(index=4)
+        
+        # 新增一行
+        new_row = pd.DataFrame({
+            'ID': [6], 'Name': ['Frank'], 'Age': [28], 
+            'City': ['Madrid'], 'Salary': [55000]
+        })
+        df = pd.concat([df, new_row], ignore_index=True)
+        
+        return df
+    
+    def _display_diff_statistics(self, row_diffs: List[RowDiff]):
+        """显示差异统计"""
+        stats = self._compute_diff_stats(row_diffs)
+        
+        col1, col2, col3, col4, col5 = st.columns(5)
+        
+        with col1:
+            st.metric("总行数", stats['total_rows'])
+        with col2:
+            st.metric("🟢 新增", stats['added_rows'], delta=stats['added_rows'])
+        with col3:
+            st.metric("🔴 删除", stats['removed_rows'], delta=-stats['removed_rows'])
+        with col4:
+            st.metric("🟡 修改", stats['modified_rows'])
+        with col5:
+            st.metric("⚪ 未变", stats['unchanged_rows'])
+    
+    def _compute_diff_stats(self, row_diffs: List[RowDiff]) -> Dict:
+        """计算差异统计"""
+        stats = {
+            'total_rows': len(row_diffs),
+            'added_rows': 0,
+            'removed_rows': 0,
+            'modified_rows': 0,
+            'unchanged_rows': 0
+        }
+        
+        for row_diff in row_diffs:
+            if row_diff.change_type == ChangeType.ADDED:
+                stats['added_rows'] += 1
+            elif row_diff.change_type == ChangeType.REMOVED:
+                stats['removed_rows'] += 1
+            elif row_diff.change_type == ChangeType.MODIFIED:
+                stats['modified_rows'] += 1
+            else:
+                stats['unchanged_rows'] += 1
+        
+        return stats
+    
+    def _create_main_diff_view(self, row_diffs: List[RowDiff]):
+        """创建主要差异视图"""
+        st.subheader("🔍 并排对比")
+        
+        left_col, right_col = st.columns(2)
+        
+        with left_col:
+            st.markdown("### 📝 原始版本")
+            self._create_diff_table(row_diffs, 'left')
+        
+        with right_col:
+            st.markdown("### ✏️ 编辑版本")
+            self._create_diff_table(row_diffs, 'right')
+    
+    def _create_diff_table(self, row_diffs: List[RowDiff], side: str):
+        """创建带差异高亮的表格"""
+        df = st.session_state.df_original if side == 'left' else st.session_state.df_edited
+        
+        # 构建HTML表格
+        html_table = self._build_diff_html_table(row_diffs, side, df)
+        
+        # 显示表格
+        st.markdown(f"""
+        <div class="vscode-diff-container">
+            <div class="diff-header">
+                {"原始版本" if side == 'left' else "编辑版本"} 
+                ({len(df)} 行)
+            </div>
+            <div class="diff-content">
+                {html_table}
+            </div>
+        </div>
+        """, unsafe_allow_html=True)
+    
+    def _build_diff_html_table(self, row_diffs: List[RowDiff], side: str, df: pd.DataFrame) -> str:
+        """构建差异HTML表格"""
+        if df.empty:
+            return "<p>数据为空</p>"
+        
+        html = '<table class="diff-table">'
+        
+        # 表头
+        html += '<thead><tr><th class="line-number">#</th>'
+        for col in df.columns:
+            html += f'<th>{col}</th>'
+        html += '</tr></thead><tbody>'
+        
+        # 根据差异类型构建行
+        for i, row_diff in enumerate(row_diffs):
+            row_idx = row_diff.row_left if side == 'left' else row_diff.row_right
+            
+            if row_idx is None:
+                continue  # 该侧没有对应行
+            
+            if row_idx >= len(df):
+                continue  # 超出范围
+                
+            # 确定行样式
+            row_class = self._get_row_css_class(row_diff.change_type)
+            
+            html += f'<tr class="{row_class}">'
+            html += f'<td class="line-number">{row_idx + 1}</td>'
+            
+            # 构建单元格
+            for col in df.columns:
+                cell_class = row_class
+                cell_value = df.iloc[row_idx][col]
+                
+                # 如果是修改的行,检查单元格级别的差异
+                if row_diff.change_type == ChangeType.MODIFIED and row_diff.cell_diffs:
+                    cell_diff = next((cd for cd in row_diff.cell_diffs if cd.column == col), None)
+                    if cell_diff:
+                        cell_class = self._get_cell_css_class(cell_diff.change_type)
+                
+                display_value = str(cell_value) if not pd.isna(cell_value) else ""
+                html += f'<td class="{cell_class}">{display_value}</td>'
+            
+            html += '</tr>'
+        
+        html += '</tbody></table>'
+        return html
+    
+    def _get_row_css_class(self, change_type: ChangeType) -> str:
+        """获取行的CSS类"""
+        mapping = {
+            ChangeType.ADDED: "diff-added",
+            ChangeType.REMOVED: "diff-removed", 
+            ChangeType.MODIFIED: "diff-modified",
+            ChangeType.UNCHANGED: "diff-unchanged"
+        }
+        return mapping.get(change_type, "diff-unchanged")
+    
+    def _get_cell_css_class(self, change_type: ChangeType) -> str:
+        """获取单元格的CSS类"""
+        return self._get_row_css_class(change_type)
+    
+    def _create_detailed_diff_view(self, row_diffs: List[RowDiff]):
+        """创建详细差异视图"""
+        st.markdown("---")
+        st.subheader("📋 详细差异列表")
+        
+        # 筛选选项
+        change_types = st.multiselect(
+            "显示的变更类型",
+            [ct.value for ct in ChangeType],
+            default=[ChangeType.ADDED.value, ChangeType.REMOVED.value, ChangeType.MODIFIED.value]
+        )
+        
+        filtered_diffs = [
+            rd for rd in row_diffs 
+            if rd.change_type.value in change_types
+        ]
+        
+        if not filtered_diffs:
+            st.info("没有符合条件的差异")
+            return
+        
+        # 显示差异详情
+        for i, row_diff in enumerate(filtered_diffs):
+            with st.expander(f"差异 {i+1}: {row_diff.change_type.value.upper()}", expanded=False):
+                self._display_row_diff_details(row_diff)
+    
+    def _display_row_diff_details(self, row_diff: RowDiff):
+        """显示行差异详情"""
+        col1, col2 = st.columns(2)
+        
+        with col1:
+            st.write("**位置信息:**")
+            st.write(f"- 左侧行: {row_diff.row_left}")
+            st.write(f"- 右侧行: {row_diff.row_right}")
+            st.write(f"- 变更类型: {row_diff.change_type.value}")
+            st.write(f"- 相似度: {row_diff.similarity:.2%}")
+        
+        with col2:
+            if row_diff.cell_diffs:
+                st.write("**单元格差异:**")
+                cell_diff_data = []
+                for cell_diff in row_diff.cell_diffs:
+                    if cell_diff.change_type != ChangeType.UNCHANGED:
+                        cell_diff_data.append({
+                            '列': cell_diff.column,
+                            '变更类型': cell_diff.change_type.value,
+                            '原值': str(cell_diff.old_value),
+                            '新值': str(cell_diff.new_value)
+                        })
+                
+                if cell_diff_data:
+                    st.dataframe(pd.DataFrame(cell_diff_data), use_container_width=True)
+
+def main():
+    """主函数"""
+    diff_viewer = VSCodeStyleDataFrameDiff()
+    diff_viewer.create_diff_view()
+
+if __name__ == "__main__":
+    main()

+ 173 - 0
streamlit_test/reports/table.py

@@ -0,0 +1,173 @@
+import streamlit as st
+import pandas as pd
+import plotly.graph_objects as go
+
+def scrollable_dataframe_1():
+    st.title("Scrollable DataFrame Example")
+
+    # 创建一个宽表格以测试滚动功能
+    df = pd.DataFrame(
+            {
+                "1 column": [1, 2, 3, 4, 5, 6, 7],
+                "2 column": [10, 20, 30, 40, 50, 60, 70],
+                "3 column": [1, 2, 3, 4, 5, 6, 7],
+                "4 column": [10, 20, 30, 40, 50, 60, 70],
+                "5 column": [1, 2, 3, 4, 5, 6, 7],
+                "6 column": [10, 20, 30, 40, 50, 60, 70],
+                "7 column": [1, 2, 3, 4, 5, 6, 7],
+                "8 column": [10, 20, 30, 40, 50, 60, 70],
+                "9 column": [1, 2, 3, 4, 5, 6, 7],
+                "10 column": [10, 20, 30, 40, 50, 60, 70],
+                "11 column": [1, 2, 3, 4, 5, 6, 7],
+                "12 column": [10, 20, 30, 40, 50, 60, 70],
+            }
+        )
+
+    st.write("1 + 1 = ", 2)
+
+    st.write("Below is a DataFrame:", df, "Above is a dataframe.")
+    st.dataframe(df, width='content', height=100)
+
+    st.latex(r'''
+        a + ar + a r^2 + a r^3 + \cdots + a r^{n-1} =
+        \sum_{k=0}^{n-1} ar^k =
+        a \left(\frac{1-r^{n}}{1-r}\right)
+        ''')
+    st.help(st.latex)
+
+def scrollable_dataframe_2():
+    st.title("Scrollable DataFrame with Custom CSS")
+    # 大数据集
+    df = pd.DataFrame({
+        f"column_{i}": [f"data_{j}_{i}" for j in range(100)] 
+        for i in range(20)
+    })
+
+    st.write("1 + 1 = ", 2)
+
+    # 自定义CSS样式
+    st.markdown("""
+    <style>
+    .custom-dataframe-container {
+        height: 400px;
+        width: 100%;
+        max-width: 1000px;
+        overflow: auto;
+        border: 2px solid #4CAF50;
+        border-radius: 10px;
+        padding: 15px;
+        margin: 10px 0;
+        background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
+        box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
+    }
+
+    .custom-dataframe-container::-webkit-scrollbar {
+        width: 12px;
+        height: 12px;
+    }
+
+    .custom-dataframe-container::-webkit-scrollbar-track {
+        background: #f1f1f1;
+        border-radius: 10px;
+    }
+
+    .custom-dataframe-container::-webkit-scrollbar-thumb {
+        background: #888;
+        border-radius: 10px;
+    }
+
+    .custom-dataframe-container::-webkit-scrollbar-thumb:hover {
+        background: #555;
+    }
+    </style>
+    """, unsafe_allow_html=True)
+
+    # 应用自定义样式
+    st.markdown("Below is a DataFrame:")
+    st.markdown('<div class="custom-dataframe-container">', unsafe_allow_html=True)
+
+    # 使用st.dataframe,它会继承容器的滚动设置
+    # st.dataframe(df, use_container_width=True)
+    st.dataframe(df, width='content')
+
+    st.markdown('</div>', unsafe_allow_html=True)
+    st.write("Above is a dataframe.")
+
+def scrollable_dataframe_2_fixed():
+    
+    st.title("Scrollable DataFrame - Fixed Version")
+    
+    # 大数据集
+    df = pd.DataFrame({
+        f"column_{i}": [f"data_{j}_{i}" for j in range(100)] 
+        for i in range(20)
+    })
+
+    st.write("1 + 1 = ", 2)
+    st.markdown("Below is a DataFrame:")
+    
+    # 方案1:直接使用st.dataframe,不包装容器
+    st.dataframe(
+        df, 
+        height=400,
+        width=1000,
+        use_container_width=False
+    )
+    
+    st.write("Above is a dataframe.")
+
+def scrollable_dataframe_3_plotly():
+    st.title("Scrollable DataFrame with Plotly")
+    
+    # 大数据集
+    df = pd.DataFrame({
+        f"column_{i}": [f"data_{j}_{i}" for j in range(100)] 
+        for i in range(15)
+    })
+
+    st.write("1 + 1 = ", 2)
+    st.markdown("Below is a DataFrame:")
+    
+    # 使用Plotly表格
+    fig = go.Figure(data=[go.Table(
+        header=dict(
+            values=list(df.columns),
+            fill_color='paleturquoise',
+            align='left',
+            font=dict(size=12)
+        ),
+        cells=dict(
+            values=[df[col] for col in df.columns],
+            fill_color='lavender',
+            align='left',
+            font=dict(size=11)
+        )
+    )])
+    
+    fig.update_layout(
+        width=1000,
+        height=400,
+        margin=dict(l=0, r=0, t=0, b=0)
+    )
+    
+    # st.plotly_chart(fig, use_container_width=True)
+    st.plotly_chart(fig, width='stretch')
+    st.write("Above is a dataframe.")
+
+
+# 页面选择
+page = st.radio("Select Page", [
+    "Scrollable DataFrame 1", 
+    "Scrollable with Custom CSS",
+    "Scrollable with Custom CSS Fixed Version", 
+    "Plotly Version",
+])
+
+if page == "Scrollable DataFrame 1":
+    scrollable_dataframe_1()
+elif page == "Scrollable with Custom CSS":
+    scrollable_dataframe_2()
+elif page == "Scrollable with Custom CSS Fixed Version":
+    scrollable_dataframe_2_fixed()
+elif page == "Plotly Version":
+    scrollable_dataframe_3_plotly()

+ 41 - 0
streamlit_test/test_multipage.py

@@ -0,0 +1,41 @@
+import streamlit as st
+
+if "logged_in" not in st.session_state:
+    st.session_state.logged_in = False
+
+def login():
+    if st.button("Log in"):
+        st.session_state.logged_in = True
+        st.rerun()
+
+def logout():
+    if st.button("Log out"):
+        st.session_state.logged_in = False
+        st.rerun()
+
+login_page = st.Page(login, title="Log in", icon=":material/login:")
+logout_page = st.Page(logout, title="Log out", icon=":material/logout:")
+
+dashboard = st.Page(
+    "reports/dashboard.py", title="Dashboard", icon=":material/dashboard:", default=True
+)
+bugs = st.Page("reports/data_editor.py", title="Data Editor", icon=":material/edit:")
+alerts = st.Page(
+    "reports/table.py", title="测试表格", icon=":material/notification_important:"
+)
+
+search = st.Page("tools/search.py", title="Search", icon=":material/search:")
+history = st.Page("tools/history.py", title="History", icon=":material/history:")
+
+if st.session_state.logged_in:
+    pg = st.navigation(
+        {
+            "Account": [logout_page],
+            "Reports": [dashboard, bugs, alerts],
+            "Tools": [search, history],
+        }
+    )
+else:
+    pg = st.navigation([login_page])
+
+pg.run()

+ 45 - 0
streamlit_test/test_theme.py

@@ -0,0 +1,45 @@
+import streamlit as st
+
+st.write("Normal efg")
+st.write("*Italic efg*")
+st.write("**Bold efg**")
+st.write("***Bold-italic efg***")
+st.write("`Code normal efg`")
+st.write("*`Code italic efg`*")
+st.write("**`Code bold efg`**")
+st.write("***`Code bold-italic efg`***")
+
+st.subheader('Counter Example using Callbacks with args')
+if 'count' not in st.session_state:
+    st.session_state.count = 0
+
+increment_value = st.number_input('Enter a value', value=0, step=1)
+
+def increment_counter(increment_value):
+    st.session_state.count += increment_value
+
+increment = st.button('Increment', on_click=increment_counter,
+    args=(increment_value, ))
+
+st.write('Count = ', st.session_state.count)
+
+
+if "celsius" not in st.session_state:
+    # set the initial default value of the slider widget
+    st.session_state.celsius = 50.0
+
+st.slider(
+    "Temperature in Celsius",
+    min_value=-100.0,
+    max_value=100.0,
+    key="celsius"
+)
+
+# This will get the value of the slider widget
+st.write(st.session_state.celsius)
+
+if 'my_button' not in st.session_state:
+    st.session_state.my_button = True
+    # Streamlit will raise an Exception on trying to set the state of button
+
+st.button('Submit', key='my_button')

+ 240 - 0
streamlit_test/tools/history.py

@@ -0,0 +1,240 @@
+import streamlit as st
+import pandas as pd
+from datetime import datetime, timedelta
+import random
+import plotly.express as px
+import plotly.graph_objects as go
+
+st.title("📚 History")
+st.markdown("操作历史和活动记录")
+
+# 模拟历史数据
+@st.cache_data
+def load_history_data():
+    actions = ["登录", "登出", "创建", "编辑", "删除", "查看", "下载", "上传"]
+    objects = ["用户账户", "订单记录", "产品信息", "系统设置", "报告文件", "配置文件"]
+    users = ["张三", "李四", "王五", "赵六", "钱七", "孙八"]
+    
+    data = []
+    for i in range(500):
+        timestamp = datetime.now() - timedelta(
+            days=random.randint(0, 90),
+            hours=random.randint(0, 23),
+            minutes=random.randint(0, 59)
+        )
+        
+        data.append({
+            'id': f"HIST-{4000 + i}",
+            'timestamp': timestamp,
+            'user': random.choice(users),
+            'action': random.choice(actions),
+            'object': random.choice(objects),
+            'ip_address': f"192.168.1.{random.randint(1, 254)}",
+            'status': random.choice(['成功', '失败', '警告']),
+            'details': f"操作详情 {i+1}: 用户执行了相关操作",
+            'session_id': f"SES-{random.randint(10000, 99999)}"
+        })
+    
+    return pd.DataFrame(data)
+
+# 加载数据
+history_df = load_history_data()
+
+# 时间范围选择
+st.subheader("📅 时间范围")
+col1, col2, col3 = st.columns(3)
+
+with col1:
+    time_filter = st.selectbox(
+        "快速选择",
+        ["最近1小时", "最近24小时", "最近7天", "最近30天", "最近90天", "自定义范围"]
+    )
+
+with col2:
+    if time_filter == "自定义范围":
+        start_date = st.date_input("开始日期", value=datetime.now() - timedelta(days=7))
+    else:
+        start_date = None
+
+with col3:
+    if time_filter == "自定义范围":
+        end_date = st.date_input("结束日期", value=datetime.now())
+    else:
+        end_date = None
+
+# 应用时间筛选
+if time_filter == "最近1小时":
+    cutoff = datetime.now() - timedelta(hours=1)
+    filtered_df = history_df[history_df['timestamp'] >= cutoff]
+elif time_filter == "最近24小时":
+    cutoff = datetime.now() - timedelta(days=1)
+    filtered_df = history_df[history_df['timestamp'] >= cutoff]
+elif time_filter == "最近7天":
+    cutoff = datetime.now() - timedelta(days=7)
+    filtered_df = history_df[history_df['timestamp'] >= cutoff]
+elif time_filter == "最近30天":
+    cutoff = datetime.now() - timedelta(days=30)
+    filtered_df = history_df[history_df['timestamp'] >= cutoff]
+elif time_filter == "最近90天":
+    cutoff = datetime.now() - timedelta(days=90)
+    filtered_df = history_df[history_df['timestamp'] >= cutoff]
+else:  # 自定义范围
+    if start_date and end_date:
+        filtered_df = history_df[
+            (history_df['timestamp'].dt.date >= start_date) &
+            (history_df['timestamp'].dt.date <= end_date)
+        ]
+    else:
+        filtered_df = history_df
+
+# 统计概览
+st.subheader("📊 活动概览")
+col1, col2, col3, col4 = st.columns(4)
+
+with col1:
+    st.metric("总操作数", len(filtered_df))
+
+with col2:
+    unique_users = filtered_df['user'].nunique()
+    st.metric("活跃用户", unique_users)
+
+with col3:
+    success_rate = len(filtered_df[filtered_df['status'] == '成功']) / len(filtered_df) * 100 if len(filtered_df) > 0 else 0
+    st.metric("成功率", f"{success_rate:.1f}%")
+
+with col4:
+    failed_ops = len(filtered_df[filtered_df['status'] == '失败'])
+    st.metric("失败操作", failed_ops)
+
+# 可视化图表
+st.subheader("📈 活动趋势")
+
+tab1, tab2, tab3 = st.tabs(["时间趋势", "用户活动", "操作分布"])
+
+with tab1:
+    # 按小时统计活动
+    if len(filtered_df) > 0:
+        hourly_activity = filtered_df.groupby(filtered_df['timestamp'].dt.hour).size().reset_index()
+        hourly_activity.columns = ['hour', 'count']
+        
+        fig_hourly = px.bar(hourly_activity, x='hour', y='count', 
+                           title='按小时统计的活动分布')
+        st.plotly_chart(fig_hourly, use_container_width=True)
+
+with tab2:
+    # 用户活动统计
+    if len(filtered_df) > 0:
+        user_activity = filtered_df['user'].value_counts().reset_index()
+        user_activity.columns = ['user', 'count']
+        
+        fig_users = px.pie(user_activity, values='count', names='user', 
+                          title='用户活动分布')
+        st.plotly_chart(fig_users, use_container_width=True)
+
+with tab3:
+    # 操作类型分布
+    if len(filtered_df) > 0:
+        action_counts = filtered_df['action'].value_counts().reset_index()
+        action_counts.columns = ['action', 'count']
+        
+        fig_actions = px.bar(action_counts, x='action', y='count',
+                            title='操作类型分布')
+        st.plotly_chart(fig_actions, use_container_width=True)
+
+# 筛选器
+st.subheader("🔍 详细筛选")
+col1, col2, col3, col4 = st.columns(4)
+
+with col1:
+    selected_users = st.multiselect(
+        "用户",
+        options=filtered_df['user'].unique(),
+        default=filtered_df['user'].unique()
+    )
+
+with col2:
+    selected_actions = st.multiselect(
+        "操作类型",
+        options=filtered_df['action'].unique(),
+        default=filtered_df['action'].unique()
+    )
+
+with col3:
+    selected_status = st.multiselect(
+        "状态",
+        options=filtered_df['status'].unique(),
+        default=filtered_df['status'].unique()
+    )
+
+with col4:
+    search_term = st.text_input("搜索", placeholder="搜索详情...")
+
+# 应用筛选
+final_df = filtered_df[
+    (filtered_df['user'].isin(selected_users)) &
+    (filtered_df['action'].isin(selected_actions)) &
+    (filtered_df['status'].isin(selected_status))
+]
+
+if search_term:
+    final_df = final_df[
+        final_df['details'].str.contains(search_term, case=False, na=False) |
+        final_df['object'].str.contains(search_term, case=False, na=False)
+    ]
+
+# 历史记录表格
+st.subheader("📋 历史记录")
+
+# 排序选项
+sort_order = st.radio("排序", ["最新在前", "最旧在前"], horizontal=True)
+final_df = final_df.sort_values('timestamp', ascending=(sort_order == "最旧在前"))
+
+# 分页
+items_per_page = st.selectbox("每页显示", [25, 50, 100], index=1)
+total_pages = (len(final_df) - 1) // items_per_page + 1 if len(final_df) > 0 else 1
+
+if total_pages > 1:
+    page = st.selectbox("页面", range(1, total_pages + 1))
+    start_idx = (page - 1) * items_per_page
+    end_idx = start_idx + items_per_page
+    display_df = final_df.iloc[start_idx:end_idx]
+else:
+    display_df = final_df
+
+# 显示数据
+if len(display_df) > 0:
+    st.dataframe(
+        display_df[['timestamp', 'user', 'action', 'object', 'status', 'ip_address', 'details']],
+        use_container_width=True,
+        column_config={
+            'timestamp': st.column_config.DatetimeColumn(
+                "时间",
+                format="MM-DD HH:mm:ss"
+            ),
+            'status': st.column_config.SelectboxColumn(
+                "状态",
+                options=['成功', '失败', '警告']
+            )
+        }
+    )
+    
+    # 导出功能
+    if st.button("📥 导出当前数据"):
+        csv = display_df.to_csv(index=False)
+        st.download_button(
+            label="下载CSV文件",
+            data=csv,
+            file_name=f"history_export_{datetime.now().strftime('%Y%m%d_%H%M%S')}.csv",
+            mime="text/csv"
+        )
+else:
+    st.warning("没有找到匹配的历史记录")
+
+# 实时更新选项
+st.subheader("🔄 实时更新")
+auto_refresh = st.checkbox("启用自动刷新 (30秒)")
+
+if auto_refresh:
+    st.info("⏱️ 页面将每30秒自动刷新")
+    time.sleep(30)
+    st.rerun()

+ 201 - 0
streamlit_test/tools/search.py

@@ -0,0 +1,201 @@
+import streamlit as st
+import pandas as pd
+import time
+from datetime import datetime, timedelta
+import random
+
+st.title("🔍 Search Tool")
+st.markdown("全局搜索工具")
+
+# 模拟搜索数据
+@st.cache_data
+def load_search_data():
+    categories = ["用户", "订单", "产品", "日志", "文档"]
+    data = []
+    
+    for i in range(200):
+        data.append({
+            'id': f"ITEM-{3000 + i}",
+            'title': f"项目 {i+1}: {random.choice(['用户管理', '订单处理', '产品信息', '系统日志', '帮助文档'])}",
+            'category': random.choice(categories),
+            'content': f"这是项目 {i+1} 的详细内容描述,包含相关信息和数据...",
+            'created_date': datetime.now() - timedelta(days=random.randint(1, 365)),
+            'tags': random.sample(['重要', '紧急', '已完成', '进行中', '待审核'], k=random.randint(1, 3))
+        })
+    
+    return pd.DataFrame(data)
+
+# 加载数据
+search_df = load_search_data()
+
+# 搜索界面
+st.subheader("🔍 搜索查询")
+
+# 搜索选项
+col1, col2 = st.columns([3, 1])
+
+with col1:
+    search_query = st.text_input(
+        "搜索内容",
+        placeholder="输入关键词进行搜索...",
+        help="支持标题、内容、标签搜索"
+    )
+
+with col2:
+    search_button = st.button("🔍 搜索", type="primary")
+
+# 高级搜索选项
+with st.expander("🔧 高级搜索选项"):
+    col1, col2, col3 = st.columns(3)
+    
+    with col1:
+        selected_categories = st.multiselect(
+            "类别筛选",
+            options=search_df['category'].unique(),
+            default=search_df['category'].unique()
+        )
+    
+    with col2:
+        date_range = st.date_input(
+            "时间范围",
+            value=(datetime.now() - timedelta(days=30), datetime.now()),
+            max_value=datetime.now()
+        )
+    
+    with col3:
+        available_tags = ['重要', '紧急', '已完成', '进行中', '待审核']
+        selected_tags = st.multiselect(
+            "标签筛选",
+            options=available_tags
+        )
+
+# 执行搜索
+if search_query or search_button:
+    with st.spinner("搜索中..."):
+        time.sleep(0.5)  # 模拟搜索延迟
+        
+        # 过滤数据
+        filtered_df = search_df[search_df['category'].isin(selected_categories)]
+        
+        # 时间范围筛选
+        if len(date_range) == 2:
+            start_date, end_date = date_range
+            filtered_df = filtered_df[
+                (filtered_df['created_date'].dt.date >= start_date) &
+                (filtered_df['created_date'].dt.date <= end_date)
+            ]
+        
+        # 标签筛选
+        if selected_tags:
+            filtered_df = filtered_df[
+                filtered_df['tags'].apply(lambda x: any(tag in x for tag in selected_tags))
+            ]
+        
+        # 关键词搜索
+        if search_query:
+            mask = (
+                filtered_df['title'].str.contains(search_query, case=False, na=False) |
+                filtered_df['content'].str.contains(search_query, case=False, na=False) |
+                filtered_df['tags'].astype(str).str.contains(search_query, case=False, na=False)
+            )
+            filtered_df = filtered_df[mask]
+        
+        # 显示搜索结果
+        st.subheader(f"📋 搜索结果 ({len(filtered_df)} 项)")
+        
+        if len(filtered_df) > 0:
+            # 结果统计
+            col1, col2, col3 = st.columns(3)
+            with col1:
+                st.metric("总结果数", len(filtered_df))
+            with col2:
+                category_counts = filtered_df['category'].value_counts()
+                st.metric("主要类别", category_counts.index[0] if len(category_counts) > 0 else "无")
+            with col3:
+                recent_count = len(filtered_df[filtered_df['created_date'] >= datetime.now() - timedelta(days=7)])
+                st.metric("最近7天", recent_count)
+            
+            # 排序选项
+            sort_option = st.selectbox(
+                "排序方式",
+                ["相关性", "时间(最新)", "时间(最旧)", "标题(A-Z)"]
+            )
+            
+            if sort_option == "时间(最新)":
+                filtered_df = filtered_df.sort_values('created_date', ascending=False)
+            elif sort_option == "时间(最旧)":
+                filtered_df = filtered_df.sort_values('created_date', ascending=True)
+            elif sort_option == "标题(A-Z)":
+                filtered_df = filtered_df.sort_values('title')
+            
+            # 分页显示
+            items_per_page = st.selectbox("每页显示", [10, 20, 50], index=1)
+            total_pages = (len(filtered_df) - 1) // items_per_page + 1
+            
+            if total_pages > 1:
+                page = st.selectbox("选择页面", range(1, total_pages + 1))
+                start_idx = (page - 1) * items_per_page
+                end_idx = start_idx + items_per_page
+                display_df = filtered_df.iloc[start_idx:end_idx]
+            else:
+                display_df = filtered_df
+            
+            # 显示结果
+            for idx, row in display_df.iterrows():
+                with st.container():
+                    st.markdown(f"### {row['title']}")
+                    
+                    col1, col2 = st.columns([3, 1])
+                    with col1:
+                        st.write(f"**类别:** {row['category']}")
+                        st.write(f"**内容预览:** {row['content'][:100]}...")
+                        
+                        # 显示标签
+                        if row['tags']:
+                            tags_html = " ".join([f"<span style='background-color: #e0e0e0; padding: 2px 6px; border-radius: 10px; font-size: 12px;'>{tag}</span>" for tag in row['tags']])
+                            st.markdown(tags_html, unsafe_allow_html=True)
+                    
+                    with col2:
+                        st.write(f"**ID:** {row['id']}")
+                        st.write(f"**创建时间:** {row['created_date'].strftime('%Y-%m-%d')}")
+                        if st.button("查看详情", key=f"view_{row['id']}"):
+                            st.session_state[f"selected_item_{row['id']}"] = True
+                    
+                    # 详情展示
+                    if st.session_state.get(f"selected_item_{row['id']}", False):
+                        with st.expander(f"详情: {row['title']}", expanded=True):
+                            st.write(f"**完整内容:** {row['content']}")
+                            st.write(f"**所有标签:** {', '.join(row['tags'])}")
+                            if st.button("收起详情", key=f"hide_{row['id']}"):
+                                st.session_state[f"selected_item_{row['id']}"] = False
+                                st.rerun()
+                    
+                    st.divider()
+                    
+        else:
+            st.warning("没有找到匹配的结果,请尝试其他搜索条件。")
+            
+            # 搜索建议
+            st.subheader("💡 搜索建议")
+            st.markdown("""
+            - 尝试使用更简单的关键词
+            - 检查拼写是否正确  
+            - 尝试使用同义词
+            - 扩大时间范围
+            - 取消一些筛选条件
+            """)
+
+else:
+    # 显示搜索提示
+    st.info("👆 在上方输入关键词开始搜索")
+    
+    # 热门搜索词
+    st.subheader("🔥 热门搜索")
+    popular_searches = ["用户管理", "订单处理", "系统日志", "产品信息", "帮助文档"]
+    
+    cols = st.columns(len(popular_searches))
+    for i, term in enumerate(popular_searches):
+        with cols[i]:
+            if st.button(f"#{term}", key=f"popular_{i}"):
+                st.session_state.search_query = term
+                st.rerun()