关于a标签,添加:data-toggle="modal" 属性,又添加了:href="#btn_top",属性导致 跳转到另一个页面出现遮罩层

探讨了在HTML中使用a标签时,错误地添加data-toggle=modal属性导致跳转页面出现遮罩层的问题。解析了这一现象的原因,并提供了理解和解决该问题的思路。

1.关于a标签,添加:data-toggle="modal" 属性,又添加了:href="#btn_top",属性导致 跳转到另一个页面出现遮罩层

如上图,如果a标签无意添加了:data-toggle="modal"属性,那么你的href标签里对应的modal要能找到,广泛解释就是。添加:data-toggle="modal"属性,添加a标签会寻找此标签的href属性,可刚好你写的href 对应的是undefined。所以当跳转到另一个页面会出现遮罩层。遮罩层会再弹出框出来前初始化。

实现第二个tab页根据devid查询设备信息 <!DOCTYPE html> <html lang="zh" xmlns:th="http://www.thymeleaf.org" xmlns:shiro="http://www.pollix.at/thymeleaf/shiro"> <head> <th:block th:include="include :: header('设备台帐管理列表')" /> <th:block th:include="include :: layout-latest-css" /> <th:block th:include="include :: ztree-css" /> </head> <body class="gray-bg"> <div class="ui-layout-west"> <div class="box box-main"> <div class="box-header"> <div class="box-title"> <i class="fa icon-grid"></i> 组织机构 </div> <div class="box-tools pull-right"> <a type="button" class="btn btn-box-tool" href="#" onclick="dept()" title="管理部门"><i class="fa fa-edit"></i></a> <button type="button" class="btn btn-box-tool" id="btnExpand" title="展开" style="display:none;"><i class="fa fa-chevron-up"></i></button> <button type="button" class="btn btn-box-tool" id="btnCollapse" title="折叠"><i class="fa fa-chevron-down"></i></button> <button type="button" class="btn btn-box-tool" id="btnRefresh" title="刷新部门"><i class="fa fa-refresh"></i></button> </div> </div> <div class="ui-layout-content"> <div id="tree" class="ztree"></div> </div> </div> </div> <div class="ui-layout-center"> <div class="container-div"> <div class="row"> <!-- 页签开始 --> <div class="ibox-content"> <!-- <form id="formId"> <input type="hidden" id="parentId" name="parentId"> </form> --> <ul class="nav nav-tabs"> <!-- { text: "设备列表", name: "devlist", alias:"devlist",visible:true}, { text: "设备信息", name: "devinfo" ,alias:"devinfo",visible:true}, { text: "扩展属性", name: "devext" ,alias:"devext",visible:true}, { text: "物料清单", name: "devbom" ,alias:"devbom",visible:true}, { text:"文档清单", name:"devdoc",alias:"devdoc",visible:true}, { text: "检修记录", name: "overhaulrecord" ,alias:"overhaulrecord",visible:true}, { text: "养护计量", name: "curingmeasure" ,alias:"curingmeasure",visible:true}, { text: "安装历史", name: "installationhistory" ,alias:"installationhistory",visible:true} --> <li class="active"><a data-toggle="tab" aria-expanded="true" onclick="queryDevlist()">设备列表</a></li> <li><a data-toggle="tab" aria-expanded="false" onclick="devinfo()">设备信息</a></li> <li><a data-toggle="tab" aria-expanded="false" onclick="devext()">扩展属性</a></li> <li><a data-toggle="tab" aria-expanded="false" onclick="devbom()">物料清单</a></li> <li><a data-toggle="tab" aria-expanded="false" onclick="devdoc()">文档清单</a></li> <li><a data-toggle="tab" aria-expanded="false" onclick="overhaulrecord()">检修记录</a></li> <li><a data-toggle="tab" aria-expanded="false" onclick="curingmeasure()">养护计量</a></li> <li><a data-toggle="tab" aria-expanded="false" onclick="installationhistory()">安装历史</a></li> <!-- <li class="active"><a data-toggle="tab" href="#tab-1" aria-expanded="true"> 第一个选项卡</a> </li> <li class=""><a data-toggle="tab" href="#tab-2" aria-expanded="false">第二个选项卡</a> </li> --> </ul> </div> <!-- 页签结束 --> <div class="col-sm-12 search-collapse"> <form id="devledger-form"> <input type="hidden" id="deptId" name="deptId"> <input type="hidden" id="devSiteId" name="devSiteId"> <div class="select-list"> <ul> <li> <label>设备名称:</label> <input type="text" name="devName"/> </li> <li> <label>固定资产编码:</label> <input type="text" name="assetCode"/> </li> <li> <label>生产厂家:</label> <input type="text" name="factory"/> </li> <!-- <li> 设备状态:<select name="status" th:with="type=${@dict.getType('sys_normal_disable')}"> <option value="">所有</option> <option th:each="dict : ${type}" th:text="${dict.dictLabel}" th:value="${dict.dictValue}"></option> </select> </li> --> <li> <a class="btn btn-primary btn-rounded btn-sm" onclick="$.table.search()"><i class="fa fa-search"></i> 搜索</a> <a class="btn btn-warning btn-rounded btn-sm" onclick="$.form.reset()"><i class="fa fa-refresh"></i> 重置</a> </li> </ul> </div> </form> </div> <div class="btn-group-sm" id="toolbar" role="group"> <a class="btn btn-success" onclick="$.operate.add()" shiro:hasPermission="system:devledger:add"> <i class="fa fa-plus"></i> 新建 </a> <!-- <a class="btn btn-primary single disabled" onclick="$.operate.edit()" shiro:hasPermission="system:devledger:edit"> <i class="fa fa-edit"></i> 修改 </a> --> <a class="btn btn-danger multiple disabled" onclick="$.operate.removeAll()" shiro:hasPermission="system:devledger:remove"> <i class="fa fa-remove"></i> 删除 </a> <a class="btn btn-warning" onclick="$.table.exportExcel()" shiro:hasPermission="system:devledger:export"> <i class="fa fa-download"></i> 导出 </a> </div> <div class="col-sm-12 select-table table-striped" style="overflow-y: auto;height: 250px;"> <table id="bootstrap-table"></table> </div> </div> </div> </div> <th:block th:include="include :: footer" /> <th:block th:include="include :: layout-latest-js" /> <th:block th:include="include :: ztree-js" /> <script th:inline="javascript"> //var editFlag = [[${@permission.hasPermi('system:devledger:edit')}]]; //var removeFlag = [[${@permission.hasPermi('system:devledger:remove')}]]; var prefix = ctx + "system/devledger"; var selectedDeviceId = ""; //判断当前元素的宽度,动态决定侧边栏是否隐藏 $(function() { var panehHidden = false; if ($(this).width() < 769) { panehHidden = true; } $('body').layout({ initClosed: panehHidden, west__size: 185 }); queryDevlist(); queryLocationTree(); }); //不同页签的按钮设定 var ButtonManager = { templates: { devlist: ` <a class="btn btn-success" onclick="$.operate.add()" shiro:hasPermission="system:location:add"> <i class="fa fa-plus"></i> 新建 </a> <a class="btn btn-danger multiple disabled" onclick="$.operate.removeAll()" shiro:hasPermission="system:location:remove"> <i class="fa fa-remove"></i> 删除 </a> <a class="btn btn-warning" onclick="$.table.exportExcel()" shiro:hasPermission="system:location:export"> <i class="fa fa-download"></i> 导出 </a> `, devinfo: ` <a class="btn btn-success" onclick="$.operate.save()" shiro:hasPermission="system:base:save"> <i class="fa fa-plus"></i> 保存 </a> <a class="btn btn-primary single disabled" onclick="$.operate.printnum()" shiro:hasPermission="system:base:printnum"> <i class="fa fa-edit"></i> 打印条形码 </a> <a class="btn btn-danger multiple disabled" onclick="$.operate.printform()" shiro:hasPermission="system:base:printform"> <i class="fa fa-remove"></i> 打印台账 </a> <a class="btn btn-warning" onclick="$.operate.renewal()" shiro:hasPermission="system:base:renewal"> <i class="fa fa-download"></i> 设备续保 </a> `, devext: ` <a class="btn btn-warning" onclick="$.operate.save()" shiro:hasPermission="system:base:save"> <i class="fa fa-download"></i> 保存 </a> `, devbom: `//此页签中包含设备物料清单、设备配套耗材、设备配套文档三个功能增删改查 <a class="btn btn-warning" onclick="$.operate.save()" shiro:hasPermission="system:base:save"> <i class="fa fa-download"></i> 保存 </a> ` }, change: function(buttonType) { if (this.templates[buttonType]) { $('#toolbar').html(this.templates[buttonType]); return true; } return false; }, // 切换到设备列表按钮 toDevlist: function() { return this.change('devlist'); }, // 切换到设备信息按钮 toDevinfo: function() { return this.change('devinfo'); }, // 切换到扩展属性按钮 toDevext: function() { return this.change('devext'); }, // 切换到物料清单按钮 toDevbom: function() { return this.change('devbom'); }, // 切换到文档清单按钮 toDevdoc: function() { return this.change('devdoc'); }, // 切换到检修记录按钮 toOverhaulrecord: function() { return this.change('overhaulrecord'); }, // 切换到养护计量按钮 toCuringmeasure: function() { return this.change('curingmeasure'); }, // 切换到安装历史按钮 toInstallationhistory: function() { return this.change('installationhistory'); } }; //查询设备列表 function queryDevlist() { var options = { url: prefix + "/list", createUrl: prefix + "/add", /* updateUrl: prefix + "/edit/{id}", */ removeUrl: prefix + "/remove", exportUrl: prefix + "/export", /* sortName: "createTime", sortOrder: "desc", */ modalName: "设备台帐管理", columns: [{ checkbox: true }, { field: 'devCode', title: '设备编码' }, { field: 'devName', title: '设备名称' /* title: '设备名称', sortable: true */ }, { field: 'devTypeId', title: '设备类型'//设备类型ID }, { field: 'devSpecksId', title: '设备型号' }, { field: 'factory', title: '生产厂家' }, { field: 'assetCode', title: '固定资产编码' }, { field: 'devStatus', title: '设备状态', align: 'center', formatter: function (value, row, index) { return statusTools(row); } }, { field: 'wzCode', title: '物资编码', visible: false // 隐藏该列 }, { field: 'snCode', title: 'SN编码', visible: false // 隐藏该列 }, { title: '操作', align: 'center', visible: false, // 隐藏该列 formatter: function(value, row, index) { var actions = []; actions.push('<a class="btn btn-success btn-xs ' + editFlag + '" href="javascript:void(0)" onclick="$.operate.edit(\'' + row.devId + '\')"><i class="fa fa-edit"></i>编辑</a> '); actions.push('<a class="btn btn-danger btn-xs ' + removeFlag + '" href="javascript:void(0)" onclick="$.operate.remove(\'' + row.devId + '\')"><i class="fa fa-remove"></i>删除</a>'); return actions.join(''); } } ], onLoadSuccess: function(data) { // 表格数据加载成功后执行 if (data && data.row.length > 0) { // 获取第一行数据 var firstRow = data.row[0]; selectedDeviceId = firstRow.devId; console.log("第一行设备ID:", selectedDeviceId); } }, // 添加双击行的回调函数 onDblClickRow: function (row, $element) { // 这里编写跳转至第二个 tab 页的逻辑 // 假设使用 jQuery 来切换 tab 页,第二个 tab 的 id 为 tab2 $('#tab2').tab('show'); } }; $.table.init(options); } function devinfo() { // 跳转到设备信息页面并传递参数 window.location.href = 'add.html?devId=' + selectedDeviceId; } function devext(){ /* prefix = ctx + "system/base"; */ debugger; var options2 = { /* id:'bootstrap-table', url: prefix + "/list", createUrl: prefix + "/add", updateUrl: prefix + "/edit/{id}", removeUrl: prefix + "/remove", exportUrl: prefix + "/export", modalName: "设备基础信息", */ url: prefix + "/devext", createUrl: prefix + "/add", /* updateUrl: prefix + "/edit/{id}", */ removeUrl: prefix + "/remove", exportUrl: prefix + "/export", /* sortName: "createTime", sortOrder: "desc", */ modalName: "设备信息", columns: [{ checkbox: true }, /* { field: 'devId', title: '设备ID', visible: false }, */ { field: 'devCode', title: '设备编码' }, { field: 'devName', title: '设备名称' }, { field: 'professionName', title: '专业名称' }, { field: 'devSpecksId', title: '设备型号' }, { field: 'measurementFlag', title: '计量标志' }, { field: 'maintenanceFlag', title: '养护标志' }, { field: 'devStatus', title: '设备状态' }, { field: 'statuDate', title: '当前状态日期' }, { field: 'userDevFlag', title: '是否用户设备' }, { field: 'factory', title: '生产厂家' }, { field: 'zclb', title: '管理分类' } , { title: '操作', align: 'center', formatter: function(value, row, index) { var actions = []; actions.push('<a class="btn btn-success btn-xs ' + editFlag + '" href="javascript:void(0)" onclick="$.operate.edit(\'' + row.devId + '\')"><i class="fa fa-edit"></i>编辑</a> '); actions.push('<a class="btn btn-danger btn-xs ' + removeFlag + '" href="javascript:void(0)" onclick="$.operate.remove(\'' + row.devId + '\')"><i class="fa fa-remove"></i>删除</a>'); return actions.join(''); } } ] }; debugger; reinitializeTable(options2); //$.table.init(options2); ButtonManager.toEquit(); // 切换回新增按钮 $.table.search(); } //查询树形结构 function queryLocationTree() { var url = ctx + "system/devledger/treeData"; var options = { url: url, expandLevel: 2, onClick : zOnClick }; $.tree.init(options); function zOnClick(event, treeId, treeNode) { $("#devSiteId").val(treeNode.id); //$("#devSiteId").val(treeNode.pId); //$("#title").val(treeNode.pId); $.table.search(); } } //树形结构的展开折叠刷新 $('#btnExpand').click(function() { $._tree.expandAll(true); $(this).hide(); $('#btnCollapse').show(); }); $('#btnCollapse').click(function() { $._tree.expandAll(false); $(this).hide(); $('#btnExpand').show(); }); $('#btnRefresh').click(function() { queryLocationTree(); }); /* 树形结构-部门 */ function dept() { var url = ctx + "system/type"; $.modal.openTab("部门管理", url); } /* 设备状态显示 */ function statusTools(row) { if (row.devStatus == "active") { return '在用'; //return '<i class=\"fa fa-toggle-off text-info fa-2x\" onclick="enable(\'' + row.userId + '\')"></i> '; } else if (row.devStatus == "tobeinstall") { return '待安装'; //return '<i class=\"fa fa-toggle-off text-info fa-2x\" onclick="enable(\'' + row.userId + '\')"></i> '; } else if (row.devStatus == "inactive") { return '停用'; //return '<i class=\"fa fa-toggle-off text-info fa-2x\" onclick="enable(\'' + row.userId + '\')"></i> '; }else if (row.devStatus == "discard") { return '报废'; //return '<i class=\"fa fa-toggle-off text-info fa-2x\" onclick="enable(\'' + row.userId + '\')"></i> '; } } </script> </body> </html>
最新发布
10-17
import os import pickle import pandas as pd import dash from dash import dcc, html, dash_table, callback_context from dash.dependencies import Input, Output, State import dash_bootstrap_components as dbc from datetime import datetime, timedelta import base64 import io # ------------------------------- # 🧩 1. 配置 Config # ------------------------------- CATEGORY_OPTIONS = [ {"label": "安装", "value": "安装"}, {"label": "勘测", "value": "勘测"}, {"label": "可选", "value": "可选"}, {"label": "网优", "value": "网优"}, {"label": "运输-正向", "value": "运输-正向"}, {"label": "运输-逆向", "value": "运输-逆向"} ] TABLE_STYLE = { 'maxHeight': '600px', 'overflowY': 'auto', 'overflowX': 'auto', 'whiteSpace': 'nowrap' } CELL_TOOLTIP_STYLE = { 'textAlign': 'left', 'overflow': 'hidden', 'textOverflow': 'ellipsis', 'maxWidth': 150, 'minWidth': 100 } CACHE_TTL = timedelta(minutes=10) # 全局缓存 DATA_CACHE = {} CLASSIFICATION_CACHE = {} RESULT_CACHE = None RESULT_CACHE_TIME = None # ------------------------------- # 🛠️ 2. 工具函数 Utils # ------------------------------- def get_time_str(): return datetime.now().strftime('%Y-%m-%d %H:%M:%S') def time_print(*args): print(f"{get_time_str()}", *args) def save_to_pickle(df_dict, path): with open(path, 'wb') as f: pickle.dump(df_dict, f) def load_from_pickle(path): with open(path, 'rb') as f: return pickle.load(f) def read_excel_with_sheets(file_path): try: xl = pd.ExcelFile(file_path) return {sheet: xl.parse(sheet) for sheet in xl.sheet_names}, None except Exception as e: return None, str(e) def get_file_size_mb(file_path): return round(os.path.getsize(file_path) / (1024 * 1024), 2) def validate_columns(df, required_cols): missing = [col for col in required_cols if col not in df.columns] return len(missing) == 0, missing def site_model(data_df, classification_df): """核心分析逻辑""" if data_df is None or classification_df is None: return pd.DataFrame({"错误": ["缺少数据"]}) key = "条目描述" if key not in data_df.columns or key not in classification_df.columns: return pd.DataFrame({"错误": [f"缺少 '{key}' 字段"]}) result = data_df.merge( classification_df[[key, "条目分类", "项目编码"]].drop_duplicates(), on=key, how="left" ).fillna({"条目分类": "未分类", "项目编码": "ALL"}) return result # ------------------------------- # 🎨 3. 布局 Layout # ------------------------------- app = dash.Dash(__name__, external_stylesheets=[dbc.themes.LUMEN], suppress_callback_exceptions=True) server = app.server app.layout = html.Div([ # html.H1("站点模型分类工具", style={'textAlign': 'center', 'margin': '30px'}), # 文件上传区 html.Div([ html.H4("选择Excel文件"), dcc.Upload( id='upload-data', children=html.Div(['拖拽文件到这里,或 ', html.A('点击选择文件')]), style={ 'width': '100%', 'height': '60px', 'lineHeight': '60px', 'borderWidth': '1px', 'borderStyle': 'dashed', 'borderRadius': '5px', 'textAlign': 'center', 'margin': '10px' }, multiple=False ), html.Div(id='file-info', style={'margin': '10px'}), html.Div(id='cache-status', style={'margin': '10px'}), ], style={'padding': '20px'}), # 主 Tabs dcc.Tabs(id="main-tabs", value='tab-import', children=[ # 导入预览 dcc.Tab(label='导入数据及预览', value='tab-import', children=[ html.H5("选择Sheet"), dcc.Dropdown(id='sheet-dropdown'), html.Div(id='validation-alert', style={'margin': '10px'}), html.Div(id='data-preview-table', style=TABLE_STYLE), ]), # 条目分类 dcc.Tab(id='tab-classify', label='条目分类', value='tab-classify', disabled=True, children=[ html.Div([ html.H4("条目分类管理"), html.Div([ html.Button("批量修改分类", id="btn-batch-category", n_clicks=0, style={'margin': '5px'}), html.Button("批量修改项目编码", id="btn-batch-code", n_clicks=0, style={'margin': '5px'}) ], style={'margin': '10px'}), html.Div(id='selection-count', style={'margin': '10px', 'color': '#666'}), # Modals dbc.Modal([ dbc.ModalHeader("批量修改 - 条目分类"), dbc.ModalBody([html.Label("选择新分类:"), dcc.Dropdown(id="modal-category-dropdown", options=CATEGORY_OPTIONS)]), dbc.ModalFooter([ html.Button("取消", id="close-category-modal", n_clicks=0), html.Button("确认修改", id="confirm-category-modal", n_clicks=0) ]) ], id="category-modal", is_open=False), dbc.Modal([ dbc.ModalHeader("批量修改 - 项目编码"), dbc.ModalBody([html.Label("输入新的项目编码(留空表示 ALL):"), dcc.Input(id="modal-code-input", type="text", placeholder="例如:PROJ001", style={'width': '100%'})]), dbc.ModalFooter([ html.Button("取消", id="close-code-modal", n_clicks=0), html.Button("确认修改", id="confirm-code-modal", n_clicks=0) ]) ], id="code-modal", is_open=False), # 分类表格 dash_table.DataTable( id='classification-table', editable=True, # row_deletable=True, row_selectable="multi", sort_action="native", filter_action="native", page_size=15, fixed_rows={'headers': True}, style_table=TABLE_STYLE, style_cell=CELL_TOOLTIP_STYLE, tooltip_duration=None ) ], style={'padding': '20px'}) ]), # 数据分析 dcc.Tab(id='tab-analyze', label='数据分析', value='tab-analyze', disabled=True, children=[ html.Div([ html.H4("分析结果"), html.Button("刷新结果", id="btn-refresh-result", n_clicks=0, style={'margin': '10px'}), html.Div(id='result-timestamp', style={'margin': '10px', 'color': '#555'}), dash_table.DataTable( id='analysis-result-table', page_size=20, style_table=TABLE_STYLE, style_cell=CELL_TOOLTIP_STYLE, tooltip_duration=None ), html.Div([ html.Button("导出全部数据", id="btn-export", n_clicks=0, style={'margin': '20px'}), dcc.Download(id="download-data") ]) ], style={'padding': '20px'}) ]) ]), # 存储组件 dcc.Store(id='stored-data-path'), dcc.Store(id='stored-sheet-name'), ]) # ------------------------------- # 🔁 回调 Callbacks # ------------------------------- from dash.exceptions import PreventUpdate @app.callback( [Output('file-info', 'children'), Output('cache-status', 'children'), Output('stored-data-path', 'data'), Output('sheet-dropdown', 'options'), Output('sheet-dropdown', 'value')], Input('upload-data', 'contents'), [State('upload-data', 'filename')] ) def handle_upload(contents, filename): if contents is None: raise PreventUpdate content_type, content_string = contents.split(',', 1) decoded = base64.b64decode(content_string) temp_path = f"./temp_{filename}" with open(temp_path, 'wb') as f: f.write(decoded) file_size = get_file_size_mb(temp_path) info_msg = f"📁 {filename} | 💾 {file_size} MB" pickle_path = temp_path + ".pickle" if os.path.exists(pickle_path): sheets_dict = load_from_pickle(pickle_path) cache_msg = "✅ 从缓存加载" else: sheets_dict, err = read_excel_with_sheets(temp_path) if err: return f"❌ 失败: {err}", "", None, [], None save_to_pickle(sheets_dict, pickle_path) cache_msg = "🆕 已缓存" sheet_options = [{'label': s, 'value': s} for s in sheets_dict.keys()] return info_msg, cache_msg, temp_path, sheet_options, sheet_options[0]['value'] @app.callback( [Output('data-preview-table', 'children'), Output('validation-alert', 'children'), Output('tab-classify', 'disabled'), Output('tab-analyze', 'disabled')], [Input('sheet-dropdown', 'value'), State('stored-data-path', 'data')] ) def update_preview(sheet, file_path): time_print("update preview...") if not file_path or not sheet: raise PreventUpdate try: df = load_from_pickle(file_path + ".pickle")[sheet] required = ["条目描述", "项目编码", "项目名称"] valid, missing = validate_columns(df, required) if valid: alert = "" can_proceed = True table = dash_table.DataTable( data=df.to_dict('records'), columns=[{"name": c, "id": c} for c in df.columns], page_size=20, style_table=TABLE_STYLE, style_cell=CELL_TOOLTIP_STYLE, tooltip_data=[ {c: {'value': str(v), 'type': 'markdown'} for c, v in row.items()} for row in df.to_dict('records') ], tooltip_duration=None ) else: alert = dbc.Alert(f"❌ 缺少字段: {', '.join(missing)}", color="danger") table = None can_proceed = False return table, alert, not can_proceed, not can_proceed except Exception as e: alert = dbc.Alert(f"❌ 预览失败: {e}", color="danger") return None, alert, True, True @app.callback( [Output('classification-table', 'data'), Output('classification-table', 'columns'), Output('classification-table', 'tooltip_data'), Output('classification-table', 'dropdown')], Input('main-tabs', 'value'), [State('stored-data-path', 'data'), State('sheet-dropdown', 'value')], prevent_initial_call=True ) def load_classification_data(tab, file_path, selected_sheet, ): time_print("load classification data...", tab,file_path, selected_sheet) if tab != 'tab-classify' or not file_path: # if not file_path or not selected_sheet: time_print("load_classification_data raise...") raise PreventUpdate try: class_file = os.path.join(os.path.dirname(file_path), "classification.xlsx") df_class = None # 尝试加载已有分类文件 if os.path.exists(class_file): df_class = pd.read_excel(class_file) required = ["条目描述", "条目分类", "项目编码"] valid, _ = validate_columns(df_class, required) if not valid: df_class = None # 若无有效分类文件,则从主表生成 if df_class is None: pickle_path = file_path + ".pickle" sheets_dict = load_from_pickle(pickle_path) main_df = sheets_dict[selected_sheet] if "条目描述" in main_df.columns: unique_desc = main_df["条目描述"].drop_duplicates().reset_index(drop=True) df_class = pd.DataFrame({ "条目描述": unique_desc, "条目分类": "", "项目编码": "" }) else: df_class = pd.DataFrame([{ "条目描述": "字段缺失", "条目分类": "", "项目编码": "" }]) # 确保 CATEGORY_OPTIONS 存在 options = CATEGORY_OPTIONS.copy() if CATEGORY_OPTIONS else [] # 构建列 columns = [ {"name": "条目描述", "id": "条目描述", "editable": False}, {"name": "条目分类", "id": "条目分类", "presentation": "dropdown", "editable": True}, {"name": "项目编码", "id": "项目编码", "editable": True, } ] # 构建数据 data = df_class.to_dict('records') if not data: data = [{"条目描述": "无数据", "条目分类": "", "项目编码": ""}] # 构建 tooltip tooltip_data = [ {k: {'value': str(v), 'type': 'markdown'} for k, v in row.items()} for row in data ] # ✅ 关键:确保 dropdown 结构正确 dropdown = { "条目分类": { "options": options or [] } } global CLASSIFICATION_CACHE CLASSIFICATION_CACHE = data.copy() if len(data) == 0: data = [{"条目描述": "", "条目分类": "", "项目编码": ""}] # time_print(dropdown) # columns, tooltip_data, # time_print(data.__len__()) # time_print(columns.__len__()) # time_print(tooltip_data.__len__()) # time_print(dropdown.__len__()) # time_print(data) # time_print(columns) # time_print(tooltip_data) # time_print(dropdown) time_print("return ...") return data, columns, tooltip_data, dropdown except Exception as e: time_print(f"[Error] 加载分类数据失败: {e}") # ✅ 返回兜底值,防止前端崩溃 fallback_cols = [ {"name": "条目描述", "id": "条目描述"}, {"name": "条目分类", "id": "条目分类", "presentation": "dropdown"}, {"name": "项目编码", "id": "项目编码"} ] empty_data = [{"条目描述": "加载失败", "条目分类": "", "项目编码": ""}] return ( empty_data, fallback_cols, [{}], {"条目分类": {"options": CATEGORY_OPTIONS or []}} ) @app.callback( [Output('category-modal', 'is_open'), Output('code-modal', 'is_open')], [Input('btn-batch-category', 'n_clicks'), Input('btn-batch-code', 'n_clicks'), Input('close-category-modal', 'n_clicks'), Input('close-code-modal', 'n_clicks'), Input('confirm-category-modal', 'n_clicks'), Input('confirm-code-modal', 'n_clicks')], [State('category-modal', 'is_open'), State('code-modal', 'is_open')], prevent_initial_call=True ) def toggle_modals(*args): time_print("toggle modals...") ctx = callback_context if not ctx.triggered: return False, False btn = ctx.triggered[0]['prop_id'].split('.')[0] if btn == 'btn-batch-category': return True, False elif btn == 'btn-batch-code': return False, True elif btn in ['close-category-modal', 'confirm-category-modal']: return False, args[-2] elif btn in ['close-code-modal', 'confirm-code-modal']: return args[-3], False return False, False # ============================================= # ✅ 修复 Bug:支持单行 inline 编辑 # ============================================= # @app.callback( # Output('classification-table', 'data', allow_duplicate=True), # Input('classification-table', 'data_timestamp'), # State('classification-table', 'data'), # prevent_initial_call=True # ) def capture_inline_edit(data_timestamp, current_data): time_print("capture inline edit...") if not current_data: raise PreventUpdate return current_data @app.callback( Output('classification-table', 'data', allow_duplicate=True), [Input('confirm-category-modal', 'n_clicks'), Input('confirm-code-modal', 'n_clicks')], [State('classification-table', 'data'), State('classification-table', 'selected_rows'), State('modal-category-dropdown', 'value'), State('modal-code-input', 'value')], prevent_initial_call=True ) def apply_batch_changes(nc1, nc2, data, selected_rows, category, code): time_print("apply_batch_changes...") if not data: raise PreventUpdate indices = selected_rows if selected_rows else range(len(data)) # 全选时应用所有行 if 'confirm-category-modal' in callback_context.triggered[0]['prop_id'] and category: # 分类修改 for i in indices: data[i]['条目分类'] = category # elif 'confirm-code-modal' in callback_context.triggered[0]['prop_id']: # final_code = code or "ALL" # for i in indices: # data[i]['项目编码'] = final_code return data @app.callback( Output('selection-count', 'children'), Input('classification-table', 'derived_viewport_selected_row_ids') ) def show_selection_count(selected): return f"✅ 已选中 {len(selected or [])} 行" if selected else "未选中任何行" @app.callback( [Output('analysis-result-table', 'data'), Output('analysis-result-table', 'columns'), Output('analysis-result-table', 'tooltip_data'), Output('result-timestamp', 'children')], # [Input('btn-refresh-result', 'n_clicks'), Input('main-tabs', 'value'), [State('stored-data-path', 'data'), State('sheet-dropdown', 'value'), State('classification-table', 'data')], prevent_initial_call=True ) def run_analysis(tab, file_path, sheet, class_data): # time_print("run analysis...") time_print("run analysis data...", tab,file_path, sheet) global RESULT_CACHE, RESULT_CACHE_TIME if tab != 'tab-analyze' or not file_path: time_print("run analysis raise...") raise PreventUpdate now = datetime.now() do_refresh = callback_context.triggered[0]['prop_id'].startswith('btn-refresh') expired = RESULT_CACHE_TIME is None or (now - RESULT_CACHE_TIME) > CACHE_TTL if do_refresh or expired: try: df = load_from_pickle(file_path + ".pickle")[sheet] class_df = pd.DataFrame(class_data) RESULT_CACHE = site_model(df, class_df) RESULT_CACHE_TIME = now except Exception as e: RESULT_CACHE = pd.DataFrame({"错误": [str(e)]}) data = RESULT_CACHE.to_dict('records') cols = [{"name": c, "id": c} for c in RESULT_CACHE.columns] tooltips = [{k: {'value': str(v), 'type': 'markdown'} for k, v in row.items()} for row in data] ts = RESULT_CACHE_TIME.strftime("%Y-%m-%d %H:%M:%S") if RESULT_CACHE_TIME else "无" return data, cols, tooltips, f"⏱️ 上次刷新: {ts}" @app.callback( Output("download-data", "data"), Input("btn-export", "n_clicks"), [State('stored-data-path', 'data'), State('sheet-dropdown', 'value'), State('classification-table', 'data'), State('analysis-result-table', 'data'), State('analysis-result-table', 'columns')], prevent_initial_call=True ) def export_data(n, file_path, sheet, class_data, result_data, result_cols): if not file_path: return None buffer = io.BytesIO() with pd.ExcelWriter(buffer, engine='openpyxl') as writer: original = load_from_pickle(file_path + ".pickle")[sheet] original.to_excel(writer, index=False, sheet_name="原始数据") pd.DataFrame(class_data).to_excel(writer, index=False, sheet_name="条目分类") pd.DataFrame(result_data).to_excel(writer, index=False, sheet_name="分析结果") buffer.seek(0) filename = f"站点模型分类结果_{datetime.now().strftime('%Y%m%d_%H%M%S')}.xlsx" return dcc.send_bytes(buffer.getvalue(), filename) # @app.callback( # Input('tab-classify', 'nclicks'), # ) # def test_tag_click(nclick): # time_print(f"click {nclick} times....") if __name__ == '__main__': app.run(debug=True, port=8050) 为什么不能跳转到tab-calssify,对比analysy分析
09-25
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值