<think>我们将修改Dash应用,使得在打开网页时自动显示第一个选项卡。这可以通过设置`dcc.Tabs`组件的`value`属性为第一个选项卡的id来实现。由于我们之前创建选项卡时没有显式指定id,所以我们需要调整选项卡创建函数,为每个选项卡分配一个id,并在布局中设置初始选中的选项卡。
修改点:
1. 在创建每个选项卡时,指定一个id(例如'tab1', 'tab2', ...)
2. 在`dcc.Tabs`中设置`value`为第一个选项卡的id(即'tab1')
同时,为了在回调中能够区分不同的选项卡,我们还需要为每个动态加载的选项卡指定一个value(即id),并在回调中根据这个value来触发更新。
具体步骤:
- 修改选项卡创建函数,增加id参数
- 在布局中设置Tabs的value为'tab1'
另外,由于我们之前使用`create_tab1`和`create_tab2`创建前两个选项卡,而后面三个使用`create_dynamic_tab`,我们需要统一为每个选项卡指定一个id(即value)。
注意:选项卡的value必须是唯一的。
我们调整如下:
选项卡1:id='tab1'
选项卡2:id='tab2'
选项卡3:id='tab3'
选项卡4:id='tab4'
选项卡5:id='tab5'
然后,在回调函数中,我们根据active_tab的值(即当前选中的选项卡id)来更新内容。
修改代码:
1. 修改`create_tab1`和`create_tab2`函数,在`dcc.Tab`中指定value(即id)
2. 修改`create_dynamic_tab`函数,增加value参数(即选项卡id)
3. 在布局中,设置`dcc.Tabs`的value为'tab1'
同时,回调函数中判断的条件也要改为对应的id(例如'tab3'、'tab4'、'tab5')
由于之前回调函数中已经使用了`active_tab`的值,并且我们之前设置的选项卡id是'tab1'到'tab5',所以回调函数中判断的条件需要和这些id对应。
我们修改回调函数:
当active_tab为'tab3'时,更新超速位置选项卡;为'tab4'时更新每日分析;为'tab5'时更新每周分析。
另外,由于我们之前没有为前两个选项卡设置id,所以现在需要设置。
以下是修改后的完整代码:</think>下面是一个修改后的完整代码,实现在打开网页时自动显示第一个选项卡(安智连GPS实时大屏)的功能:
```python
import os
import dash
from dash import dcc, html
from dash.dependencies import Input, Output
import dash_bootstrap_components as dbc
import flask
import socket
import datetime
from glob import glob
# 初始化Dash应用
app = dash.Dash(
__name__,
title="SESS班车GPS管理查询分析系统",
external_stylesheets=[dbc.themes.BOOTSTRAP],
assets_folder="assets" # 静态文件目录
)
server = app.server # 获取Flask服务器实例
# 配置常量
SERVER_IP = "109.136.150.21" # 服务器IP
PORT = 8088 # 服务器端口
WHITELIST = ["192.168.1.100", "10.0.0.55"] # 权限白名单(替换为实际IP)
LOG_FILE = "logon.log" # 日志文件路径
ASSETS_DIR = "./assets" # HTML文件存储目录
# 创建日志文件(如果不存在)
if not os.path.exists(LOG_FILE):
with open(LOG_FILE, "w") as f:
f.write("SESS系统访问日志\n")
# ======================
# 权限控制中间件
# ======================
@server.before_request
def check_permission():
"""拦截请求进行权限验证"""
client_ip = flask.request.remote_addr
# 获取客户端计算机名(可能不可靠)
try:
client_host = socket.gethostbyaddr(client_ip)[0]
except:
client_host = "Unknown"
# 记录访问日志
log_entry = (
f"Time: {datetime.datetime.now()}, "
f"IP: {client_ip}, "
f"Hostname: {client_host}, "
f"Path: {flask.request.path}"
)
# 白名单检查
if client_ip not in WHITELIST and flask.request.path == "/":
log_entry += ", Status: DENIED"
with open(LOG_FILE, "a") as f:
f.write(log_entry + "\n")
return html.Div([
html.H1("访问被拒绝"),
html.P("没有获得远程访问权限,请联系开发者"),
html.P(f"您的IP: {client_ip}")
]), 403
# 记录合法访问
log_entry += ", Status: ALLOWED"
with open(LOG_FILE, "a") as f:
f.write(log_entry + "\n")
# ======================
# 布局组件函数
# ======================
def create_tab1():
"""创建选项卡1:安智连GPS实时大屏"""
return dcc.Tab(
label="安智连GPS实时大屏",
value="tab1", # 添加唯一标识符
children=html.Iframe(
src="https://doc.azliot.com/equipmentDistribution?tfKey=821a3d78112fae663745f161094e00e5&tk=eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJjb21wYW55S2V5IjoiNjA2NDkzM2QxN2E5MGE2MjRmZGNmMDM5YjY2OGM5ZjkiLCJleHAiOjE5MDc2MjU0MDIsInVzZXJTZWNyZXREZXMiOiJUcGlyb0lNWnJNZWJlSVpKOFR5aE42UnJqazh4cGFXR3ZQbnNNaC8yZ0ZNV25TaGs2TGRRbWc9PSIsImlhdCI6MTc0NTYyNTQwMiwidXNlcktleSI6ImI3Y2Q0Yzc3ZjRiNmY2OTVkYzk3ZTE5MjQzZjkyY2IxIn0.OVqyYVQrW3-gimOoTpPZuSGUCWNgpharxC9JVRXe_FfREcCMcD_mRGV0ylbZ5BLlQTGBQRwDViVnn2Cx0QGklAaPQdTHs3Szcabw0cro77XpFIhhQH3OQS8Rq7CAO2r9-hxyvSY0_xV2pRy8QYmb4m5y7opz5vnuIo4zrSorA1Y",
style={"width": "100%", "height": "85vh", "border": "none"}
)
)
def create_tab2():
"""创建选项卡2:SESS车队GPS实时定位"""
return dcc.Tab(
label="SESS车队GPS实时定位",
value="tab2", # 添加唯一标识符
children=html.Iframe(
src="https://doc.azliot.com/adminH5/home?tfKey=821a3d78112fae663745f161094e00e5&tk=eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJjb21wYW55S2V5IjoiNjA2NDkzM2QxN2E5MGE2MjRmZGNmMDM5YjY2OGM5ZjkiLCJleHAiOjE5MDc2MjU4MDIsInVzZXJTZWNyZXREZXMiOiJUcGlyb0lNWnJNZWJlSVpKOFR5aE42UnJqazh4cGFXR3ZQbnNNaC8yZ0ZNV25TaGs2TGRRbWc9PSIsImlhdCI6MTc0NTYyNTQwMiwidXNlcktleSI6ImI3Y2Q0Yzc3ZjRiNmY2OTVkYzk3ZTE5MjQzZjkyY2IxIn0.OVqyYVQrW3-gimOoTpPZuSGUCWNgpharxC9JVRXe_FfREcCMcD_mRGV0ylbZ5BLlQTGBQRwDViVnn2Cx0QGklAaPQdTHs3Szcabw0cro77XpFIhhQH3OQS8Rq7CAO2r9-hxyvSY0_xV2pRy8QYmb4m5y7opz5vnuIo4zrSorA1Y",
style={"width": "100%", "height": "85vh", "border": "none"}
)
)
def create_dynamic_tab(tab_id, iframe_id, pattern):
"""创建动态加载的选项卡模板"""
return dcc.Tab(
label=tab_id,
value=tab_id.lower().replace(" ", "-"), # 生成唯一标识符
children=html.Div([
html.Iframe(
id=iframe_id,
style={"width": "100%", "height": "85vh", "border": "none"}
)
])
)
# ======================
# 文件处理函数
# ======================
def find_latest_html(pattern):
"""
查找匹配模式的最新HTML文件
参数:
pattern (str): 文件名包含的关键字
返回:
str: 最新文件的URL路径
"""
# 获取匹配文件列表
files = glob(os.path.join(ASSETS_DIR, f"*{pattern}*.html"))
if not files:
return "/assets/error.html" # 返回错误页面
# 按修改时间排序获取最新文件
latest_file = max(files, key=os.path.getmtime)
return f"/assets/{os.path.basename(latest_file)}"
# ======================
# 应用布局
# ======================
app.layout = html.Div([
html.H1("SESS班车GPS管理查询分析系统",
style={"textAlign": "center", "padding": "20px"}),
# 设置默认激活的选项卡为第一个
dcc.Tabs(
id="main-tabs",
value="tab1", # 默认激活第一个选项卡
children=[
create_tab1(), # 选项卡1
create_tab2(), # 选项卡2
create_dynamic_tab("SESS车队超速位置可视化查询", "iframe-overspeed", "overspeed"),
create_dynamic_tab("每日车辆运行数据可视化分析", "iframe-daily", "main"),
create_dynamic_tab("每周车辆运行数据可视化分析", "iframe-weekly", "month")
]
)
])
# ======================
# 回调函数
# ======================
@app.callback(
Output("iframe-overspeed", "src"),
[Input("main-tabs", "value")]
)
def update_overspeed_tab(active_tab):
"""更新超速位置选项卡内容"""
# 使用更精确的匹配条件
if active_tab == "sess车队超速位置可视化查询":
return find_latest_html("overspeed")
return dash.no_update
@app.callback(
Output("iframe-daily", "src"),
[Input("main-tabs", "value")]
)
def update_daily_tab(active_tab):
"""更新每日分析选项卡内容"""
if active_tab == "每日车辆运行数据可视化分析":
return find_latest_html("main")
return dash.no_update
@app.callback(
Output("iframe-weekly", "src"),
[Input("main-tabs", "value")]
)
def update_weekly_tab(active_tab):
"""更新每周分析选项卡内容"""
if active_tab == "每周车辆运行数据可视化分析":
return find_latest_html("month")
return dash.no_update
# ======================
# 主函数
# ======================
if __name__ == "__main__":
# 创建assets目录(如果不存在)
os.makedirs(ASSETS_DIR, exist_ok=True)
# 创建示例错误页面
error_page = os.path.join(ASSETS_DIR, "error.html")
if not os.path.exists(error_page):
with open(error_page, "w") as f:
f.write("<h1>文件未找到</h1><p>请联系系统管理员</p>")
# 启动服务器
app.run_server(
host=SERVER_IP,
port=PORT,
debug=False,
dev_tools_ui=False
)
```
### 关键修改说明
#### 1. 设置默认激活的选项卡
在`dcc.Tabs`组件中添加了`value="tab1"`属性,确保第一个选项卡在页面加载时自动激活:
```python
dcc.Tabs(
id="main-tabs",
value="tab1", # 默认激活第一个选项卡
children=[...]
)
```
#### 2. 为选项卡添加唯一标识符
为每个选项卡添加了`value`属性作为唯一标识符,这是激活特定选项卡的关键:
```python
def create_tab1():
return dcc.Tab(
label="安智连GPS实时大屏",
value="tab1", # 唯一标识符
...
)
def create_dynamic_tab(tab_id, iframe_id, pattern):
return dcc.Tab(
label=tab_id,
value=tab_id.lower().replace(" ", "-"), # 生成唯一标识符
...
)
```
#### 3. 优化回调函数匹配逻辑
修改了回调函数的匹配条件,使用更精确的标识符进行匹配:
```python
def update_overspeed_tab(active_tab):
if active_tab == "sess车队超速位置可视化查询": # 使用动态生成的标识符
...
```
### 工作原理
1. **页面加载流程**:
- 用户访问网站时,服务器返回页面
- `dcc.Tabs`组件根据`value="tab1"`属性自动激活第一个选项卡
- 第一个选项卡的内容(安智连GPS实时大屏)立即显示
2. **选项卡切换机制**:
- 每个选项卡都有唯一的`value`标识符
- 当用户点击其他选项卡时,`main-tabs`组件的值会更新为对应选项卡的标识符
- 回调函数根据当前激活的标识符决定是否更新内容
3. **动态内容加载**:
- 对于后三个选项卡,内容只在激活时加载
- 使用`dash.no_update`避免不必要的刷新
- 文件查找逻辑只在需要时执行
### 优势与效果
1. **用户体验提升**:
- 页面打开立即显示最常用的安智连GPS实时大屏
- 避免用户需要手动切换到第一个选项卡
- 减少初始加载时间(后三个选项卡内容延迟加载)
2. **性能优化**:
- 避免同时加载所有选项卡内容
- 减少初始页面加载的网络请求
- 资源按需加载,提高响应速度
3. **代码可维护性**:
- 清晰的标识符系统
- 模块化的选项卡创建函数
- 回调函数逻辑明确分离
### 使用建议
1. **标识符命名规范**:
- 保持标识符简短、一致且有意义
- 避免使用特殊字符和空格
- 建议使用英文命名(如`real-time-monitor`)
2. **扩展性考虑**:
- 如需添加更多选项卡,确保标识符唯一
- 可考虑将选项卡配置存储在外部文件中
- 使用工厂函数批量创建选项卡
3. **用户反馈机制**:
- 在加载动态内容时添加加载指示器
- 对于大文件加载提供进度条
- 添加错误处理机制(如文件未找到)