使用Python編寫(xiě)截圖輕量化工具
這是用Python做到截圖工具,不過(guò)由于使用了ctypes調(diào)用了Windows的API, 同時(shí)訪問(wèn)了Windows中"C:/Windows/Cursors/"中的.cur光標(biāo)樣式文件, 這個(gè)工具只適用于Windows環(huán)境;
如果要提升其跨平臺(tái)性的話,需要考慮替換ctypes的一些專屬于Windows的API以及設(shè)置不同的.cur訪問(wèn)方式;
該模塊只使用了PIL這一個(gè)第三方庫(kù),和別的使用pygame\pyautogui等模塊不同,該工具著重強(qiáng)調(diào)輕量化,無(wú)關(guān)或沒(méi)必要使用的庫(kù)盡可能不使用。
目前模塊尚未完成,只包含了基本的截圖時(shí)調(diào)整大小,保存,翻頁(yè)查看不同截圖這些簡(jiǎn)單功能;后續(xù)會(huì)繼續(xù)完善,但當(dāng)前模塊的可用性已足夠,且代碼經(jīng)過(guò)重構(gòu),具備一定的可讀性以及可拓展性。
完整代碼如下,單文件,自行安裝PIL模塊,在Windows環(huán)境上運(yùn)行。
import ctypes import tkinter as tk from tkinter import messagebox, filedialog from PIL import ImageGrab, ImageTk ScaleFactor = ctypes.windll.shcore.GetScaleFactorForDevice(0) ctypes.windll.shcore.SetProcessDpiAwareness(1) class FlatButton(tk.Label): def __init__( self, parent, command, enter_fg="#000000", click_color="#25C253", *args, **kwargs ): super().__init__(parent, *args, **kwargs) self.__fg = fg = kwargs.get("fg", "#474747") self.__enter_fg = enter_fg self.__click_fg = click_color self.command = command self.config(cursor="hand2") self.bind("<Enter>", lambda _: self.config(fg=enter_fg)) self.bind("<Leave>", lambda _: self.config(fg=fg)) self.bind("<Button-1>", lambda _: self.config(fg=click_color)) self.bind("<ButtonRelease-1>", self.__command) if fg == enter_fg: raise ValueError("enter_fg must be different from fg") def __command(self, event): try: if self.cget("fg") in (self.__enter_fg, self.__click_fg): self.command(event) self.config(fg=self.__fg) except tk.TclError: # Button have been destroy. pass class AdjustableRect(object): """ The judgement seq is so important that you must care about: (right, bottom), (left, top), (right, top), (left, bottom), (center_x, top), (center_x, bottom), (left, center_y, ), (right, center_y) """ ANCHOR_SIZE = 3 ANCHOR_HOVER_DISTANCE = 20 CURSOR_FILES_NAME = ["aero_nwse_l.cur", "aero_nesw_l.cur", "aero_ns_l.cur", "aero_ew_l.cur"] CURSOR_FILES = [f"@C:/Windows/Cursors/{cursor_file}" for cursor_file in CURSOR_FILES_NAME] CURSORS = [ CURSOR_FILES[0], CURSOR_FILES[0], CURSOR_FILES[1], CURSOR_FILES[1], CURSOR_FILES[2], CURSOR_FILES[2], CURSOR_FILES[3], CURSOR_FILES[3], "fleur", "arrow" ] def __init__(self, parent, screenshot): self.parent: tk.Canvas = parent self.screenshot: ScreenshotUtils = screenshot self.__rect: int = 0 self.__anchors: list[int] = [] self.anchor_id: int = 0 def rect_coords(self) -> tuple[int, int, int, int]: try: return self.parent.coords(self.__rect) except tk.TclError: return [ min(self.screenshot.start_x, self.screenshot.end_x), min(self.screenshot.start_y, self.screenshot.end_y), max(self.screenshot.start_x, self.screenshot.end_x), max(self.screenshot.start_y, self.screenshot.end_y) ] def anchor_coords(self) -> tuple[int, int, int, int]: left, top, right, bottom = self.rect_coords() horizontal_middle = (left + right) // 2 vertical_middle = (top + bottom) // 2 return ( (left, top), (horizontal_middle, top), (right, top), (right, vertical_middle), (right, bottom), (horizontal_middle, bottom), (left, bottom), (left, vertical_middle) ) def get_anchor(self, event) -> int: cls = self.__class__ left, top, right, bottom = self.rect_coords() center_x, center_y = (left + right) // 2, (top + bottom) // 2 def near(actual, target): return abs(actual - target) < cls.ANCHOR_HOVER_DISTANCE # 務(wù)必注意這個(gè)判斷順序,這與后面rect_adjust密切相關(guān) judgement_pos = ( (right, bottom), (left, top), (right, top), (left, bottom), (center_x, top), (center_x, bottom), (left, center_y, ), (right, center_y) ) for index, pos in enumerate(judgement_pos): if near(event.x, pos[0]) and near(event.y, pos[1]): return index if left < event.x < right and top < event.y < bottom: return 8 return -1 def create_anchors(self): cls = self.__class__ for coord in self.anchor_coords(): anchor = self.parent.create_rectangle( coord[0]-cls.ANCHOR_SIZE, coord[1]-cls.ANCHOR_SIZE, coord[0]+cls.ANCHOR_SIZE, coord[1]+cls.ANCHOR_SIZE, fill="#1AAE1A", outline="#1AAE1A" ) self.__anchors.append(anchor) def move_anchors(self): cls = self.__class__ for anchor, coord in zip(self.__anchors, self.anchor_coords()): self.parent.coords( anchor, coord[0]-cls.ANCHOR_SIZE, coord[1]-2, coord[0]+cls.ANCHOR_SIZE, coord[1]+cls.ANCHOR_SIZE ) def on_press(self, event): self.screenshot.start_x = event.x self.screenshot.start_y = event.y self.__rect= self.parent.create_rectangle(self.screenshot.start_x, self.screenshot.start_y, self.screenshot.start_x, self.screenshot.start_y, outline='#1AAE1A', width=2) self.create_anchors() def on_release(self, _): self.screenshot.start_x, self.screenshot.start_y,\ self.screenshot.end_x, self.screenshot.end_y = self.rect_coords() def on_hover(self, event): self.anchor_id = self.get_anchor(event) cursor = self.CURSORS[self.anchor_id] self.parent.config(cursor=cursor) def move_rect(self, event): offset_x = event.x - self.screenshot.move_start_x offset_y = event.y - self.screenshot.move_start_y self.screenshot.start_x += offset_x self.screenshot.start_y += offset_y self.screenshot.end_x += offset_x self.screenshot.end_y += offset_y self.screenshot.move_start_x = event.x self.screenshot.move_start_y = event.y self.parent.coords(self.__rect, self.screenshot.start_x, self.screenshot.start_y, self.screenshot.end_x, self.screenshot.end_y) self.move_anchors() def rect_adjust(self, event): if self.anchor_id == 8: return self.move_rect(event) if self.anchor_id == 0: self.screenshot.end_x, self.screenshot.end_y = event.x, event.y elif self.anchor_id == 1: self.screenshot.start_x, self.screenshot.start_y = event.x, event.y elif self.anchor_id == 2: self.screenshot.end_x, self.screenshot.start_y = event.x, event.y elif self.anchor_id == 3: self.screenshot.start_x, self.screenshot.end_y = event.x, event.y elif self.anchor_id == 4: self.screenshot.start_y = event.y elif self.anchor_id == 5: self.screenshot.end_y = event.y elif self.anchor_id == 6: self.screenshot.start_x = event.x elif self.anchor_id == 7: self.screenshot.end_x = event.x else: return self.parent.coords(self.__rect, self.screenshot.start_x, self.screenshot.start_y, self.screenshot.end_x, self.screenshot.end_y) self.move_anchors() class ScreenshotUtils(object): """ 截圖的關(guān)鍵是坐標(biāo);這個(gè)類管理著圖片的引用和截圖坐標(biāo); """ def TkS(value) -> int: return int(ScaleFactor/100*value) ZOOM: int = 4 ZOOM_WIDTH: float = TkS(28.75) ZOOM_SCREEN_SIZE: int = int(ZOOM_WIDTH*ZOOM) MAGNIFIER_OFFSET: int = 36 AJUST_BAR_WIDTH: int = TkS(100) # 截圖相關(guān)變量 def __init__(self): self.start_x = self.start_y = self.end_x = self.end_y = 0 self.move_start_x = self.move_start_y = self.move_end_x = self.move_end_y = 0 self.current_image = None self.pixel_reader = None self.final_images = list() # 這種是只移動(dòng)但不改變大小和內(nèi)容的控件,只需移動(dòng)無(wú)需重繪 self.screenshot_move_widget = list() # 這種是移動(dòng)和改變大小的控件,需要實(shí)時(shí)重繪 self.screenshot_redraw_widget = list() @staticmethod def TkS(value) -> int: return int(ScaleFactor/100*value) @classmethod def move_widget_coords(cls, x, y) -> list[tuple[int, int, int, int]]: # 按照主框架,水平線,垂直線的順序返回坐標(biāo) magnifier_x = x+cls.MAGNIFIER_OFFSET magnifier_y = y+cls.MAGNIFIER_OFFSET main_frame_coord = ( magnifier_x, magnifier_y, magnifier_x+cls.ZOOM_SCREEN_SIZE, magnifier_y+cls.ZOOM_SCREEN_SIZE ) horrontal_line_coord = ( magnifier_x, magnifier_y+cls.ZOOM_SCREEN_SIZE // 2, magnifier_x+cls.ZOOM_SCREEN_SIZE, magnifier_y+cls.ZOOM_SCREEN_SIZE // 2 ) vertical_line_coord = ( magnifier_x+cls.ZOOM_SCREEN_SIZE // 2, magnifier_y, magnifier_x+cls.ZOOM_SCREEN_SIZE // 2, magnifier_y+cls.ZOOM_SCREEN_SIZE ) coords = [main_frame_coord, horrontal_line_coord, vertical_line_coord] return coords def redraw_widget_coords(self, x, y) -> list[tuple]: # 按照"長(zhǎng) × 寬"、"放大鏡圖像"、"POS標(biāo)簽"、"RGB標(biāo)簽"的順序返回坐標(biāo) offset = self.__class__.MAGNIFIER_OFFSET width_height_info = (min(self.start_x, self.end_x), min(self.start_y, self.end_y) - 40) magnifier_coord = (x + offset, y + offset) pos_info = (x + offset, y + offset+self.__class__.ZOOM_SCREEN_SIZE + 10) rgb_info = (x + offset, y + offset+self.__class__.ZOOM_SCREEN_SIZE + 47) coords = [width_height_info, magnifier_coord, pos_info, rgb_info] return coords class MainUI(tk.Tk): def __init__(self): super().__init__() self.screenshot = ScreenshotUtils() self.set_window() self.menu_bar: tk.Frame = self.set_menubar() self.cut_btn: tk.Label = self.set_cut_btn() self.save_btn: tk.Label = self.set_save_btn() self.turn_left_btn: tk.Label = self.set_turn_left_btn() self.turn_right_btn: tk.Label = self.set_turn_right_btn() self.show_image_canvas: tk.Canvas = self.set_show_image_canvas() self.adjust_rect: AdjustableRect = None self.pos_label: tk.Label = None self.rgb_label: tk.Label = None self.adjust_bar: tk.Frame = None def set_window(self): self.title("截圖工具") self.geometry(f"{self.screenshot.TkS(240)}x{self.screenshot.TkS(30)}") def set_menubar(self) -> tk.Frame: menubar = tk.Frame(self, bg="#FFFFFF", height=30) menubar.pack(fill=tk.X) return menubar def set_cut_btn(self) -> tk.Label: btn_cut = FlatButton( self.menu_bar, self.start_capture, text="?", bg="#FFFFFF", font=("Segoe UI Emoji", 18), ) btn_cut.pack(side=tk.LEFT, padx=5) return btn_cut def set_save_btn(self) -> tk.Label: btn_save = FlatButton( self.menu_bar, self.save_image, text="??", bg="#FFFFFF", font=("Segoe UI Emoji", 18), ) btn_save.pack(side=tk.LEFT, padx=5) return btn_save def set_turn_left_btn(self) -> tk.Label: turn_left_btn = FlatButton( self.menu_bar, self.turn_page, text="\u25C0", bg="#FFFFFF", font=("Segoe UI Emoji", 18), ) turn_left_btn.pack(side=tk.LEFT, padx=5) return turn_left_btn def set_turn_right_btn(self) -> tk.Label: turn_page_btn = FlatButton( self.menu_bar, self.turn_page, text="\u25B6", bg="#FFFFFF", font=("Segoe UI Emoji", 18), ) turn_page_btn.pack(side=tk.LEFT, padx=5) return turn_page_btn def set_cancel_btn(self, parent) -> tk.Label: cancel_btn = FlatButton( parent, self.clear_capture_info, text="×", bg="#FFFFFF", enter_fg="#DB1A21",fg="#CC181F", font=("微軟雅黑", 18) ) cancel_btn.pack(side=tk.RIGHT, padx=5) return cancel_btn def set_confirm_btn(self, parent) -> tk.Label: confirm_btn = FlatButton( parent, self.confirm_capture, fg="#23B34C", text="√", enter_fg="#27C956", bg="#FFFFFF", font=("微軟雅黑", 18) ) return confirm_btn def set_show_image_canvas(self) -> tk.Canvas: canvas = tk.Canvas(self, bg="white") return canvas def set_adjust_bar(self) -> tk.Frame: self.adjust_bar = tk.Frame(self.full_screenshot_canvas, bg="#FFFFFF", height=50) cancel_btn = self.set_cancel_btn(self.adjust_bar) confirm_btn = self.set_confirm_btn(self.adjust_bar) cancel_btn.pack(side=tk.RIGHT, padx=5) confirm_btn.pack(side=tk.RIGHT, padx=10) def set_magnifier_frame(self, event) -> None: initial_coord = (0, 0, 0, 0) main_frame_id = self.full_screenshot_canvas.create_rectangle(*initial_coord, outline='#1AAE1A', width=1) horrontal_line = self.full_screenshot_canvas.create_line(*initial_coord, fill="#1AAE1A", width=2) vertical_line = self.full_screenshot_canvas.create_line(*initial_coord, fill="#1AAE1A", width=2) self.screenshot.screenshot_move_widget = [main_frame_id, horrontal_line, vertical_line] event.x = event.x + event.widget.winfo_rootx() event.y = event.y + event.widget.winfo_rooty() self.update_magnifier(event) def set_full_screenshot_canvas(self, parent) -> tk.Canvas: img = ImageGrab.grab() self.screenshot.current_image = img self.screenshot.pixel_reader = img.convert("RGB") photo = ImageTk.PhotoImage(img) full_screenshot_canvas = tk.Canvas(parent, bg="white") full_screenshot_canvas.create_image(0, 0, anchor=tk.NW, image=photo) full_screenshot_canvas.image = photo full_screenshot_canvas.pack(fill=tk.BOTH, expand=True) return full_screenshot_canvas def set_pos_rgb_label(self, parent) -> tk.Label: label = tk.Label(parent, bg="#000000", font=("微軟雅黑", 8), fg="#ffffff") return label class ScreenshotTool(MainUI): def __init__(self): super().__init__() self.page_index = 0 def start_capture(self, event): self.attributes('-alpha', 0) self.update() self.capture_win = tk.Toplevel() self.capture_win.geometry(f"{self.winfo_screenwidth()}x{self.winfo_screenheight()}+0+0") self.capture_win.overrideredirect(True) self.full_screenshot_canvas = self.set_full_screenshot_canvas(self.capture_win) self.pos_label = self.set_pos_rgb_label(self.full_screenshot_canvas) self.rgb_label = self.set_pos_rgb_label(self.full_screenshot_canvas) self.adjust_rect = AdjustableRect(self.full_screenshot_canvas, self.screenshot) self.set_magnifier_frame(event) self.set_adjust_bar() self.full_screenshot_canvas.bind("<Button-1>", self.on_press) self.full_screenshot_canvas.bind("<Motion>", self.update_magnifier) self.full_screenshot_canvas.bind("<ButtonRelease-1>", self.on_release) def on_press(self, event): self.adjust_rect.on_press(event) self.full_screenshot_canvas.unbind("<Motion>") self.full_screenshot_canvas.bind("<Motion>", self.on_drag) def on_drag(self, event): self.adjust_rect.rect_adjust(event) self.update_magnifier(event) def on_release(self, event, resize=True): self.unbind_all() self.adjust_rect.on_release(event) self.full_screenshot_canvas.config(cursor=self.adjust_rect.CURSOR_FILES[0]) self.full_screenshot_canvas.bind("<Button-1>", self.start_move) self.full_screenshot_canvas.bind("<Motion>", self.adjust_rect.on_hover) self.adjust_bar.place(x=self.screenshot.end_x - 300, y=self.screenshot.end_y + 10, width=300) if resize: self.screenshot.end_x, self.screenshot.end_y = event.x, event.y def unbind_all(self): events = ("<Button-1>", "<Motion>", "<ButtonRelease-1>") for event in events: self.full_screenshot_canvas.unbind(event) def update_magnifier(self, event): x, y = event.x, event.y size = ScreenshotUtils.ZOOM_WIDTH img = self.screenshot.current_image.crop((x - size//2, y - size//2, x + size//2, y + size//2)) img = img.resize((ScreenshotUtils.ZOOM_SCREEN_SIZE, ScreenshotUtils.ZOOM_SCREEN_SIZE)) photo = ImageTk.PhotoImage(img) self.full_screenshot_canvas.image2 = photo w, h = abs(self.screenshot.end_x - self.screenshot.start_x), abs(self.screenshot.end_y - self.screenshot.start_y) for redraw_widget in self.screenshot.screenshot_redraw_widget: self.full_screenshot_canvas.delete(redraw_widget) self.screenshot.screenshot_redraw_widget.clear() redraw_widget_coords = self.screenshot.redraw_widget_coords(x, y) width_height_info, magnifier_coord, pos_info, rgb_info = redraw_widget_coords wh_info_widget = self.full_screenshot_canvas.create_text(*width_height_info, anchor=tk.NW, fill="white", text=f"{w} × {h}") zoom_img = self.full_screenshot_canvas.create_image(*magnifier_coord, anchor=tk.NW, image=photo) self.pos_label.config(text=f"POS: ({x}, {y})") self.rgb_label.config(text=f"RGB: {self.screenshot.pixel_reader.getpixel((x, y))}") self.pos_label.place(x=pos_info[0], y=pos_info[1]) self.rgb_label.place(x=rgb_info[0], y=rgb_info[1]) self.screenshot.screenshot_redraw_widget = [wh_info_widget, zoom_img] self.update_magnifier_frame(x, y) def update_magnifier_frame(self, x: int, y: int) -> None: coords = self.screenshot.move_widget_coords(x, y) for widget, coord in zip(self.screenshot.screenshot_move_widget, coords): self.full_screenshot_canvas.coords(widget, *coord) self.full_screenshot_canvas.tag_raise(widget) def start_move(self, event): self.screenshot.move_start_x = event.x self.screenshot.move_start_y = event.y self.adjust_bar.place_forget() self.pos_label.place_forget() self.rgb_label.place_forget() for widget in self.screenshot.screenshot_redraw_widget: self.full_screenshot_canvas.delete(widget) for widget in self.screenshot.screenshot_move_widget: self.full_screenshot_canvas.tag_lower(widget) self.full_screenshot_canvas.bind("<B1-Motion>", self.adjust_rect.rect_adjust) self.full_screenshot_canvas.bind("<ButtonRelease-1>", lambda e: self.on_release(e, False)) def clear_capture_info(self, _): self.capture_win.destroy() self.full_screenshot_canvas.destroy() self.attributes('-alpha', 1) self.screenshot.screenshot_move_widget.clear() def confirm_capture(self, event): self.clear_capture_info(event) x1, y1, x2, y2 = self.adjust_rect.rect_coords() image = self.screenshot.current_image.crop((x1, y1, x2, y2)) result = self.show_image(image) self.screenshot.final_images.append(result) self.page_index = len(self.screenshot.final_images) - 1 self.attributes('-topmost', 1) def show_image(self, image): self.geometry(f"{max(image.width, ScreenshotUtils.TkS(240))}x{image.height+self.menu_bar.winfo_height()}") photo = ImageTk.PhotoImage(image) self.show_image_canvas.config(width=image.width, height=image.height) self.show_image_canvas.delete("all") self.show_image_canvas.create_image(0, 0, anchor=tk.NW, image=photo) self.show_image_canvas.image = photo self.show_image_canvas.pack() return image def save_image(self, _): try: image = self.screenshot.final_images[self.page_index] filename = filedialog.asksaveasfilename( defaultextension=".png", filetypes=[("PNG files", "*.png"), ("JPEG files", "*.jpg")], initialfile=f"{image.width}x{image.height}.png" ) if not filename: return image.save(filename) except IndexError: messagebox.showerror("保存失敗", "未檢測(cè)到截取圖像") def turn_page(self, event): if len(self.screenshot.final_images) == 0: return messagebox.showinfo("提示", "暫無(wú)圖片可切換!") if event.widget == self.turn_left_btn: if self.page_index == 0: return messagebox.showinfo("提示", "已經(jīng)是第一張圖片!") self.page_index -= 1 else: if self.page_index == len(self.screenshot.final_images) - 1: return messagebox.showinfo("提示", "已經(jīng)是最后一張圖片!") self.page_index += 1 self.show_image(self.screenshot.final_images[self.page_index]) if __name__ == "__main__": app = ScreenshotTool() app.mainloop()
到此這篇關(guān)于使用Python編寫(xiě)截圖輕量化工具的文章就介紹到這了,更多相關(guān)Python截圖內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
python下paramiko模塊實(shí)現(xiàn)ssh連接登錄Linux服務(wù)器
這篇文章主要介紹了python下paramiko模塊實(shí)現(xiàn)ssh連接登錄Linux服務(wù)器的方法,實(shí)例分析了paramiko模塊實(shí)現(xiàn)ssh連接的具體用法,需要的朋友可以參考下2015-06-06python selenium 無(wú)界面瀏覽器的實(shí)現(xiàn)
有時(shí)我們不想讓瀏覽器窗口跳出來(lái),而是想在后臺(tái)進(jìn)行操作,這就需要用到無(wú)界面瀏覽器,本文主要介紹了python selenium 無(wú)界面瀏覽器的實(shí)現(xiàn),具有一定的參考價(jià)值,感興趣的可以了解一下2023-10-10python 線程的暫停, 恢復(fù), 退出詳解及實(shí)例
這篇文章主要介紹了python 線程的暫停, 恢復(fù), 退出詳解及實(shí)例的相關(guān)資料,需要的朋友可以參考下2016-12-12Python實(shí)現(xiàn)字符串的逆序 C++字符串逆序算法
這篇文章主要為大家詳細(xì)介紹了Python實(shí)現(xiàn)字符串的逆序,C++將字符串逆序輸出,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-04-04基于python實(shí)現(xiàn)的百度新歌榜、熱歌榜下載器(附代碼)
這篇文章主要介紹了基于python實(shí)現(xiàn)的百度新歌榜、熱歌榜下載器(附代碼),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-08-08Python列表轉(zhuǎn)換為Excel表格第一列的方法詳解
在數(shù)據(jù)處理和分析的過(guò)程中,我們經(jīng)常需要將Python中的數(shù)據(jù)結(jié)構(gòu)(如列表)導(dǎo)出到Excel表格中,本文為大家整理了Python列表轉(zhuǎn)換為Excel表格第一列的幾種方法,希望對(duì)大家有所幫助2024-11-11Python利用splinter實(shí)現(xiàn)瀏覽器自動(dòng)化操作方法
今天小編就為大家分享一篇Python利用splinter實(shí)現(xiàn)瀏覽器自動(dòng)化操作方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2018-05-05python出現(xiàn)更新庫(kù)失敗A?new?release?of?pip?is?available:?23.0.
學(xué)習(xí)了Python我們知道它自帶了很多的庫(kù),同時(shí)我們還需要對(duì)某個(gè)庫(kù)進(jìn)行升級(jí),這篇文章主要給大家介紹了關(guān)于python出現(xiàn)更新庫(kù)失敗A?new?release?of?pip?is?available:?23.0.1?->?23.3的解決辦法,需要的朋友可以參考下2024-03-03