【办公工具】用于文本格式处理或文件格式处理的一些Python脚本

任何关于代码的改进不胜感激。

脚本处理后的结果记得核对一下。

写一个Python脚本删除一个.py文件的所有注释

点击查看代码
// 也可以尝试一下 https://python-minifier.com/

import re


def remove_comments(file_path):
    with open(file_path, 'r') as file:
        content = file.read()

    # First, find and store string assignments
    protected_strings = {}
    counter = 0

    def protect_string_assignments(match):
        nonlocal counter
        var_name, string_content = match.groups()
        key = f'PROTECTED_STRING_{counter}'
        protected_strings[key] = match.group(0)
        counter += 1
        return key

    # Protect strings that are part of assignments
    protected_content = re.sub(
        r'([a-zA-Z_][a-zA-Z0-9_]*\s*=\s*)("""[\s\S]*?"""|\'\'\'[\s\S]*?\'\'\')',
        protect_string_assignments,
        content
    )

    # Remove docstring comments (triple-quoted strings not part of assignments)
    cleaned_content = re.sub(
        r'"""[\s\S]*?"""|\'\'\'[\s\S]*?\'\'\'',
        '',
        protected_content
    )

    # Remove single-line comments and empty lines
    lines = []
    for line in cleaned_content.split('\n'):
        # Remove inline comments
        line = re.sub(r'#.*$', '', line)
        if line.strip():
            lines.append(line)

    # Restore protected strings
    final_content = '\n'.join(lines)
    for key, value in protected_strings.items():
        final_content = final_content.replace(key, value)

    # Write back to file
    with open(file_path, 'w') as file:
        file.write(final_content)

# Example usage:
remove_comments('your_script.py')

行内公式使用$$而不是$$$$

使用cloud-document-converter下载的markdown文件中,行内公式显示不正常,你可能需要这个脚本。

点击查看代码
import re
from datetime import datetime

def replace_formula_delimiters_in_md(input_file):
    try:
        timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
        output_file = f"{input_file.rsplit('.', 1)[0]}_{timestamp}.md"
        
        with open(input_file, 'r', encoding='utf-8') as file:
            lines = file.readlines()
        
        new_lines = []
        pattern = r'^\${2}(.+?)\${2}$'  # Matches lines with only $$content$$
        replace_pattern = r'\${2}(.+?)\${2}'  # Matches $$content$$ anywhere
        
        replacement_count = 0
        print("Changes to be made:")
        for line in lines:
            stripped_line = line.strip()
            if re.match(pattern, stripped_line):
                print(f"Keeping unchanged: {stripped_line}")
                new_lines.append(line)
            else:
                matches = list(re.finditer(replace_pattern, line))
                if matches:
                    replacement_count += len(matches)
                    new_line = re.sub(replace_pattern, r'$\1$', line)
                    print(f"Changing: {line.strip()} → {new_line.strip()}")
                    new_lines.append(new_line)
                else:
                    new_lines.append(line)
        
        with open(output_file, 'w', encoding='utf-8') as file:
            file.writelines(new_lines)
        
        print(f"\nCreated new file: {output_file}")
        print(f"Converted {replacement_count} formulas from $$ to $")
        
    except FileNotFoundError:
        print(f"Error: File '{input_file}' not found")
    except Exception as e:
        print(f"An error occurred: {str(e)}")

if __name__ == "__main__":
    input_md_file = "your_file.md"
    replace_formula_delimiters_in_md(input_md_file)

给文字加上反爬水印

点击查看代码
import random
import re

# Base templates for the reminder sentence variations
templates = [
    "【防盗链提醒:爬虫是吧?原贴在:<a href=\"{url}\" target=\"_blank\">{url}</a>】",
    "【防爬提示:你是爬虫吗?原文地址:<a href=\"{url}\" target=\"_blank\">{url}</a>】",
    "【反爬警告:爬虫别乱来!原帖链接:<a href=\"{url}\" target=\"_blank\">{url}</a>】",
    "【防盗链通知:爬虫注意了,原文在:<a href=\"{url}\" target=\"_blank\">{url}</a>】",
    "【反爬提醒:是爬虫的话,原文见:<a href=\"{url}\" target=\"_blank\">{url}</a>】",
    "【防爬虫提示:爬虫请止步,原文链接:<a href=\"{url}\" target=\"_blank\">{url}</a>】",
    "【盗链警告:爬虫你好,原文地址:<a href=\"{url}\" target=\"_blank\">{url}</a>】",
    "【防爬声明:爬虫请看,原文出处:<a href=\"{url}\" target=\"_blank\">{url}</a>】",
    "【反盗链提醒:爬虫注意,原链接:<a href=\"{url}\" target=\"_blank\">{url}</a>】",
    "【爬虫警告:想爬吗?原文在这:<a href=\"{url}\" target=\"_blank\">{url}</a>】",
]

