From abbbbb1154ffba6544c0a70a322bf487eda03a7e Mon Sep 17 00:00:00 2001 From: John Smith Date: Fri, 15 Mar 2024 22:37:13 +0800 Subject: [PATCH] =?UTF-8?q?GUI=E6=8F=92=E4=BB=B6=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E5=AF=BC=E5=87=BAExcel?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- plugins/native-ui/designer/native-ui.fbp | 96 ++++++++++++++++++++++-- plugins/native-ui/designer/ui_base.py | 18 ++++- plugins/native-ui/requirements.txt | 1 + plugins/native-ui/ui/room_frame.py | 79 ++++++++++++++++--- 4 files changed, 177 insertions(+), 17 deletions(-) diff --git a/plugins/native-ui/designer/native-ui.fbp b/plugins/native-ui/designer/native-ui.fbp index ba427e0..29f7f77 100644 --- a/plugins/native-ui/designer/native-ui.fbp +++ b/plugins/native-ui/designer/native-ui.fbp @@ -242,7 +242,7 @@ 5 - + wxLEFT 0 1 @@ -443,7 +443,7 @@ 付费消息 - 1 + 0 1 1 @@ -824,7 +824,7 @@ 统计 - 0 + 1 1 1 @@ -1021,6 +1021,92 @@ -1 + + 5 + wxALL|wxEXPAND + 0 + + + bSizer11 + wxHORIZONTAL + none + + 5 + + 0 + + 1 + 1 + 1 + 1 + + + + + 0 + + + + + 1 + 0 + 1 + + 1 + + 0 + 0 + + Dock + 0 + Left + 0 + 1 + + 1 + + + 0 + 0 + wxID_ANY + 导出Excel + + 0 + + 0 + + + 0 + + 1 + export_excel_button + 1 + + + protected + 1 + + + + Resizable + 1 + + + ; ; forward_declare + 0 + + + wxFILTER_NONE + wxDefaultValidator + + + + + _on_export_excel_button_click + + + + @@ -1959,7 +2045,7 @@ 0 0 - wxID_ANY + wxID_OK 确定 0 @@ -2033,7 +2119,7 @@ 0 0 - wxID_ANY + wxID_CANCEL 取消 0 diff --git a/plugins/native-ui/designer/ui_base.py b/plugins/native-ui/designer/ui_base.py index d41351c..60d6929 100644 --- a/plugins/native-ui/designer/ui_base.py +++ b/plugins/native-ui/designer/ui_base.py @@ -40,7 +40,7 @@ class RoomFrameBase ( wx.Frame ): bSizer3.Add( ( 0, 0), 1, wx.EXPAND, 5 ) self.collapse_console_button = wx.Button( self, wx.ID_ANY, u">>", wx.DefaultPosition, wx.DefaultSize, 0 ) - bSizer3.Add( self.collapse_console_button, 0, 0, 5 ) + bSizer3.Add( self.collapse_console_button, 0, wx.LEFT, 5 ) bSizer2.Add( bSizer3, 0, wx.ALL|wx.EXPAND, 5 ) @@ -64,7 +64,7 @@ class RoomFrameBase ( wx.Frame ): self.paid_panel.SetSizer( bSizer4 ) self.paid_panel.Layout() bSizer4.Fit( self.paid_panel ) - self.console_notebook.AddPage( self.paid_panel, u"付费消息", True ) + self.console_notebook.AddPage( self.paid_panel, u"付费消息", False ) self.super_chat_panel = wx.Panel( self.console_notebook, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.TAB_TRAVERSAL ) bSizer5 = wx.BoxSizer( wx.VERTICAL ) @@ -103,11 +103,19 @@ class RoomFrameBase ( wx.Frame ): bSizer7.Add( self.statistics_text, 0, wx.ALL, 5 ) + bSizer11 = wx.BoxSizer( wx.HORIZONTAL ) + + self.export_excel_button = wx.Button( self.statistics_panel, wx.ID_ANY, u"导出Excel", wx.DefaultPosition, wx.DefaultSize, 0 ) + bSizer11.Add( self.export_excel_button, 0, 0, 5 ) + + + bSizer7.Add( bSizer11, 0, wx.ALL|wx.EXPAND, 5 ) + self.statistics_panel.SetSizer( bSizer7 ) self.statistics_panel.Layout() bSizer7.Fit( self.statistics_panel ) - self.console_notebook.AddPage( self.statistics_panel, u"统计", False ) + self.console_notebook.AddPage( self.statistics_panel, u"统计", True ) bSizer1.Add( self.console_notebook, 1, wx.EXPAND, 5 ) @@ -122,6 +130,7 @@ class RoomFrameBase ( wx.Frame ): self.config_button.Bind( wx.EVT_BUTTON, self._on_config_button_click ) self.stay_on_top_button.Bind( wx.EVT_TOGGLEBUTTON, self._on_stay_on_top_button_toggle ) self.collapse_console_button.Bind( wx.EVT_BUTTON, self._on_collapse_console_button_click ) + self.export_excel_button.Bind( wx.EVT_BUTTON, self._on_export_excel_button_click ) def __del__( self ): pass @@ -140,6 +149,9 @@ class RoomFrameBase ( wx.Frame ): def _on_collapse_console_button_click( self, event ): event.Skip() + def _on_export_excel_button_click( self, event ): + event.Skip() + ########################################################################### ## Class RoomConfigDialogBase diff --git a/plugins/native-ui/requirements.txt b/plugins/native-ui/requirements.txt index 43c69b2..901a0fe 100644 --- a/plugins/native-ui/requirements.txt +++ b/plugins/native-ui/requirements.txt @@ -1,3 +1,4 @@ PyPubSub==4.0.3 wxasync==0.49 wxPython==4.2.1 +XlsxWriter==3.2.0 diff --git a/plugins/native-ui/ui/room_frame.py b/plugins/native-ui/ui/room_frame.py index aee8c2c..985dde4 100644 --- a/plugins/native-ui/ui/room_frame.py +++ b/plugins/native-ui/ui/room_frame.py @@ -6,6 +6,7 @@ from typing import * import pubsub.pub as pub import wx +import xlsxwriter.exceptions import blcsdk import blcsdk.models as sdk_models @@ -107,6 +108,59 @@ class RoomFrame(designer.ui_base.RoomFrameBase): self.SetSize(window_size) self.Layout() + def _on_export_excel_button_click(self, event): + room = listener.get_room(self._room_key) + room_str = str(room.room_id) if room is not None else str(self._room_key) + cur_time = datetime.datetime.now() + time_str = cur_time.strftime('%Y%m%d_%H%M%S') + with wx.FileDialog( + self, + wildcard='Excel 文件 (*.xlsx)|*.xlsx', + defaultFile=f'room_{room_str}-{time_str}.xlsx', + style=wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT, + name='导出Excel', + ) as dialog: + if dialog.ShowModal() != wx.ID_OK: + return + path = dialog.GetPath() + + try: + with xlsxwriter.Workbook(path) as workbook: + self._write_list_ctrl_to_workbook(self.super_chat_list, workbook, '醒目留言') + self._write_list_ctrl_to_workbook(self.gift_list, workbook, '礼物&舰长') + self._write_list_ctrl_to_workbook(self.paid_user_list, workbook, '付费用户') + + if room is not None: + sheet = workbook.add_worksheet('统计') + row_texts = ['总弹幕数', '互动用户数', '总付费'] + sheet.write_column(0, 0, row_texts) + row_texts = [str(room.danmaku_num), str(len(room.interact_uids)), f'{room.total_paid_price:.1f}'] + sheet.write_column(0, 1, row_texts) + + sheet.set_column_pixels(0, 0, 120) + + except (OSError, xlsxwriter.exceptions.XlsxWriterException) as e: + logger.exception('Failed to save excel file:') + wx.MessageBox(str(e), '导出Excel失败', wx.OK | wx.ICON_ERROR | wx.CENTRE, self) + + def _write_list_ctrl_to_workbook(self, list_ctrl: wx.ListCtrl, workbook: xlsxwriter.Workbook, sheet_name): + sheet = workbook.add_worksheet(sheet_name) + for row, col_texts in enumerate(self._list_ctrl_to_col_texts(list_ctrl)): + sheet.write_row(row, 0, col_texts) + + col_num = list_ctrl.GetColumnCount() + for col in range(col_num): + sheet.set_column_pixels(col, col, list_ctrl.GetColumnWidth(col)) + # sheet.autofit() + + @staticmethod + def _list_ctrl_to_col_texts(list_ctrl: wx.ListCtrl): + col_num = list_ctrl.GetColumnCount() + row_num = list_ctrl.GetItemCount() + yield [list_ctrl.GetColumn(col).GetText() for col in range(col_num)] + for row in range(row_num): + yield [list_ctrl.GetItemText(row, col) for col in range(col_num)] + # # 模型事件 # @@ -115,14 +169,16 @@ class RoomFrame(designer.ui_base.RoomFrameBase): if room.room_key != self._room_key: return - super_chat = value[index] - col_texts = [ + col_texts = self._super_chat_to_col_texts(value[index]) + self._update_list_ctrl(self.super_chat_list, index, is_new, col_texts) + + def _super_chat_to_col_texts(self, super_chat: listener.SuperChatRecord): + return [ self._format_time(super_chat.time), super_chat.author_name, f'{super_chat.price:.1f}', super_chat.content, ] - self._update_list_ctrl(self.super_chat_list, index, is_new, col_texts) @staticmethod def _format_time(time: datetime.datetime): @@ -161,15 +217,17 @@ class RoomFrame(designer.ui_base.RoomFrameBase): if room.room_key != self._room_key: return - gift = value[index] - col_texts = [ + col_texts = self._gift_to_col_texts(value[index]) + self._update_list_ctrl(self.gift_list, index, is_new, col_texts) + + def _gift_to_col_texts(self, gift: listener.GiftRecord): + return [ self._format_time(gift.time), gift.author_name, gift.gift_name, str(gift.num), f'{gift.price:.1f}', ] - self._update_list_ctrl(self.gift_list, index, is_new, col_texts) def _on_uid_paid_user_dict_change( self, room: listener.Room, value: Dict[str, listener.PaidUserRecord], index, is_new # noqa @@ -182,12 +240,15 @@ class RoomFrame(designer.ui_base.RoomFrameBase): item_data = self._uid_to_paid_user_item_data[index] = self._next_paid_user_item_data self._next_paid_user_item_data += 1 - paid_user = value[index] - col_texts = [ + col_texts = self._paid_user_to_col_texts(value[index]) + self._update_list_ctrl(self.paid_user_list, item_data, is_new, col_texts) + + @staticmethod + def _paid_user_to_col_texts(paid_user: listener.PaidUserRecord): + return [ paid_user.name, f'{paid_user.price:.1f}', ] - self._update_list_ctrl(self.paid_user_list, item_data, is_new, col_texts) def _on_simple_statistics_change(self, room: listener.Room, value=None, index=None, is_new=None): # noqa if room.room_key != self._room_key: