{"id":10021275,"date":"2026-02-25T20:46:22","date_gmt":"2026-02-25T12:46:22","guid":{"rendered":"https:\/\/itmax.cn\/?p=10021275"},"modified":"2026-02-25T20:46:22","modified_gmt":"2026-02-25T12:46:22","slug":"%e5%be%ae%e4%bf%a1%e7%94%b5%e8%84%91%e7%89%884-1-7-30%e7%89%88%e6%9c%ac%e8%81%8a%e5%a4%a9%e8%ae%b0%e5%bd%95%e7%9b%91%e5%90%ac%e8%84%9a%e6%9c%ac%ef%bc%88%e5%b8%a6ui%e7%95%8c%e9%9d%a2%ef%bc%89","status":"publish","type":"post","link":"https:\/\/itmax.cn\/?p=10021275","title":{"rendered":"\u5fae\u4fe1\u7535\u8111\u72484.1.7.30\u7248\u672c\u804a\u5929\u8bb0\u5f55\u76d1\u542c\u811a\u672c\uff08\u5e26ui\u754c\u9762\uff09"},"content":{"rendered":"<h2>\u6458\u8981<\/h2>\n<p>\u5fae\u4fe14.1.x.x\u7248\u672c\u7684UI\u91c7\u7528\u65b0\u7684\u6846\u67b6\u5f00\u53d1\uff0c\u80fd\u591f\u83b7\u53d6\u5230\u7684\u4fe1\u606f\u6709\u9650\uff0c\u76ee\u524d\u53ea\u80fd\u83b7\u53d6\u5230\u6d88\u606f\u5217\u8868\u7684\u63a7\u4ef6\u5185\u5bb9\u3002<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" width=\"723\" height=\"531\" referrerpolicy=\"no-referrer\" src=\"https:\/\/segmentfault.com\/img\/bVdnYxh\" alt=\"image.png\" title=\"image.png\"><\/p>\n<h2>\u4ee3\u7801<\/h2>\n<p>\u8fd0\u884c\u540e\u9700\u8981\u5b89\u88c52\u4e2a\u5e93\uff0c\u6839\u636e\u63d0\u793a\u5b89\u88c5\u3002<\/p>\n<p><strong>\u5982\u4f55\u8fd0\u884c\uff1f<\/strong><\/p>\n<ul>\n<li>\u767b\u5f55\u5fae\u4fe1\u7535\u8111\u72484.1.7.30\uff0c\u4e0d\u80fd\u5b8c\u5168\u70b9\u51fb\u274c\u5173\u95ed\u754c\u9762\uff0c\u53ef\u4ee5\u6700\u5c0f\u5316\uff1b<\/li>\n<li>\u8fd0\u884c\u811a\u672c\uff1b<\/li>\n<li>UI\u754c\u9762\u542f\u52a8\u5373\u53ef\u81ea\u52a8\u83b7\u53d6\u804a\u5929\u5217\u8868\u7684\u6700\u65b0\u7684\u90a3\u6761\u6d88\u606f\uff1b<\/li>\n<\/ul>\n<p>\u5982\u679c\u65e0\u6cd5\u83b7\u53d6\u5230\uff0c\u8bf7\u9000\u51fa\u5fae\u4fe1\u91cd\u65b0\u767b\u5f55\u518d\u8bd5\u8bd5\u3002<\/p>\n<pre><code># -*- coding: utf-8 -*-\n# pip install pyqt5 uiautomation\nimport sys\nimport re\nimport datetime\nimport uiautomation as auto\nfrom PyQt5 import QtCore, QtWidgets\n\nTARGET_DEPTH = 14\n\n\ndef is_time_line(text: str):\n    text = text.strip()\n    return bool(re.match(r'^\/d{1,2}:\/d{2}$', text) or re.match(r'^\/d{2}\/\/d{2}$', text))\n\n\ndef parse_session(name_block: str):\n    lines = [l.strip() for l in name_block.splitlines()]\n    lines = [l for l in lines if l]\n    if not lines:\n        return None\n\n    session_name = lines[0]\n    time_text = None\n    for l in reversed(lines):\n        if is_time_line(l):\n            time_text = l\n            break\n\n    ignore_keywords = ['\u5df2\u7f6e\u9876', '\u6d88\u606f\u514d\u6253\u6270', '\u64a4\u9500']\n    message_line = None\n    for l in lines[1:]:\n        if l == time_text:\n            continue\n        if any(k in l for k in ignore_keywords):\n            continue\n        message_line = l\n\n    if not message_line:\n        return None\n\n    msg_type = &quot;\u6587\u672c&quot;\n    if ':' in message_line:\n        sender, content = message_line.split(':', 1)\n        return {\n            &quot;group_name&quot;: session_name,\n            &quot;sender&quot;: sender.strip().strip('&quot;'),\n            &quot;content&quot;: content.strip(),\n            &quot;time&quot;: time_text,\n            &quot;msg_type&quot;: msg_type\n        }\n\n    return {\n        &quot;group_name&quot;: session_name,\n        &quot;sender&quot;: &quot;\u6211&quot;,\n        &quot;content&quot;: message_line,\n        &quot;time&quot;: time_text,\n        &quot;msg_type&quot;: msg_type\n    }\n\n\ndef time_to_sort_key(time_str):\n    &quot;&quot;&quot;\u628a HH:MM \u6216 MM\/DD \u8f6c\u6210 datetime\uff0c\u7528\u4e8e\u6392\u5e8f&quot;&quot;&quot;\n    if not time_str:\n        return datetime.datetime.min\n    try:\n        if re.match(r'^\/d{1,2}:\/d{2}$', time_str):\n            h, m = map(int, time_str.split(&quot;:&quot;))\n            now = datetime.datetime.now()\n            return datetime.datetime(now.year, now.month, now.day, h, m)\n        elif re.match(r'^\/d{2}\/\/d{2}$', time_str):\n            month, day = map(int, time_str.split(&quot;\/&quot;))\n            now = datetime.datetime.now()\n            return datetime.datetime(now.year, month, day)\n    except:\n        return datetime.datetime.min\n    return datetime.datetime.min\n\n\nclass FetchThread(QtCore.QThread):\n    data_signal = QtCore.pyqtSignal(list)\n\n    def __init__(self, interval=1.5):\n        super().__init__()\n        self.interval = interval\n        self._running = False\n        auto.SetGlobalSearchTimeout(10)\n\n    def run(self):\n        self._running = True\n        while self._running:\n            try:\n                results = self.fetch_data()\n                if results:\n                    # \u6309\u65f6\u95f4\u5012\u5e8f\u6392\u5e8f\n                    results.sort(key=lambda x: time_to_sort_key(x.get(&quot;time&quot;)), reverse=True)\n                    self.data_signal.emit(results)\n            except:\n                pass\n            self.msleep(int(self.interval * 1000))\n\n    def stop(self):\n        self._running = False\n\n    def set_interval(self, interval):\n        self.interval = interval\n\n    def fetch_data(self):\n        result_list = []\n        root = auto.GetRootControl()\n        target = root.Control(searchDepth=5, ClassName='mmui::MainWindow')\n        if not target.Exists(2):\n            return result_list\n\n        def dump(control, depth=0):\n            if depth == TARGET_DEPTH:\n                try:\n                    if control.ClassName == 'mmui::ChatSessionCell' and control.Name:\n                        result = parse_session(control.Name)\n                        if result:\n                            result_list.append(result)\n                except:\n                    pass\n                return\n            for child in control.GetChildren():\n                dump(child, depth + 1)\n\n        dump(target)\n        return result_list\n\n\nclass MainWindow(QtWidgets.QWidget):\n    def __init__(self):\n        super().__init__()\n        self.resize(1300, 690)\n        self.setWindowFlags(QtCore.Qt.FramelessWindowHint)\n        self.setAttribute(QtCore.Qt.WA_TranslucentBackground)\n\n        self.main_layout = QtWidgets.QVBoxLayout(self)\n        self.main_layout.setContentsMargins(8, 8, 8, 8)\n\n        self.container = QtWidgets.QFrame()\n        self.container.setObjectName(&quot;container&quot;)\n        self.container_layout = QtWidgets.QVBoxLayout(self.container)\n        self.container_layout.setContentsMargins(20, 20, 20, 20)\n        self.main_layout.addWidget(self.container)\n\n        # \u9876\u90e8\u680f\n        title_bar = QtWidgets.QHBoxLayout()\n        self.container_layout.addLayout(title_bar)\n        self.title = QtWidgets.QLabel(&quot;\u5fae\u4fe1\u4f1a\u8bdd\u5b9e\u65f6\u76d1\u542c&quot;)\n        self.title.setStyleSheet(&quot;font-size:18px;font-weight:bold;&quot;)\n        self.close_btn = QtWidgets.QPushButton(&quot;\u2715&quot;)\n        self.close_btn.setFixedSize(36, 32)\n        self.close_btn.clicked.connect(self.close)\n        title_bar.addWidget(self.title)\n        title_bar.addStretch()\n        title_bar.addWidget(self.close_btn)\n\n        # \u5207\u6362\u6309\u94ae\n        self.toggle_btn = QtWidgets.QPushButton()\n        self.toggle_btn.setFixedSize(120, 60)\n        self.toggle_btn.setStyleSheet(&quot;font-size:18px;font-weight:bold;&quot;)\n        self.container_layout.addWidget(self.toggle_btn, alignment=QtCore.Qt.AlignLeft)\n\n        # \u8868\u683c\n        self.table = QtWidgets.QTableWidget()\n        self.table.setColumnCount(5)\n        self.table.setHorizontalHeaderLabels(\n            [&quot;\u6635\u79f0&quot;, &quot;\u53d1\u9001\u8005&quot;, &quot;\u6d88\u606f\u7c7b\u578b&quot;, &quot;\u5185\u5bb9&quot;, &quot;\u65f6\u95f4&quot;]\n        )\n        self.table.horizontalHeader().setSectionResizeMode(QtWidgets.QHeaderView.Stretch)\n        self.table.verticalHeader().setVisible(False)\n        self.table.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers)\n        self.table.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows)\n        self.table.setWordWrap(True)\n        self.container_layout.addWidget(self.table)\n\n        # \u7ebf\u7a0b\n        self.thread = FetchThread(interval=1.5)\n        self.thread.data_signal.connect(self.update_table)\n\n        self.toggle_btn.clicked.connect(self.toggle_listen)\n\n        self.setStyleSheet(&quot;&quot;&quot;\n        QWidget {\n            font-family: &quot;Microsoft YaHei&quot;;\n            color: #e6e6e6;\n        }\n        #container {\n            background-color: #1e1f26;\n            border-radius: 14px;\n        }\n        QPushButton {\n            border-radius: 10px;\n            font-size:18px;\n            font-weight:bold;\n        }\n        QTableWidget {\n            background-color: #232530;\n            gridline-color: #2f3240;\n        }\n        QHeaderView::section {\n            background-color: #2d2f3a;\n            border: none;\n            padding: 6px;\n        }\n        &quot;&quot;&quot;)\n\n        self._drag_pos = None\n        self.listening = False\n        self.start_listen()\n        self.shown_msgs = set()\n\n    # \u62d6\u52a8\n    def mousePressEvent(self, event):\n        if event.button() == QtCore.Qt.LeftButton:\n            self._drag_pos = event.globalPos() - self.frameGeometry().topLeft()\n            event.accept()\n\n    def mouseMoveEvent(self, event):\n        if self._drag_pos and event.buttons() == QtCore.Qt.LeftButton:\n            self.move(event.globalPos() - self._drag_pos)\n            event.accept()\n\n    def mouseReleaseEvent(self, event):\n        self._drag_pos = None\n\n    # \u76d1\u542c\u63a7\u5236\n    def toggle_listen(self):\n        if self.listening:\n            self.stop_listen()\n        else:\n            self.start_listen()\n\n    def start_listen(self):\n        if not self.thread.isRunning():\n            self.thread.start()\n        self.listening = True\n        self.toggle_btn.setText(&quot;\u505c\u6b62\u76d1\u542c&quot;)\n        self.toggle_btn.setStyleSheet(&quot;&quot;&quot;\n            QPushButton {\n                background-color: #c0392b;\n                border-radius: 10px;\n                font-size:18px;\n                font-weight:bold;\n            }\n            QPushButton:hover {\n                background-color: #e74c3c;\n            }\n        &quot;&quot;&quot;)\n\n    def stop_listen(self):\n        self.thread.stop()\n        self.listening = False\n        self.toggle_btn.setText(&quot;\u5f00\u59cb\u76d1\u542c&quot;)\n        self.toggle_btn.setStyleSheet(&quot;&quot;&quot;\n            QPushButton {\n                background-color: #27ae60;\n                border-radius: 10px;\n                font-size:18px;\n                font-weight:bold;\n            }\n            QPushButton:hover {\n                background-color: #2ecc71;\n            }\n        &quot;&quot;&quot;)\n\n    # \u8868\u683c\u8ffd\u52a0\uff0c\u6700\u65b0\u5728\u4e0a\n    def add_center_item(self, row, col, text):\n        item = QtWidgets.QTableWidgetItem(text)\n        item.setTextAlignment(QtCore.Qt.AlignCenter)\n        self.table.setItem(row, col, item)\n\n    # \u4fee\u6539 update_table \u65b9\u6cd5\n    def update_table(self, data):\n        for item in data:\n            msg_key = (item[&quot;group_name&quot;], item[&quot;sender&quot;], item[&quot;content&quot;], item[&quot;time&quot;])\n            if msg_key in self.shown_msgs:\n                continue  # \u5df2\u6e32\u67d3\u8fc7\u5c31\u8df3\u8fc7\n            self.shown_msgs.add(msg_key)\n\n            self.table.insertRow(0)  # \u6700\u65b0\u6d88\u606f\u63d2\u5728\u6700\u4e0a\u9762\n            self.add_center_item(0, 0, item[&quot;group_name&quot;])\n            self.add_center_item(0, 1, item[&quot;sender&quot;])\n            self.add_center_item(0, 2, item[&quot;msg_type&quot;])\n            content_item = QtWidgets.QTableWidgetItem(item[&quot;content&quot;])\n            content_item.setTextAlignment(QtCore.Qt.AlignCenter)\n            content_item.setToolTip(item[&quot;content&quot;])\n            self.table.setItem(0, 3, content_item)\n            self.add_center_item(0, 4, item[&quot;time&quot;] or &quot;&quot;)\n            self.table.setRowHeight(0, 60)\n            self.table.setColumnWidth(3, 300)\n\n\nif __name__ == &quot;__main__&quot;:\n    app = QtWidgets.QApplication(sys.argv)\n    window = MainWindow()\n    window.show()\n    sys.exit(app.exec_())<\/code><\/pre>\n<h2>\u754c\u9762<\/h2>\n<p><img decoding=\"async\" width=\"723\" height=\"388\" referrerpolicy=\"no-referrer\" src=\"https:\/\/segmentfault.com\/img\/bVdnYyU\" alt=\"image.png\" title=\"image.png\" loading=\"lazy\"><\/p>\n<h2>\u672c\u6587\u4f5c\u8005<\/h2>\n<p>TANKING<\/p>\n<p><a href=\"https:\/\/segmentfault.com\/a\/1190000047621009\">https:\/\/segmentfault.com\/a\/1190000047621009<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>\u6458\u8981 \u5fae\u4fe14.1.x.x\u7248\u672c\u7684UI\u91c7\u7528\u65b0\u7684\u6846\u67b6\u5f00\u53d1\uff0c\u80fd\u591f\u83b7\u53d6\u5230\u7684\u4fe1\u606f\u6709\u9650\uff0c\u76ee\u524d\u53ea\u80fd\u83b7\u53d6\u5230\u6d88\u606f\u5217\u8868\u7684\u63a7\u4ef6\u5185\u5bb9\u3002  [&hellip;]<\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_jetpack_memberships_contains_paid_content":false,"footnotes":""},"categories":[4],"tags":[],"class_list":["post-10021275","post","type-post","status-publish","format-standard","hentry","category-share"],"jetpack_sharing_enabled":true,"jetpack_featured_media_url":"","_links":{"self":[{"href":"https:\/\/itmax.cn\/index.php?rest_route=\/wp\/v2\/posts\/10021275","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/itmax.cn\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/itmax.cn\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/itmax.cn\/index.php?rest_route=\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/itmax.cn\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=10021275"}],"version-history":[{"count":0,"href":"https:\/\/itmax.cn\/index.php?rest_route=\/wp\/v2\/posts\/10021275\/revisions"}],"wp:attachment":[{"href":"https:\/\/itmax.cn\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=10021275"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/itmax.cn\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=10021275"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/itmax.cn\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=10021275"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}