细节优化

main
risingLee 2026-02-07 01:57:05 +08:00
parent a0417d080c
commit 44c582487e
3 changed files with 156 additions and 38 deletions

63
dashboard.json Normal file
View File

@ -0,0 +1,63 @@
{
"canvas": {
"x": 0.0,
"y": 0.0,
"w": 1920.0,
"h": 1080.0
},
"widgets": [
{
"widget_type": "label",
"x": 173.0,
"y": 210.0,
"w": 162.0,
"h": 62.0,
"z": 1.0,
"config": {
"fieldName": "主轴承#3",
"prefix": "主轴承温度",
"suffix": " ℃",
"fontSize": 16,
"color": "#FFFFFF"
}
},
{
"widget_type": "label",
"x": 46.0,
"y": 378.0,
"w": 162.0,
"h": 62.0,
"z": 1.0,
"config": {
"fieldName": "减速箱小轴承2",
"prefix": "减速箱小轴承温度",
"suffix": "℃",
"fontSize": 16,
"color": "#FFFFFF"
}
},
{
"widget_type": "image",
"x": 0.0,
"y": 0.0,
"w": 650.0,
"h": 865.0,
"z": 0.0,
"config": {
"imagePath": "D:/1-2.JPG"
}
},
{
"widget_type": "web",
"x": 648.5,
"y": 0.0,
"w": 887.0,
"h": 863.0,
"z": 0.0,
"config": {
"url": "http://127.0.0.1:8086/orgs/b2542eeb72a3e614/dashboards/0f8ab8a328fe9000?lower=now%28%29+-+1h",
"locked": false
}
}
]
}

View File

@ -4,5 +4,5 @@
"org": "MEASCON", "org": "MEASCON",
"bucket": "PCM", "bucket": "PCM",
"interval_ms": 1000, "interval_ms": 1000,
"query": "from(bucket: \"PCM\")\n |> range(start: -3h)\n |> filter(fn: (r) => r._measurement == \"PCM_Measurement\")\n |> filter(fn: (r) => r.data_type == \"LSDAQ\")\n |> keep(columns: [\"_time\", \"_field\", \"_value\"])\n |> last()" "query": "from(bucket: \"PCM\")\n |> range(start: -24h)\n |> filter(fn: (r) => r._measurement == \"PCM_Measurement\")\n |> filter(fn: (r) => r.data_type == \"LSDAQ\")\n |> keep(columns: [\"_time\", \"_field\", \"_value\"])\n |> last()"
} }

129
main.py
View File