# URL to use
url = "https://www.cnblogs.com/yhm138/p/18549168"

# Function to read content and split into sentences
def split_into_sentences(content):
    # Split on sentence endings, keeping the delimiter
    sentences = re.split(r'([.!?。!?\n])\s+', content)
    # Combine delimiter with preceding text to form complete sentences
    result = []
    for i in range(0, len(sentences) - 1, 2):
        result.append(sentences[i] + (sentences[i + 1] if i + 1 < len(sentences) else ""))
    if len(sentences) % 2 != 0:  # Handle odd number of splits
        result.append(sentences[-1])
    return [s.strip() for s in result if s.strip()]  # Remove empty lines and strip whitespace

# Read input markdown file
input_file = "original_mdfile.md"
try:
    with open(input_file, "r", encoding="utf-8") as f:
        original_content = f.read()
except FileNotFoundError:
    print(f"Error: File '{input_file}' not found! Creating a sample input.")
    original_content = "This is sentence one. Here's sentence two! # Header here\nAnother sentence? Yes, indeed."

# Split content into sentences
sentences = split_into_sentences(original_content)

# Determine valid insertion points (between sentences, excluding before '#')
valid_insertion_points = []
for i in range(len(sentences) - 1):
    next_sentence = sentences[i + 1]
    if not next_sentence.strip().startswith('#'):
        valid_insertion_points.append(i)

if not valid_insertion_points:
    print("No valid insertion points found (all subsequent sentences start with '#'). Output will be unchanged.")
    content = original_content
else:
    # Decide how many insertions (up to 10 or available valid points)
    num_insertions = min(10, len(valid_insertion_points))

    # Randomly select insertion points from valid ones
    insertion_indices = random.sample(valid_insertion_points, num_insertions)
    insertion_indices.sort()  # Sort to maintain order

    # Shuffle templates and select needed number
    random.shuffle(templates)
    selected_templates = templates[:num_insertions]

    # Build new content with insertions
    content = ""
    template_idx = 0
    for i, sentence in enumerate(sentences):
        content += sentence
        if i in insertion_indices and template_idx < num_insertions:
            content += " " + selected_templates[template_idx].format(url=url) + " "
            print("insert.")
            template_idx += 1
        # Preserve line breaks after sentences
        if i < len(sentences) - 1 and original_content[original_content.index(sentence) + len(sentence):].startswith('\n'):
            content += "\n"

# Write to output markdown file
output_file = "original_mdfile_with_reminders.md"
with open(output_file, "w", encoding="utf-8") as f:
    f.write(content)

print(f"Markdown file '{output_file}' has been generated with up to {num_insertions} insertions!")

PyPDF2 顺时针旋转一个PDF的所有页面并保存

点击查看代码
import PyPDF2

# 打开原始PDF文件
with open('input.pdf', 'rb') as file:
    reader = PyPDF2.PdfReader(file)
    writer = PyPDF2.PdfWriter()
    
    # 遍历所有页面
    for page in reader.pages:
        # 顺时针旋转90度
        rotated_page = page.rotate(90)
        writer.add_page(rotated_page)
    
    # 保存旋转后的PDF
    with open('output.pdf', 'wb') as output_file:
        writer.write(output_file)

多张图片拼接为一张长图

点击查看代码
# Requirements: pip install pillow tkinterdnd2 requests

from PIL import Image, ImageTk, ImageGrab
import tkinter as tk
from tkinter import ttk, filedialog, messagebox, simpledialog, Menu
from tkinterdnd2 import DND_FILES, TkinterDnD, DND_TEXT
import os
import tempfile
import requests
import uuid
import math

class ImageStitcher:
    def __init__(self, root):
        self.root = root
        self.root.title("Image Stitcher — Drag/URL • Zoom • Rotate • Pin • Preview")
        self.root.geometry("860x640")
        self.root.minsize(780, 540)

        # Each image now has 'zoom' and 'rotate' (degrees)
        self.images = []  # [{'path':str, 'zoom':float, 'rotate':float, 'thumb':PhotoImage, 'temp':bool}]
        self.direction = tk.StringVar(value="horizontal")
        self.always_on_top = tk.BooleanVar(value=False)

        self.temp_dir = tempfile.mkdtemp(prefix="imgstitch_")
        self.base_thumb_size = 80

        self._init_ui()
        self._setup_reorder_drag()
        self._setup_paste_menu()

        self.root.bind("<Control-v>", self.paste_from_clipboard)
        self.always_on_top.trace("w", self._update_on_top)

    def _init_ui(self):
        top_frame = tk.Frame(self.root)
        top_frame.pack(fill="x", padx=10, pady=(6,0))

        tk.Label(top_frame, text="Image Stitcher", font=("Segoe UI", 11, "bold")).pack(side="left")

        pin_btn = ttk.Checkbutton(
            top_frame,
            text="Pin  📌",
            variable=self.always_on_top,
        )
        pin_btn.pack(side="right")

        main = tk.Frame(self.root)
        main.pack(fill="both", expand=True, padx=12, pady=6)

        left = tk.Frame(main, width=340)
        left.pack(side="left", fill="y", padx=(0,12))

        tk.Label(left, text="Images (drag files/browser/URL, right-click or Ctrl+V paste,\ndouble-click → set zoom % + rotation °)", 
                 font=("Segoe UI", 10)).pack(anchor="w")

        self.listbox = tk.Listbox(left, font=("Consolas", 10), height=22, selectmode="single")
        self.listbox.pack(fill="both", expand=True, pady=(4,0))

        scrollbar = ttk.Scrollbar(left, orient="vertical", command=self.listbox.yview)
        self.listbox.configure(yscrollcommand=scrollbar.set)
        scrollbar.pack(side="right", fill="y")

        btn_frame = tk.Frame(left)
        btn_frame.pack(fill="x", pady=8)

        ttk.Button(btn_frame, text="Clear", width=8, command=self.clear_all).pack(side="left", padx=4)
        ttk.Button(btn_frame, text="Remove", width=10, command=self.remove_selected).pack(side="left", padx=4)

        settings = tk.Frame(left)
        settings.pack(fill="x", pady=(6,0))

        tk.Label(settings, text="Stitch:").pack(side="left")
        ttk.Radiobutton(settings, text="Horizontal", variable=self.direction, value="horizontal").pack(side="left", padx=8)
        ttk.Radiobutton(settings, text="Vertical",   variable=self.direction, value="vertical").pack(side="left")

        ttk.Button(left, text="Stitch & Save", command=self.stitch_and_save).pack(pady=12, fill="x")

        right = tk.Frame(main)
        right.pack(side="right", fill="both", expand=True)

        tk.Label(right, text="Preview (click to see large stitched result)", font=("Segoe UI", 10)).pack(anchor="w")

        self.preview_canvas = tk.Canvas(right, bg="#f9f9fa", highlightthickness=1, relief="ridge")
        self.preview_canvas.pack(fill="both", expand=True, pady=(4,0))

        self.preview_canvas.bind("<Button-1>", self._show_large_preview)

        self.listbox.drop_target_register(DND_FILES, DND_TEXT)
        self.listbox.dnd_bind('<<Drop>>', self.on_drop)

    def _setup_paste_menu(self):
        self.context_menu = Menu(self.root, tearoff=0)
        self.context_menu.add_command(label="Paste from Clipboard", command=self.paste_from_clipboard)

        def show_context_menu(event):
            try:
                self.context_menu.tk_popup(event.x_root, event.y_root)
            finally:
                self.context_menu.grab_release()

        self.listbox.bind("<Button-3>", show_context_menu)
        self.root.bind("<Button-3>", show_context_menu)
        self.root.bind_class("Tk", "<Button-3>", show_context_menu, add="+")

    def _update_on_top(self, *args):
        self.root.attributes("-topmost", self.always_on_top.get())

    def _show_large_preview(self, event=None):
        stitched = self._build_stitched_image()
        if stitched is None:
            return

        preview_win = tk.Toplevel(self.root)
        preview_win.title("Stitched Preview")
        preview_win.geometry("900x700")
        preview_win.transient(self.root)

        canvas = tk.Canvas(preview_win, bg="#f0f0f0")
        canvas.pack(fill="both", expand=True)

        max_w, max_h = 880, 640
        scale = min(1.0, max_w / stitched.width, max_h / stitched.height)
        if scale < 1:
            disp = stitched.resize((int(stitched.width * scale), int(stitched.height * scale)), Image.Resampling.LANCZOS)
        else:
            disp = stitched

        photo = ImageTk.PhotoImage(disp)
        canvas.create_image(10, 10, image=photo, anchor="nw")
        canvas.image = photo
        canvas.configure(scrollregion=(0, 0, disp.width + 20, disp.height + 20))

        ttk.Button(preview_win, text="Close", command=preview_win.destroy).pack(pady=8)

    def _build_stitched_image(self):
        if not self.images:
            messagebox.showinfo("Nothing loaded", "No images.")
            return None

        try:
            pil_images = []
            for d in self.images:
                img = Image.open(d["path"]).convert("RGBA")
                # Apply zoom
                if d["zoom"] != 1.0:
                    new_size = (int(img.width * d["zoom"]), int(img.height * d["zoom"]))
                    img = img.resize(new_size, Image.Resampling.LANCZOS)
                # Apply rotation
                if d["rotate"] % 360 != 0:
                    img = img.rotate(d["rotate"], expand=True, resample=Image.Resampling.BICUBIC)
                pil_images.append(img)
        except Exception as e:
            messagebox.showerror("Error", f"Load/resize/rotate failed:\n{e}")
            return None

        is_h = self.direction.get() == "horizontal"

        if is_h:
            max_h = max(img.height for img in pil_images)
            total_w = sum(img.width for img in pil_images)
            size = (total_w, max_h)
            get_pos = lambda off, w, h: (off, (max_h - h) // 2)
        else:
            max_w = max(img.width for img in pil_images)
            total_h = sum(img.height for img in pil_images)
            size = (max_w, total_h)
            get_pos = lambda off, w, h: ((max_w - w) // 2, off)

        result = Image.new("RGBA", size, (0,0,0,0))
        offset = 0
        for img in pil_images:
            w, h = img.size
            pos = get_pos(offset, w, h)
            result.paste(img, pos)
            offset += w if is_h else h

        return result

    def stitch_and_save(self):
        result = self._build_stitched_image()
        if result is None:
            return

        first = os.path.splitext(os.path.basename(self.images[0]["path"]))[0]
        default = f"stitched_{first}_[{len(self.images)}]_{self.direction.get()}"
        path = filedialog.asksaveasfilename(
            defaultextension=".png",
            filetypes=[("PNG","*.png"), ("JPEG","*.jpg"), ("WebP","*.webp")],
            initialfile=default
        )
        if not path: return

        try:
            if path.lower().endswith((".jpg",".jpeg")):
                result = result.convert("RGB")
            result.save(path, quality=92)
            messagebox.showinfo("Saved", f"Output:\n{path}")
        except Exception as e:
            messagebox.showerror("Save failed", str(e))

    def _edit_properties(self, event):
        idx = self.listbox.nearest(event.y)
        if idx < 0 or idx >= len(self.images):
            return

        d = self.images[idx]
        name = os.path.basename(d["path"])

        dialog = tk.Toplevel(self.root)
        dialog.title(f"Properties — {name}")
        dialog.transient(self.root)
        dialog.grab_set()

        tk.Label(dialog, text=f"Image: {name}", font=("Segoe UI", 10, "bold")).pack(pady=(10,5))

        # Zoom
        zoom_frame = tk.Frame(dialog)
        zoom_frame.pack(fill="x", padx=20, pady=5)
        tk.Label(zoom_frame, text="Zoom (%):").pack(side="left")
        zoom_var = tk.DoubleVar(value=d["zoom"] * 100)
        tk.Entry(zoom_frame, textvariable=zoom_var, width=10).pack(side="left", padx=10)

        # Rotation
        rot_frame = tk.Frame(dialog)
        rot_frame.pack(fill="x", padx=20, pady=5)
        tk.Label(rot_frame, text="Rotate (°):").pack(side="left")
        rot_var = tk.DoubleVar(value=d["rotate"])
        tk.Entry(rot_frame, textvariable=rot_var, width=10).pack(side="left", padx=10)

        def apply_changes():
            try:
                new_zoom = zoom_var.get() / 100.0
                new_rot = rot_var.get()
                if new_zoom < 0.1 or new_zoom > 5.0:
                    raise ValueError("Zoom must be between 10% and 500%")
                d["zoom"] = new_zoom
                d["rotate"] = new_rot % 360
                self._regen_thumbnail(idx)
                self._update_listbox()
                self.update_preview()
                dialog.destroy()
            except Exception as e:
                messagebox.showerror("Invalid input", str(e))

        ttk.Button(dialog, text="Apply", command=apply_changes).pack(pady=15)
        ttk.Button(dialog, text="Cancel", command=dialog.destroy).pack()

    def _regen_thumbnail(self, index):
        d = self.images[index]
        try:
            pil_img = Image.open(d["path"]).convert("RGBA")

            # Apply zoom for thumbnail
            scale = max(0.5, min(3.0, d["zoom"]))
            thumb_size = int(self.base_thumb_size * scale)

            # Resize first (zoom)
            if d["zoom"] != 1.0:
                new_size = (int(pil_img.width * d["zoom"]), int(pil_img.height * d["zoom"]))
                pil_img = pil_img.resize(new_size, Image.Resampling.LANCZOS)

            # Then rotate
            if d["rotate"] % 360 != 0:
                pil_img = pil_img.rotate(d["rotate"], expand=True, resample=Image.Resampling.BICUBIC)

            thumb = pil_img.copy()
            thumb.thumbnail((thumb_size, thumb_size), Image.Resampling.LANCZOS)
            d["thumb"] = ImageTk.PhotoImage(thumb)
        except:
            pass

    def _setup_reorder_drag(self):
        self.listbox.bind("<Button-1>", self._start_drag)
        self.listbox.bind("<B1-Motion>", self._drag_motion)
        self.listbox.bind("<ButtonRelease-1>", self._drop_reorder)
        self.listbox.bind("<Double-Button-1>", self._edit_properties)  # ← changed
        self.drag_data = {"index": None}

    def _start_drag(self, event):
        idx = self.listbox.nearest(event.y)
        if 0 <= idx < self.listbox.size():
            self.drag_data["index"] = idx
            self.listbox.selection_clear(0, tk.END)
            self.listbox.selection_set(idx)

    def _drag_motion(self, event):
        if self.drag_data["index"] is None: return
        target = self.listbox.nearest(event.y)
        if target == self.drag_data["index"] or target < 0 or target >= self.listbox.size():
            return
        text = self.listbox.get(self.drag_data["index"])
        self.listbox.delete(self.drag_data["index"])
        self.listbox.insert(target, text)
        self.drag_data["index"] = target
        self.listbox.selection_clear(0, tk.END)
        self.listbox.selection_set(target)

    def _drop_reorder(self, event):
        if self.drag_data["index"] is None: return
        new_order = []
        for i in range(self.listbox.size()):
            fname = self.listbox.get(i).split("  (")[0]
            for img in self.images:
                if os.path.basename(img["path"]) == fname:
                    new_order.append(img)
                    break
        self.images = new_order
        self.update_preview()
        self.drag_data["index"] = None

    def on_drop(self, event):
        data = event.data.strip()
        added = 0

        if data.startswith("{") or os.path.sep in data or data.lower().startswith(("file://", "file:")):
            paths = self.root.tk.splitlist(data)
            for raw in paths:
                path = raw.strip('{}').strip()
                if self._add_image_from_path(path):
                    added += 1
        elif data.startswith("http"):
            urls = [u.strip() for u in data.split() if u.startswith("http")]
            for url in urls:
                if self._add_image_from_url(url):
                    added += 1

        if added > 0:
            self._update_listbox()
            self.update_preview()

    def _add_image_from_path(self, path):
        if not os.path.isfile(path): return False
        if not path.lower().endswith(('.png','.jpg','.jpeg','.webp','.bmp','.gif','.tif','.tiff')): return False
        if any(d["path"] == path for d in self.images): return False
        return self._load_and_add(path, is_temp=False)

    def _add_image_from_url(self, url):
        if any(d["path"] == url for d in self.images): return False
        try:
            resp = requests.get(url, timeout=8)
            if resp.status_code != 200 or 'image' not in resp.headers.get('content-type', ''):
                return False
            ct = resp.headers.get('content-type', '')
            ext = '.png'
            if 'jpeg' in ct: ext = '.jpg'
            elif 'webp' in ct: ext = '.webp'
            elif 'gif' in ct: ext = '.gif'
            temp_path = os.path.join(self.temp_dir, f"dl_{uuid.uuid4().hex[:8]}{ext}")
            with open(temp_path, "wb") as f:
                f.write(resp.content)
            return self._load_and_add(temp_path, is_temp=True)
        except:
            return False

    def paste_from_clipboard(self, event=None):
        try:
            img = ImageGrab.grabclipboard()
            if img is None or not isinstance(img, Image.Image):
                messagebox.showinfo("Clipboard", "No image found in clipboard.")
                return False

            temp_path = os.path.join(self.temp_dir, f"clip_{uuid.uuid4().hex[:8]}.png")
            img.save(temp_path, "PNG")

            if self._load_and_add(temp_path, is_temp=True):
                self._update_listbox()
                self.update_preview()
                return True
            else:
                if os.path.isfile(temp_path):
                    os.remove(temp_path)
                return False
        except Exception as e:
            messagebox.showwarning("Clipboard", f"Failed to paste:\n{str(e)}")
            return False

    def _load_and_add(self, path, is_temp=False):
        try:
            pil_img = Image.open(path)
            thumb = pil_img.copy()
            thumb.thumbnail((self.base_thumb_size, self.base_thumb_size), Image.Resampling.LANCZOS)
            tk_thumb = ImageTk.PhotoImage(thumb)

            self.images.append({
                "path": path,
                "zoom": 1.0,
                "rotate": 0.0,
                "thumb": tk_thumb,
                "temp": is_temp
            })
            return True
        except:
            if is_temp and os.path.isfile(path):
                try: os.remove(path)
                except: pass
            return False

    def _update_listbox(self):
        self.listbox.delete(0, tk.END)
        for img in self.images:
            name = os.path.basename(img["path"])
            parts = []
            if img["zoom"] != 1.0:
                parts.append(f"{int(img['zoom']*100)}%")
            if img["rotate"] % 360 != 0:
                rot = int(img["rotate"] % 360)
                parts.append(f"rot {rot}°")
            extra = f"  ({', '.join(parts)})" if parts else ""
            self.listbox.insert(tk.END, name + extra)

    def update_preview(self):
        self.preview_canvas.delete("all")
        if not self.images: return

        x, y = 12, 12
        spacing = 16
        for img_dict in self.images:
            thumb = img_dict["thumb"]
            w, h = thumb.width(), thumb.height()
            self.preview_canvas.create_image(x + w//2, y + h//2, image=thumb)
            x += w + spacing
            if x > self.preview_canvas.winfo_width() - 140:
                x = 12
                y += max(90, h + 24)
        self.preview_canvas.configure(scrollregion=self.preview_canvas.bbox("all"))

    def remove_selected(self):
        sel = self.listbox.curselection()
        if not sel: return
        idx = sel[0]
        item = self.images.pop(idx)
        if item["temp"] and os.path.isfile(item["path"]):
            try: os.remove(item["path"])
            except: pass
        self._update_listbox()
        self.update_preview()

    def clear_all(self):
        for item in self.images:
            if item["temp"] and os.path.isfile(item["path"]):
                try: os.remove(item["path"])
                except: pass
        self.images.clear()
        self.listbox.delete(0, tk.END)
        self.preview_canvas.delete("all")

    def __del__(self):
        for item in self.images:
            if item.get("temp") and os.path.isfile(item["path"]):
                try: os.remove(item["path"])
                except: pass
        try: os.rmdir(self.temp_dir)
        except: pass


if __name__ == "__main__":
    root = TkinterDnD.Tk()
    app = ImageStitcher(root)
    root.mainloop()

Python打包tkinter单文件代码及依赖为dist/目录下的单个exe

pyinstaller --noconfirm --onefile --windowed --collect-all tkinterdnd2 your_single_file.py
posted @ 2024-11-16 11:16  yhm138  阅读(108)  评论(0)    收藏  举报