基于Python實(shí)現(xiàn)一個(gè)目錄/文件遞歸檢索工具
核心功能
1. 目錄結(jié)構(gòu)檢索
遞歸掃描 :深度遍歷指定目錄及其所有子目錄
多種檢索模式 :
- 僅文件夾模式:只顯示目錄結(jié)構(gòu)
- 僅文件模式:只顯示文件列表
- 文件+文件夾模式:完整顯示目錄樹(shù)結(jié)構(gòu)(默認(rèn)模式)
2. 智能過(guò)濾系統(tǒng)
文件后綴過(guò)濾 :支持多后綴過(guò)濾(如:.txt; .py; .jpg)
屏蔽詞管理 :
- 支持通配符(如 .tmp; backup_ )
- 可創(chuàng)建和管理多個(gè)屏蔽配置
- 支持導(dǎo)入/導(dǎo)出配置
3. 結(jié)果輸出
- 樹(shù)形結(jié)構(gòu)展示 :直觀顯示目錄層級(jí)關(guān)系
- 可視化標(biāo)識(shí) :
- 統(tǒng)計(jì)信息 :自動(dòng)生成項(xiàng)目數(shù)量統(tǒng)計(jì)
- 結(jié)果導(dǎo)出 :一鍵導(dǎo)出為文本文件
特色功能
4. 用戶友好界面
- 直觀操作 :清晰的按鈕布局和分組
- 深色主題 :減輕視覺(jué)疲勞
- 實(shí)時(shí)狀態(tài)提示 :顯示當(dāng)前操作狀態(tài)
- 智能路徑建議 :自動(dòng)生成默認(rèn)輸出路徑
5. 配置管理
- 配置文件存儲(chǔ) :配置文件保存在程序同目錄
- 多配置支持 :可創(chuàng)建和管理多個(gè)屏蔽配置
- 配置導(dǎo)出 :支持將配置導(dǎo)出為JSON文件
6. 高效性能
- 快速掃描 :優(yōu)化遞歸算法提高效率
- 錯(cuò)誤處理 :自動(dòng)跳過(guò)無(wú)權(quán)限訪問(wèn)的目錄
- 排序功能 :文件和文件夾按名稱排序
使用場(chǎng)景
- 項(xiàng)目結(jié)構(gòu)分析 :快速查看項(xiàng)目目錄結(jié)構(gòu)
- 文件系統(tǒng)清理 :識(shí)別特定類(lèi)型的文件(如臨時(shí)文件)
- 文檔編制 :生成項(xiàng)目目錄樹(shù)文檔
- 資產(chǎn)盤(pán)點(diǎn) :統(tǒng)計(jì)特定類(lèi)型的文件數(shù)量
- 系統(tǒng)維護(hù) :查找分散的配置文件或日志文件
附源代碼
import os import sys import re import json import fnmatch from PyQt5.QtWidgets import ( QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, QLabel, QLineEdit, QPushButton, QRadioButton, QButtonGroup, QGroupBox, QFileDialog, QTextEdit, QComboBox, QMessageBox, QCheckBox, QListWidget, QListWidgetItem, QInputDialog ) from PyQt5.QtCore import Qt from PyQt5.QtGui import QFont, QPalette, QColor # 獲取腳本所在目錄 if getattr(sys, 'frozen', False): # 如果是打包后的可執(zhí)行文件 APP_DIR = os.path.dirname(sys.executable) else: # 如果是腳本文件 APP_DIR = os.path.dirname(os.path.abspath(__file__)) # 修改配置文件路徑為腳本所在目錄 CONFIG_FILE = os.path.join(APP_DIR, "config.json") class FileSearchApp(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle("目錄/文件遞歸檢索工具") self.setGeometry(300, 300, 800, 650) # 初始化變量 self.ignore_configs = [] self.current_ignore_config = {"name": "默認(rèn)配置", "patterns": []} self.load_config() # 創(chuàng)建UI self.init_ui() class FileSearchApp(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle("目錄/文件遞歸檢索工具 V1.0 by:Sunf10wer") self.setGeometry(300, 300, 800, 650) # 初始化變量 self.ignore_configs = [] self.current_ignore_config = {"name": "默認(rèn)配置", "patterns": []} self.load_config() # 創(chuàng)建UI self.init_ui() def init_ui(self): # 主布局 main_widget = QWidget() main_layout = QVBoxLayout() main_widget.setLayout(main_layout) self.setCentralWidget(main_widget) # 設(shè)置標(biāo)題樣式 title_label = QLabel("目錄/文件遞歸檢索工具") title_font = QFont("Arial", 16, QFont.Bold) title_label.setFont(title_font) title_label.setAlignment(Qt.AlignCenter) title_label.setStyleSheet("color: #FFFFFF; padding: 10px;") main_layout.addWidget(title_label) # 目錄選擇 dir_layout = QHBoxLayout() dir_label = QLabel("目標(biāo)目錄:") self.dir_entry = QLineEdit() self.dir_entry.setPlaceholderText("請(qǐng)選擇或輸入要檢索的目錄...") browse_button = QPushButton("瀏覽...") browse_button.clicked.connect(self.browse_directory) dir_layout.addWidget(dir_label) dir_layout.addWidget(self.dir_entry, 4) dir_layout.addWidget(browse_button, 1) main_layout.addLayout(dir_layout) # 輸出文件選擇 output_layout = QHBoxLayout() output_label = QLabel("輸出文件:") self.output_entry = QLineEdit() self.output_entry.setPlaceholderText("輸出文件名...") output_browse_button = QPushButton("瀏覽...") output_browse_button.clicked.connect(self.browse_output_file) output_layout.addWidget(output_label) output_layout.addWidget(self.output_entry, 4) output_layout.addWidget(output_browse_button, 1) main_layout.addLayout(output_layout) # 檢索類(lèi)型選擇 search_type_group = QGroupBox("檢索類(lèi)型") search_layout = QHBoxLayout() self.folder_radio = QRadioButton("僅文件夾") self.file_radio = QRadioButton("僅文件") self.both_radio = QRadioButton("文件和文件夾") self.both_radio.setChecked(True) self.search_type_group = QButtonGroup() self.search_type_group.addButton(self.folder_radio) self.search_type_group.addButton(self.file_radio) self.search_type_group.addButton(self.both_radio) # 文件后綴過(guò)濾 suffix_layout = QHBoxLayout() suffix_label = QLabel("文件后綴(用分號(hào)分隔):") self.suffix_entry = QLineEdit() self.suffix_entry.setPlaceholderText("例如: .txt; .py; .jpg") suffix_layout.addWidget(suffix_label) suffix_layout.addWidget(self.suffix_entry) search_layout.addWidget(self.folder_radio) search_layout.addWidget(self.file_radio) search_layout.addWidget(self.both_radio) search_layout.addStretch() search_type_group.setLayout(search_layout) main_layout.addWidget(search_type_group) main_layout.addLayout(suffix_layout) # 屏蔽詞管理 ignore_group = QGroupBox("屏蔽詞管理") ignore_layout = QVBoxLayout() # 屏蔽詞配置選擇 config_layout = QHBoxLayout() config_label = QLabel("當(dāng)前配置:") self.config_combo = QComboBox() self.config_combo.setMinimumWidth(150) self.config_combo.currentIndexChanged.connect(self.config_selected) new_config_btn = QPushButton("新建配置") new_config_btn.clicked.connect(self.create_new_config) config_layout.addWidget(config_label) config_layout.addWidget(self.config_combo, 1) config_layout.addWidget(new_config_btn) # 屏蔽詞列表 ignore_list_layout = QVBoxLayout() list_label = QLabel("屏蔽詞列表(支持通配符,如 *.tmp; backup_*)") self.ignore_list = QListWidget() self.ignore_list.setAlternatingRowColors(True) add_btn = QPushButton("添加屏蔽詞") add_btn.clicked.connect(self.add_ignore_pattern) remove_btn = QPushButton("移除選中") remove_btn.clicked.connect(self.remove_selected_pattern) list_btn_layout = QHBoxLayout() list_btn_layout.addWidget(add_btn) list_btn_layout.addWidget(remove_btn) ignore_list_layout.addWidget(list_label) ignore_list_layout.addWidget(self.ignore_list) ignore_list_layout.addLayout(list_btn_layout) ignore_layout.addLayout(config_layout) ignore_layout.addLayout(ignore_list_layout) ignore_group.setLayout(ignore_layout) main_layout.addWidget(ignore_group) # 操作按鈕 button_layout = QHBoxLayout() self.search_btn = QPushButton("開(kāi)始檢索") self.search_btn.setStyleSheet( "background-color: #3498db; color: white; font-weight: bold; padding: 8px;" ) self.search_btn.clicked.connect(self.start_search) export_btn = QPushButton("導(dǎo)出配置") export_btn.clicked.connect(self.export_config) button_layout.addStretch() button_layout.addWidget(self.search_btn) button_layout.addWidget(export_btn) button_layout.addStretch() main_layout.addLayout(button_layout) # 狀態(tài)欄 self.status_bar = self.statusBar() self.status_label = QLabel("就緒") self.status_bar.addWidget(self.status_label) # 更新UI self.update_config_combo() self.update_ignore_list() # 連接信號(hào) self.dir_entry.textChanged.connect(self.update_output_filename) def load_config(self): """從配置文件加載屏蔽詞配置""" if os.path.exists(CONFIG_FILE): try: with open(CONFIG_FILE, 'r', encoding='utf-8') as f: self.ignore_configs = json.load(f) # 確保至少有一個(gè)默認(rèn)配置 if not any(cfg['name'] == '默認(rèn)配置' for cfg in self.ignore_configs): self.ignore_configs.insert(0, {"name": "默認(rèn)配置", "patterns": []}) except: self.ignore_configs = [{"name": "默認(rèn)配置", "patterns": []}] else: self.ignore_configs = [{"name": "默認(rèn)配置", "patterns": []}] self.current_ignore_config = self.ignore_configs[0] def save_config(self): """保存屏蔽詞配置到文件""" try: with open(CONFIG_FILE, 'w', encoding='utf-8') as f: json.dump(self.ignore_configs, f, ensure_ascii=False, indent=2) return True except Exception as e: QMessageBox.critical(self, "保存錯(cuò)誤", f"保存配置時(shí)出錯(cuò): {str(e)}") return False def update_config_combo(self): """更新配置下拉框""" self.config_combo.clear() for config in self.ignore_configs: self.config_combo.addItem(config['name']) # 選擇當(dāng)前配置 current_index = next( (i for i, config in enumerate(self.ignore_configs) if config['name'] == self.current_ignore_config['name']), 0 ) self.config_combo.setCurrentIndex(current_index) def update_ignore_list(self): """更新屏蔽詞列表""" self.ignore_list.clear() for pattern in self.current_ignore_config['patterns']: self.ignore_list.addItem(pattern) def config_selected(self, index): """配置選擇改變事件""" if 0 <= index < len(self.ignore_configs): self.current_ignore_config = self.ignore_configs[index] self.update_ignore_list() def create_new_config(self): """創(chuàng)建新的屏蔽詞配置""" name, ok = QInputDialog.getText( self, "新建配置", "輸入配置名稱:", text=f"配置{len(self.ignore_configs)+1}" ) if ok and name: # 檢查名稱是否已存在 if any(cfg['name'] == name for cfg in self.ignore_configs): QMessageBox.warning(self, "名稱沖突", f"配置名 '{name}' 已存在!") return new_config = {"name": name, "patterns": []} self.ignore_configs.append(new_config) self.current_ignore_config = new_config self.update_config_combo() self.update_ignore_list() self.save_config() def add_ignore_pattern(self): """添加屏蔽詞""" pattern, ok = QInputDialog.getText( self, "添加屏蔽詞", "請(qǐng)輸入屏蔽詞(支持通配符):" ) if ok and pattern: if pattern not in self.current_ignore_config['patterns']: self.current_ignore_config['patterns'].append(pattern) self.update_ignore_list() self.save_config() def remove_selected_pattern(self): """移除選中的屏蔽詞""" selected_items = self.ignore_list.selectedItems() if not selected_items: return for item in selected_items: pattern = item.text() if pattern in self.current_ignore_config['patterns']: self.current_ignore_config['patterns'].remove(pattern) self.update_ignore_list() self.save_config() def browse_directory(self): """瀏覽目錄""" directory = QFileDialog.getExistingDirectory(self, "選擇目錄") if directory: self.dir_entry.setText(directory) self.update_output_filename() def browse_output_file(self): """瀏覽輸出文件""" # 獲取默認(rèn)輸出路徑 default_path = self.output_entry.text() or self.get_default_output_path() # 確保默認(rèn)路徑包含文件名 if os.path.isdir(default_path): default_path = os.path.join(default_path, self.get_default_filename()) # 打開(kāi)文件對(duì)話框 file_path, _ = QFileDialog.getSaveFileName( self, "保存結(jié)果", default_path, "文本文件 (*.txt)" ) if file_path: # 確保文件擴(kuò)展名正確 if not file_path.endswith('.txt'): file_path += '.txt' self.output_entry.setText(file_path) def get_default_output_path(self): """獲取默認(rèn)輸出目錄""" # 優(yōu)先使用目標(biāo)目錄所在位置 if self.dir_entry.text(): return os.path.dirname(self.dir_entry.text()) # 使用當(dāng)前工作目錄作為備選 return os.getcwd() def update_output_filename(self): """當(dāng)目錄改變時(shí)更新默認(rèn)輸出文件名""" # 僅當(dāng)輸出框?yàn)榭諘r(shí)更新 if not self.output_entry.text(): self.output_entry.setText(self.get_default_output_path()) def get_default_filename(self): """獲取默認(rèn)文件名""" directory = self.dir_entry.text() if directory: # 獲取目錄名作為文件名基礎(chǔ) base_name = os.path.basename(directory) or "root" return f"{base_name}_檢索結(jié)果.txt" return "檢索結(jié)果.txt" def export_config(self): """導(dǎo)出當(dāng)前配置""" if not self.current_ignore_config['patterns']: QMessageBox.information(self, "導(dǎo)出配置", "當(dāng)前配置沒(méi)有屏蔽詞!") return file_path, _ = QFileDialog.getSaveFileName( self, "導(dǎo)出配置", "", "JSON文件 (*.json)" ) if file_path: if not file_path.endswith('.json'): file_path += '.json' try: with open(file_path, 'w', encoding='utf-8') as f: json.dump(self.current_ignore_config, f, ensure_ascii=False, indent=2) QMessageBox.information(self, "導(dǎo)出成功", f"配置已導(dǎo)出到:\n{file_path}") except Exception as e: QMessageBox.critical(self, "導(dǎo)出錯(cuò)誤", f"導(dǎo)出配置時(shí)出錯(cuò): {str(e)}") def start_search(self): """開(kāi)始檢索""" # 獲取輸入?yún)?shù) target_dir = self.dir_entry.text().strip() if not target_dir or not os.path.isdir(target_dir): QMessageBox.warning(self, "目錄錯(cuò)誤", "請(qǐng)選擇有效的目標(biāo)目錄!") return # 獲取輸出文件路徑 output_file = self.output_entry.text().strip() if not output_file: # 如果沒(méi)有指定輸出文件,使用默認(rèn)文件名 output_file = os.path.join( self.get_default_output_path(), self.get_default_filename() ) self.output_entry.setText(output_file) if not output_file.endswith('.txt'): output_file += '.txt' # 確保輸出目錄存在 output_dir = os.path.dirname(output_file) if output_dir and not os.path.exists(output_dir): try: os.makedirs(output_dir) except Exception as e: QMessageBox.critical(self, "目錄錯(cuò)誤", f"無(wú)法創(chuàng)建輸出目錄: {str(e)}") return # 檢查檢索類(lèi)型 search_folders = self.folder_radio.isChecked() search_files = self.file_radio.isChecked() search_both = self.both_radio.isChecked() file_extensions = [] if search_files or search_both: ext_str = self.suffix_entry.text().strip() if ext_str: file_extensions = [ext.strip().lower() for ext in ext_str.split(';') if ext.strip()] # 獲取屏蔽詞 ignore_patterns = self.current_ignore_config['patterns'] # 執(zhí)行檢索 self.status_label.setText("正在檢索...") QApplication.processEvents() # 更新UI try: # 獲取層級(jí)結(jié)構(gòu)的檢索結(jié)果 results = [] self.recursive_traverse( target_dir, target_dir, results, 0, search_folders, search_files, search_both, file_extensions, ignore_patterns ) # 寫(xiě)入文件 with open(output_file, 'w', encoding='utf-8') as f: # 寫(xiě)入頭部信息 f.write(f"檢索目錄: {target_dir}\n") if search_folders: f.write(f"檢索類(lèi)型: 僅文件夾\n") elif search_files: f.write(f"檢索類(lèi)型: 僅文件\n") else: f.write(f"檢索類(lèi)型: 文件和文件夾\n") if (search_files or search_both) and file_extensions: f.write(f"文件后綴: {', '.join(file_extensions)}\n") if ignore_patterns: f.write(f"屏蔽配置: {self.current_ignore_config['name']}\n") f.write(f"屏蔽詞: {', '.join(ignore_patterns)}\n") f.write("\n" + "=" * 70 + "\n\n") # 寫(xiě)入層級(jí)結(jié)構(gòu) total_items = 0 total_folders = 0 total_files = 0 for item in results: # 根據(jù)層級(jí)深度添加縮進(jìn) indent = "│ " * (item['depth'] - 1) prefix = "├── " if item['depth'] > 0 else "" # 添加文件夾/文件標(biāo)識(shí) if item['type'] == 'folder': line = f"{indent}{prefix}?? {item['name']}/" total_folders += 1 else: line = f"{indent}{prefix}?? {item['name']}" total_files += 1 f.write(line + "\n") total_items += 1 # 添加統(tǒng)計(jì)信息 f.write("\n" + "=" * 70 + "\n\n") f.write(f"統(tǒng)計(jì)信息:\n") f.write(f"總項(xiàng)目數(shù): {total_items}\n") f.write(f"文件夾數(shù): {total_folders}\n") f.write(f"文件數(shù): {total_files}\n") self.status_label.setText(f"檢索完成!找到 {total_items} 個(gè)項(xiàng)目,結(jié)果已保存到: {output_file}") QMessageBox.information(self, "完成", f"檢索完成!\n" f"總項(xiàng)目數(shù): {total_items}\n" f"文件夾數(shù): {total_folders}\n" f"文件數(shù): {total_files}\n" f"結(jié)果已保存到:\n{output_file}" ) except Exception as e: self.status_label.setText("檢索出錯(cuò)") QMessageBox.critical(self, "錯(cuò)誤", f"檢索過(guò)程中出錯(cuò): {str(e)}") def recursive_traverse(self, root_dir, current_dir, results, depth, search_folders, search_files, search_both, file_extensions, ignore_patterns): """遞歸遍歷目錄,保持實(shí)際目錄結(jié)構(gòu)""" try: # 獲取當(dāng)前目錄下的條目 entries = os.listdir(current_dir) except Exception as e: # 跳過(guò)無(wú)權(quán)訪問(wèn)的目錄 return # 排序條目 entries.sort(key=lambda s: s.lower()) # 獲取當(dāng)前目錄相對(duì)于根目錄的相對(duì)路徑 rel_dir = os.path.relpath(current_dir, root_dir) # 如果是根目錄,添加根目錄項(xiàng) if current_dir == root_dir: results.append({ 'name': os.path.basename(root_dir) or os.path.splitdrive(root_dir)[0], 'path': root_dir, 'depth': depth, 'type': 'folder' }) # 處理文件夾 folders = [e for e in entries if os.path.isdir(os.path.join(current_dir, e))] for folder in folders: folder_path = os.path.join(current_dir, folder) rel_path = os.path.relpath(folder_path, root_dir) # 檢查是否在屏蔽列表中 if self.is_ignored(rel_path, ignore_patterns): continue # 添加到結(jié)果(如果需要檢索文件夾) if search_folders or search_both: results.append({ 'name': folder, 'path': folder_path, 'depth': depth + 1, 'type': 'folder' }) # 遞歸處理子目錄 self.recursive_traverse( root_dir, folder_path, results, depth + 1, search_folders, search_files, search_both, file_extensions, ignore_patterns ) # 處理文件 files = [e for e in entries if os.path.isfile(os.path.join(current_dir, e))] for file in files: file_path = os.path.join(current_dir, file) rel_path = os.path.relpath(file_path, root_dir) # 檢查是否在屏蔽列表中 if self.is_ignored(rel_path, ignore_patterns): continue # 檢查文件后綴 if (search_files or search_both) and file_extensions: ext = os.path.splitext(file)[1].lower() if ext not in file_extensions: continue # 添加到結(jié)果 if search_files or search_both: results.append({ 'name': file, 'path': file_path, 'depth': depth + 1, 'type': 'file' }) def is_ignored(self, path, patterns): """檢查路徑是否與任何屏蔽模式匹配""" for pattern in patterns: if fnmatch.fnmatch(path, pattern): return True if pattern in path: return True return False if __name__ == "__main__": app = QApplication([]) app.setStyle("Fusion") # 設(shè)置應(yīng)用樣式 palette = QPalette() palette.setColor(QPalette.Window, QColor(53, 53, 53)) palette.setColor(QPalette.WindowText, Qt.white) palette.setColor(QPalette.Base, QColor(35, 35, 35)) palette.setColor(QPalette.AlternateBase, QColor(53, 53, 53)) palette.setColor(QPalette.ToolTipBase, Qt.white) palette.setColor(QPalette.ToolTipText, Qt.white) palette.setColor(QPalette.Text, Qt.white) palette.setColor(QPalette.Button, QColor(53, 53, 53)) palette.setColor(QPalette.ButtonText, Qt.white) palette.setColor(QPalette.BrightText, Qt.red) palette.setColor(QPalette.Highlight, QColor(142, 45, 197).lighter()) palette.setColor(QPalette.HighlightedText, Qt.black) app.setPalette(palette) window = FileSearchApp() window.show() app.exec_()
文件遞歸檢索工具依賴庫(kù)
PyQt5==5.15.9
pip install PyQt5
pyinstaller --onefile -w so.py
到此這篇關(guān)于基于Python實(shí)現(xiàn)一個(gè)目錄/文件遞歸檢索工具的文章就介紹到這了,更多相關(guān)Python目錄/文件遞歸檢索內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Python實(shí)現(xiàn)身份證號(hào)碼解析
本文給大家匯總介紹下使用Python實(shí)現(xiàn)身份證號(hào)碼驗(yàn)證解析的幾個(gè)方法,有需要的小伙伴可以參考下。2015-09-09詳解python?sklearn中的數(shù)據(jù)預(yù)處理方法
本篇文章主要講解Python的sklearn庫(kù)中常用的數(shù)據(jù)預(yù)處理方法,主要介紹工具中的內(nèi)容,即該庫(kù)中的相關(guān)方法包含的常用接口和基本使用,希望對(duì)大家有所幫助2023-08-08Python requests timeout的設(shè)置
這篇文章主要介紹了Python requests timeout的設(shè)置,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-04-04python client使用http post 到server端的代碼
python client使用 http post 到server端的代碼,供大家學(xué)習(xí)參考2013-02-02Python OpenCV中的resize()函數(shù)的使用
這篇文章主要介紹了Python OpenCV中的resize()函數(shù)的使用,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-06-06一文解決django 2.2與mysql兼容性問(wèn)題
Django是一個(gè)開(kāi)放源代碼的Web應(yīng)用框架,由Python寫(xiě)成。采用了MTV的框架模式,即模型M,視圖V和模版T。這篇文章主要介紹了解決django 2.2與mysql兼容性問(wèn)題,需要的朋友可以參考下2020-07-07Django JSonResponse對(duì)象的實(shí)現(xiàn)
本文主要介紹了Django JSonResponse對(duì)象的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2023-03-03