@ -201,6 +201,25 @@ class DashboardItem(QGraphicsRectItem):
super().paint(painter, option, widget) super().paint(painter, option, widget)
def to_state(self) -> WidgetState: def to_state(self) -> WidgetState:
"""将组件状态转换为 WidgetState使用精确的 pos() 和 rect(),而不是 sceneBoundingRect()"""
pos = self.pos()
r = self.rect()
return WidgetState(
widget_type=self._get_widget_type(),
x=float(pos.x()),
y=float(pos.y()),
w=float(r.width()),
h=float(r.height()),
z=float(self.zValue()),
config=self._get_config(),
)
def _get_widget_type(self) -> str:
"""子类需要重写,返回组件类型"""
raise NotImplementedError
def _get_config(self) -> Dict[str, Any]:
"""子类需要重写,返回配置字典"""
raise NotImplementedError raise NotImplementedError
def apply_state(self, state: WidgetState): def apply_state(self, state: WidgetState):
@ -387,17 +406,11 @@ class ImageItem(DashboardItem):
) )
self._pixmap_item.setScale(scale) self._pixmap_item.setScale(scale)
def to_state(self) -> WidgetState: def _get_widget_type(self) -> str:
r = self.sceneBoundingRect() return "image"
return WidgetState(
widget_type="image", def _get_config(self) -> Dict[str, Any]:
x=r.x(), return {"imagePath": self._image_path}
y=r.y(),
w=r.width(),
h=r.height(),
z=float(self.zValue()),
config={"imagePath": self._image_path},
)
def apply_state(self, state: WidgetState): def apply_state(self, state: WidgetState):
super().apply_state(state) super().apply_state(state)
@ -484,17 +497,11 @@ class LabelItem(DashboardItem):
if self._field_name and self._field_name in data: if self._field_name and self._field_name in data:
self._update_preview_text(data[self._field_name]) self._update_preview_text(data[self._field_name])
def to_state(self) -> WidgetState: def _get_widget_type(self) -> str:
r = self.sceneBoundingRect() return "label"
return WidgetState(
widget_type="label", def _get_config(self) -> Dict[str, Any]:
x=r.x(), return self.label_config()
y=r.y(),
w=r.width(),
h=r.height(),
z=float(self.zValue()),
config=self.label_config(),
)
def apply_state(self, state: WidgetState): def apply_state(self, state: WidgetState):
super().apply_state(state) super().apply_state(state)
@ -558,20 +565,14 @@ class WebItem(DashboardItem):
def is_locked(self) -> bool: def is_locked(self) -> bool:
return self._locked return self._locked
def to_state(self) -> WidgetState: def _get_widget_type(self) -> str:
r = self.sceneBoundingRect() return "web"
return WidgetState(
widget_type="web", def _get_config(self) -> Dict[str, Any]:
x=r.x(), return {
y=r.y(), "url": self._url,
w=r.width(), "locked": self._locked,
h=r.height(), }
z=float(self.zValue()),
config={
"url": self._url,
"locked": self._locked,
},
)
def apply_state(self, state: WidgetState): def apply_state(self, state: WidgetState):
super().apply_state(state) super().apply_state(state)
@ -715,9 +716,35 @@ class DashboardView(QGraphicsView):
# 布局持久化 # 布局持久化
def save_layout(self, path: str): def save_layout(self, path: str):
states = [] states = []
canvas_bounds = self._scene.canvas_rect()
canvas_pos = self._scene.canvas_item.pos()
canvas_left = canvas_pos.x()
canvas_top = canvas_pos.y()
canvas_right = canvas_left + canvas_bounds.width()
canvas_bottom = canvas_top + canvas_bounds.height()
for it in self._scene.items(): for it in self._scene.items():
if isinstance(it, DashboardItem): if isinstance(it, DashboardItem):
states.append(asdict(it.to_state())) # 获取组件状态
state = it.to_state()
state_dict = asdict(state)
# 修正超出边界的位置数据(直接修改字典,不移动组件)
item_left = state_dict["x"]
item_top = state_dict["y"]
item_w = state_dict["w"]
item_h = state_dict["h"]
# 修正超出边界的位置数据
final_x = max(canvas_left, min(item_left, canvas_right - item_w))
final_y = max(canvas_top, min(item_top, canvas_bottom - item_h))
# 更新状态数据
state_dict["x"] = final_x
state_dict["y"] = final_y
states.append(state_dict)
with open(path, "w", encoding="utf-8") as f: with open(path, "w", encoding="utf-8") as f:
payload = { payload = {
"canvas": self._scene.canvas_state(), "canvas": self._scene.canvas_state(),
@ -769,6 +796,28 @@ class DashboardView(QGraphicsView):
else: else:
continue continue
it.apply_state(ws) it.apply_state(ws)
# 加载后也应用边界限制,确保组件不超出画布
canvas_bounds = self._scene.canvas_rect()
canvas_pos = self._scene.canvas_item.pos()
canvas_left = canvas_pos.x()
canvas_top = canvas_pos.y()
canvas_right = canvas_left + canvas_bounds.width()
canvas_bottom = canvas_top + canvas_bounds.height()
pos = it.pos()
r = it.rect()
item_left = pos.x()
item_top = pos.y()
item_right = item_left + r.width()
item_bottom = item_top + r.height()
# 修正超出边界的位置
final_x = max(canvas_left, min(item_left, canvas_right - r.width()))
final_y = max(canvas_top, min(item_top, canvas_bottom - r.height()))
if final_x != item_left or final_y != item_top:
it.setPos(final_x, final_y)
# ----------------------------- # -----------------------------
@ -1106,6 +1155,8 @@ class MainWindow(QMainWindow):
super().__init__() super().__init__()
self.setWindowTitle("PCM Viewer - Widgets Dashboard") self.setWindowTitle("PCM Viewer - Widgets Dashboard")
self.resize(1400, 840) self.resize(1400, 840)
# 设置焦点策略,确保能够接收键盘事件(特别是 ESC 键)
self.setFocusPolicy(Qt.FocusPolicy.StrongFocus)
self._edit_mode = True self._edit_mode = True
# 全屏编辑标志(展示模式本身也会全屏) # 全屏编辑标志(展示模式本身也会全屏)
self._fullscreen_edit = False self._fullscreen_edit = False
@ -1219,12 +1270,16 @@ class MainWindow(QMainWindow):
if want_full: if want_full:
# 真全屏:使用 Qt 的 showFullScreen退出时用 showMaximized 恢复普通 Windows 窗口 # 真全屏:使用 Qt 的 showFullScreen退出时用 showMaximized 恢复普通 Windows 窗口
self.showFullScreen() self.showFullScreen()
# 全屏后确保窗口获得焦点以便接收键盘事件ESC 键)
self.setFocus()
# 全屏时关闭滚动条,避免 1920x1080 屏幕上还出现滚动条 # 全屏时关闭滚动条,避免 1920x1080 屏幕上还出现滚动条
self.view.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff) self.view.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
self.view.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff) self.view.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
else: else:
# 恢复为标准的最大化窗口(带边框/任务栏) # 恢复为标准的最大化窗口(带边框/任务栏)
self.showMaximized() self.showMaximized()
# 恢复焦点,确保能够接收键盘事件
self.setFocus()
self.view.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAsNeeded) self.view.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAsNeeded)
self.view.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAsNeeded) self.view.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAsNeeded)