html_errors标签单独显示各个错误消息

本文介绍了一个基于Struts框架的用户注册表单验证实现方法。通过自定义的UserForm类继承ActionForm并实现了validate方法来完成用户名和两次输入密码的一致性校验。此外,还展示了如何在HTML表单中展示错误信息。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

public class UserForm extends ActionForm {

private static final long serialVersionUID = 1L;

private User user = new User();
private String password2;

public User getUser() {
return user;
}

public void setUser(User user) {
this.user = user;
}

// ALT + SHIFT + S + R
public String getPassword2() {
return password2;
}

public void setPassword2(String password2) {
this.password2 = password2;
}

@Override
public ActionErrors validate(ActionMapping mapping,
HttpServletRequest request) {
ActionErrors errors = new ActionErrors();
String name1 = user.getUsername();
if(name1 != null)
name1 = name1.trim();
if("".equals(name1))
errors.add("[color=blue][b]username[/b][/color]", new ActionMessage("error.username"));
if(!user.getPassword().equals(password2)) {
errors.add("[b]password2[/b]", new ActionMessage("error.password2"));
}
return errors;
}
}


<form action="<html:rewrite action="/RegUser"/>">

user:<input type="text" name="user.username" value="${userForm.user.username}"/> <html:errors property="[color=blue][b]username[/b][/color]" /><br>

password:<input type="password" name="user.password" value="${userForm.user.password}" /><br>

password1:<input type="password" name="password2" value="${userForm.password2}"/><html:errors property="[color=blue][b]password2[/b][/color]" /><br>

以上蓝色部分必须对应上*****

<input type="submit" title="info.submit" value="<bean:message key='info.submit'/>" />
<html:submit titleKey="info.submit"><bean:message key="info.submit"/> </html:submit>
</form>
import os import subprocess import shutil import time import tkinter as tk from tkinter import filedialog, ttk, scrolledtext, messagebox, PhotoImage import win32com.client as win32 import threading import tempfile import queue import traceback import webbrowser import glob class DiffProcessorApp: def __init__(self, root): self.root = root root.title("高级文件夹比较工具") root.geometry("1000x700") root.configure(bg="#f5f5f5") # 创建现代风格主题 self.style = ttk.Style() self.style.theme_use('clam') # 自定义主题颜色 self.style.configure('TButton', font=('Segoe UI', 10, 'bold'), borderwidth=1, foreground="#333", background="#4CAF50", bordercolor="#388E3C", relief="flat", padding=8, anchor="center") self.style.map('TButton', background=[('active', '#388E3C'), ('disabled', '#BDBDBD')], foreground=[('disabled', '#9E9E9E')]) self.style.configure('TLabel', font=('Segoe UI', 9), background="#f5f5f5") self.style.configure('TLabelframe', font=('Segoe UI', 10, 'bold'), background="#f5f5f5", relief="flat", borderwidth=2) self.style.configure('TLabelframe.Label', font=('Segoe UI', 10, 'bold'), background="#f5f5f5", foreground="#2E7D32") self.style.configure('Treeview', font=('Segoe UI', 9), rowheight=25) self.style.configure('Treeview.Heading', font=('Segoe UI', 9, 'bold')) # 创建主框架 main_frame = ttk.Frame(root, padding="15") main_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10) # 标题区域 header_frame = ttk.Frame(main_frame) header_frame.pack(fill=tk.X, pady=(0, 15)) # 添加标题图标 try: icon = PhotoImage(file="folder_icon.png") self.icon_label = ttk.Label(header_frame, image=icon) self.icon_label.image = icon self.icon_label.pack(side=tk.LEFT, padx=(0, 10)) except: self.icon_label = ttk.Label(header_frame, text="📁", font=("Arial", 24)) self.icon_label.pack(side=tk.LEFT, padx=(0, 10)) title_label = ttk.Label(header_frame, text="高级文件夹比较工具", font=("Segoe UI", 18, "bold"), foreground="#2E7D32") title_label.pack(side=tk.LEFT) # 文件选择区域 file_frame = ttk.LabelFrame(main_frame, text="文件夹选择", padding="12") file_frame.pack(fill=tk.X, pady=5) # 文件夹选择 self.old_folder_entry, _ = self.create_folder_selector(file_frame, "原始文件夹:") self.new_folder_entry, _ = self.create_folder_selector(file_frame, "修改后文件夹:") # 比较选项区域 options_frame = ttk.LabelFrame(main_frame, text="比较选项", padding="12") options_frame.pack(fill=tk.X, pady=5) # 递归比较选项 self.recursive_var = tk.BooleanVar(value=True) recursive_check = ttk.Checkbutton(options_frame, text="递归比较子文件夹", variable=self.recursive_var) recursive_check.grid(row=0, column=0, padx=10, pady=5, sticky=tk.W) # 文件过滤 filter_frame = ttk.Frame(options_frame) filter_frame.grid(row=0, column=1, padx=10, pady=5, sticky=tk.W) ttk.Label(filter_frame, text="文件过滤:").pack(side=tk.LEFT, padx=(0, 5)) self.filter_var = tk.StringVar(value="*.*") filter_entry = ttk.Entry(filter_frame, textvariable=self.filter_var, width=15) filter_entry.pack(side=tk.LEFT) # 文件报告选项 self.file_report_var = tk.BooleanVar(value=True) file_report_check = ttk.Checkbutton(options_frame, text="生成文件比较报告", variable=self.file_report_var) file_report_check.grid(row=0, column=2, padx=10, pady=5, sticky=tk.W) # 添加报告格式选择 report_frame = ttk.Frame(options_frame) report_frame.grid(row=1, column=0, columnspan=3, padx=10, pady=5, sticky=tk.W) ttk.Label(report_frame, text="报告格式:").pack(side=tk.LEFT, padx=(0, 5)) self.report_format_var = tk.StringVar(value="html") ttk.Radiobutton(report_frame, text="HTML", variable=self.report_format_var, value="html").pack(side=tk.LEFT, padx=5) ttk.Radiobutton(report_frame, text="XML", variable=self.report_format_var, value="xml").pack(side=tk.LEFT, padx=5) # 输出设置区域 self.excel_frame = ttk.LabelFrame(main_frame, text="输出设置", padding="12") self.excel_frame.pack(fill=tk.X, pady=5) # 目标Excel选择 ttk.Label(self.excel_frame, text="目标Excel文件:").grid(row=0, column=0, sticky=tk.W, padx=5, pady=5) self.excel_file_entry = ttk.Entry(self.excel_frame, width=60) self.excel_file_entry.grid(row=0, column=1, padx=5, pady=5) ttk.Button(self.excel_frame, text="浏览...", command=lambda: self.select_file(self.excel_file_entry, [("Excel文件", "*.xlsx *.xlsm")])).grid(row=0, column=2, padx=5, pady=5) # WinMerge路径设置 winmerge_frame = ttk.Frame(self.excel_frame) winmerge_frame.grid(row=1, column=0, columnspan=3, sticky=tk.W, padx=5, pady=5) ttk.Label(winmerge_frame, text="WinMerge路径:").grid(row=0, column=0, sticky=tk.W) self.winmerge_entry = ttk.Entry(winmerge_frame, width=60) self.winmerge_entry.grid(row=0, column=1, padx=5) self.winmerge_entry.insert(0, r"E:\App\WinMerge\WinMerge2.16.12.0\WinMergeU.exe") # 默认路径 ttk.Button(winmerge_frame, text="浏览...", command=lambda: self.select_file(self.winmerge_entry, [("WinMerge 可执行文件", "*.exe")])).grid(row=0, column=2) # 执行按钮区域 button_frame = ttk.Frame(main_frame) button_frame.pack(fill=tk.X, pady=10) self.run_button = ttk.Button(button_frame, text="执行比较", command=self.start_processing, width=20, style='TButton') self.run_button.pack(side=tk.LEFT) # 停止按钮 self.stop_button = ttk.Button(button_frame, text="停止", command=self.stop_processing, width=10, state=tk.DISABLED) self.stop_button.pack(side=tk.LEFT, padx=10) # 查看报告按钮 self.view_report_button = ttk.Button(button_frame, text="查看报告", command=self.view_report, width=10, state=tk.DISABLED) self.view_report_button.pack(side=tk.LEFT, padx=10) # 进度条 self.progress = ttk.Progressbar(main_frame, orient=tk.HORIZONTAL, length=700, mode='determinate') self.progress.pack(fill=tk.X, pady=5) # 状态信息 status_frame = ttk.Frame(main_frame) status_frame.pack(fill=tk.X, pady=5) self.status_var = tk.StringVar(value="准备就绪") status_label = ttk.Label(status_frame, textvariable=self.status_var, font=("Segoe UI", 9), foreground="#2E7D32") status_label.pack(side=tk.LEFT) # 日志和预览区域 notebook = ttk.Notebook(main_frame) notebook.pack(fill=tk.BOTH, expand=True, pady=5) # 文件夹结构标签 tree_frame = ttk.Frame(notebook, padding="5") notebook.add(tree_frame, text="文件夹结构") # 创建树形视图 self.tree = ttk.Treeview(tree_frame, columns=("Status"), show="tree") self.tree.heading("#0", text="文件夹结构", anchor=tk.W) self.tree.heading("Status", text="状态", anchor=tk.W) self.tree.column("#0", width=400) self.tree.column("Status", width=100) vsb = ttk.Scrollbar(tree_frame, orient="vertical", command=self.tree.yview) hsb = ttk.Scrollbar(tree_frame, orient="horizontal", command=self.tree.xview) self.tree.configure(yscrollcommand=vsb.set, xscrollcommand=hsb.set) self.tree.grid(row=0, column=0, sticky="nsew") vsb.grid(row=0, column=1, sticky="ns") hsb.grid(row=1, column=0, sticky="ew") # 日志标签 log_frame = ttk.Frame(notebook, padding="5") notebook.add(log_frame, text="执行日志") self.log_text = scrolledtext.ScrolledText(log_frame, height=10, wrap=tk.WORD, font=("Consolas", 9)) self.log_text.pack(fill=tk.BOTH, expand=True) self.log_text.config(state=tk.DISABLED) # 设置网格权重 tree_frame.grid_rowconfigure(0, weight=1) tree_frame.grid_columnconfigure(0, weight=1) # 线程控制 self.processing = False self.queue = queue.Queue() self.last_report_path = None # 启动队列处理 self.root.after(100, self.process_queue) def create_folder_selector(self, parent, label_text): """创建文件夹选择器组件""" frame = ttk.Frame(parent) frame.pack(fill=tk.X, pady=5) ttk.Label(frame, text=label_text).grid(row=0, column=0, sticky=tk.W, padx=5, pady=5) entry = ttk.Entry(frame, width=70) entry.grid(row=0, column=1, padx=5, pady=5) button = ttk.Button(frame, text="浏览文件夹...", command=lambda: self.select_folder(entry)) button.grid(row=0, column=2, padx=5, pady=5) return entry, button def select_folder(self, entry): """选择文件夹""" foldername = filedialog.askdirectory() if foldername: entry.delete(0, tk.END) entry.insert(0, foldername) # 自动填充文件夹结构 self.populate_folder_tree(foldername) def select_file(self, entry, filetypes=None): """选择文件""" if filetypes is None: filetypes = [("所有文件", "*.*")] filename = filedialog.askopenfilename(filetypes=filetypes) if filename: entry.delete(0, tk.END) entry.insert(0, filename) def populate_folder_tree(self, path): """填充文件夹结构树""" self.tree.delete(*self.tree.get_children()) if not os.path.isdir(path): return # 添加根节点 root_node = self.tree.insert("", "end", text=os.path.basename(path), values=("文件夹",), open=True) self.add_tree_nodes(root_node, path) def add_tree_nodes(self, parent, path): """递归添加树节点""" try: for item in os.listdir(path): item_path = os.path.join(path, item) if os.path.isdir(item_path): node = self.tree.insert(parent, "end", text=item, values=("文件夹",)) self.add_tree_nodes(node, item_path) else: self.tree.insert(parent, "end", text=item, values=("文件",)) except PermissionError: self.log_message(f"权限错误: 无法访问 {path}") def log_message(self, message): """记录日志消息""" self.queue.put(("log", message)) def update_progress(self, value): """更新进度条""" self.queue.put(("progress", value)) def update_status(self, message): """更新状态信息""" self.queue.put(("status", message)) def process_queue(self): """处理线程队列中的消息""" try: while not self.queue.empty(): msg_type, data = self.queue.get_nowait() if msg_type == "log": self.log_text.config(state=tk.NORMAL) self.log_text.insert(tk.END, data + "\n") self.log_text.see(tk.END) self.log_text.config(state=tk.DISABLED) elif msg_type == "progress": self.progress['value'] = data elif msg_type == "status": self.status_var.set(data) except queue.Empty: pass self.root.after(100, self.process_queue) def view_report(self): """查看生成的报告""" if self.last_report_path and os.path.exists(self.last_report_path): try: webbrowser.open(self.last_report_path) except Exception as e: messagebox.showerror("错误", f"无法打开报告: {str(e)}") else: messagebox.showwarning("警告", "没有可用的报告文件") def process_folders(self, old_path, new_path, excel_file): """处理文件夹比较的线程函数""" output_html = None try: # 设置报告目录为Excel文件所在目录 report_dir = os.path.dirname(excel_file) os.makedirs(report_dir, exist_ok=True) # 步骤1: 生成HTML差异文件 self.update_status("生成HTML差异文件...") self.update_progress(30) # 使用临时文件存储HTML报告 with tempfile.NamedTemporaryFile(suffix=".html", delete=False) as temp_file: output_html = temp_file.name winmerge_path = self.winmerge_entry.get() if not self.run_winmerge(winmerge_path, old_path, new_path, output_html, report_dir): self.update_status("WinMerge执行失败") return # 步骤2:HTML文件复制到Excel目录 self.update_status("复制HTML报告到目标目录...") self.update_progress(60) if report_dir: target_html = os.path.join(report_dir, "diff_report.html") try: shutil.copy(output_html, target_html) self.log_message(f"已将HTML文件复制到: {target_html}") self.last_report_path = target_html self.view_report_button.config(state=tk.NORMAL) except Exception as e: self.log_message(f"文件复制失败: {str(e)}") self.last_report_path = output_html # 步骤3: 打开Excel并等待用户操作 self.update_status("打开Excel文件...") self.update_progress(80) if not self.open_excel_file(excel_file): self.update_status("打开Excel失败") return # 完成 self.update_progress(100) self.update_status("处理完成!") self.log_message("文件夹比较流程执行完毕") messagebox.showinfo("完成", "已生成HTML报告并打开Excel文件") except Exception as e: error_msg = f"执行过程中发生错误: {str(e)}\n{traceback.format_exc()}" self.log_message(error_msg) self.update_status("执行失败") messagebox.showerror("错误", f"处理失败: {str(e)}") finally: # 重新启用执行按钮 if self.processing: self.stop_processing() # 清理临时文件 if output_html and os.path.exists(output_html): try: os.remove(output_html) except: pass def start_processing(self): """启动处理线程""" if self.processing: self.log_message("警告: 处理正在进行中") return # 获取路径 old_path = self.old_folder_entry.get() new_path = self.new_folder_entry.get() excel_file = self.excel_file_entry.get() # 详细路径验证 validation_errors = [] if not old_path: validation_errors.append("原始文件夹路径为空") elif not os.path.isdir(old_path): validation_errors.append(f"原始文件夹路径无效: {old_path}") if not new_path: validation_errors.append("新文件夹路径为空") elif not os.path.isdir(new_path): validation_errors.append(f"新文件夹路径无效: {new_path}") if not excel_file: validation_errors.append("Excel文件路径为空") elif not excel_file.lower().endswith(('.xlsx', '.xlsm')): validation_errors.append("Excel文件必须是.xlsx或.xlsm格式") winmerge_path = self.winmerge_entry.get() if not winmerge_path or not os.path.exists(winmerge_path): validation_errors.append("WinMerge路径无效或未设置") if validation_errors: self.log_message("错误: " + "; ".join(validation_errors)) messagebox.showerror("输入错误", "\n".join(validation_errors)) return # 禁用执行按钮,启用停止按钮 self.run_button.config(state=tk.DISABLED) self.stop_button.config(state=tk.NORMAL) self.view_report_button.config(state=tk.DISABLED) self.processing = True # 启动处理线程 thread = threading.Thread(target=self.process_folders, args=(old_path, new_path, excel_file)) thread.daemon = True thread.start() self.log_message("处理线程已启动") def run_winmerge(self, winmerge_path, path1, path2, output_html, report_dir): """针对WinMerge 2.16.12.0优化的报告生成方法""" # 验证WinMerge可执行文件 if not os.path.exists(winmerge_path): self.log_message(f"错误: WinMerge路径不存在 {winmerge_path}") return False # 确保报告目录存在 os.makedirs(report_dir, exist_ok=True) # 构建基本命令 base_cmd = [ winmerge_path, '/u', '/nosplash', # 不显示启动画面 '/dl', 'Base', '/dr', 'Modified', '/noninteractive' # 非交互模式 ] # 添加递归选项 if self.recursive_var.get(): base_cmd.append('/r') # 添加文件过滤 file_filter = self.filter_var.get() if file_filter and file_filter != "*.*": base_cmd.extend(['-f', file_filter]) # 执行文件夹报告生成 folder_report_success = False self.update_status("正在生成文件夹结构比较报告...") # 使用临时文件存储文件夹报告 with tempfile.NamedTemporaryFile(suffix=".html", delete=False) as temp_file: folder_report_path = temp_file.name folder_report_cmd = base_cmd.copy() folder_report_cmd.extend([ '/or', folder_report_path, # 文件夹报告输出到临时文件 path1, path2 ]) folder_success = self.execute_winmerge_command(folder_report_cmd, "文件夹报告") # 验证文件夹报告 if folder_success: if os.path.exists(folder_report_path) and os.path.getsize(folder_report_path) > 0: self.log_message(f"文件夹报告生成成功: {folder_report_path}") folder_report_success = True # 将文件夹报告复制到最终位置 shutil.copy(folder_report_path, output_html) self.log_message(f"已将文件夹报告复制到: {output_html}") else: self.log_message("警告: 文件夹报告文件为空或不存在") # 执行文件报告生成(如果启用) file_report_success = False if self.file_report_var.get() and folder_report_success: self.update_status("正在生成文件内容比较报告...") # 创建文件报告专用目录 file_report_dir = os.path.join(report_dir, "file_reports") os.makedirs(file_report_dir, exist_ok=True) # 构建文件报告命令 file_report_cmd = base_cmd.copy() file_report_cmd.extend([ path1, path2, '/file-report', # 启用文件报告 f'/report-type:{self.report_format_var.get()}', f'/report-output:{file_report_dir}' # 文件报告输出到专用目录 ]) file_success = self.execute_winmerge_command(file_report_cmd, "文件报告") # 验证文件报告 if file_success: # 检查报告目录中的文件报告 report_files = self.find_winmerge_file_reports(file_report_dir) if report_files: self.log_message(f"找到 {len(report_files)} 个文件报告:") for f in report_files: self.log_message(f" - {os.path.basename(f)}") # 检查是否有主报告文件 main_report = os.path.join(file_report_dir, f"diff_report.{self.report_format_var.get()}") if os.path.exists(main_report): self.log_message(f"找到合并报告文件: {os.path.basename(main_report)}") file_report_success = True # 将合并报告复制到报告目录 shutil.copy(main_report, os.path.join(report_dir, "file_diff_report.html")) else: self.log_message("警告: 未找到预期的diff_report文件") else: self.log_message("警告: 未生成任何文件报告") # 尝试备选查找方法 self.find_alternative_reports(file_report_dir) # 清理临时文件 if os.path.exists(folder_report_path): try: os.remove(folder_report_path) except: pass return folder_report_success and (not self.file_report_var.get() or file_report_success) def find_winmerge_file_reports(self, report_dir): """查找WinMerge生成的文件报告 - 增强版""" # 可能的报告文件名模式 patterns = [ f"diff_report.{self.report_format_var.get()}", # 主报告文件 f"*.diff.{self.report_format_var.get()}", # 文件名.diff.html f"*.compare.{self.report_format_var.get()}", # 文件名.compare.html f"Report*.{self.report_format_var.get()}", # Report*.html f"WinMergeReport*.{self.report_format_var.get()}", # WinMergeReport*.html f"*_report.{self.report_format_var.get()}", # 以_report结尾的文件 ] report_files = [] try: # 首先尝试查找特定模式 for pattern in patterns: for f in glob.glob(os.path.join(report_dir, pattern)): if os.path.isfile(f) and f not in report_files: report_files.append(f) # 如果没有找到,尝试查找所有HTML/XML文件 if not report_files: for ext in ['.html', '.xml']: for f in glob.glob(os.path.join(report_dir, f"*{ext}")): if os.path.isfile(f) and f not in report_files: report_files.append(f) except Exception as e: self.log_message(f"查找报告文件错误: {str(e)}\n{traceback.format_exc()}") return report_files def find_alternative_reports(self, report_dir): """备选报告查找方法""" # 检查是否有未预料到的报告文件 all_files = [] try: for f in os.listdir(report_dir): full_path = os.path.join(report_dir, f) if os.path.isfile(full_path): all_files.append(f) except: pass if all_files: self.log_message("目录中的文件列表:") for f in all_files: self.log_message(f" - {f}") # 检查WinMerge日志 winmerge_log = os.path.join(report_dir, "WinMerge-report.log") if os.path.exists(winmerge_log): self.log_message(f"发现WinMerge日志文件: {winmerge_log}") try: with open(winmerge_log, 'r', encoding='utf-8') as log_file: content = log_file.read(1000) # 读取前1000字符 self.log_message(f"日志片段: {content[:500]}...") except Exception as e: self.log_message(f"读取日志失败: {str(e)}") def execute_winmerge_command(self, cmd, report_type): """执行WinMerge命令并处理结果 - 增强版""" try: self.log_message(f"开始生成{report_type}...") self.log_message(f"执行命令: {' '.join(cmd)}") start_time = time.time() # 使用Popen执行命令 process = subprocess.Popen( cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, encoding='utf-8', errors='replace', creationflags=subprocess.CREATE_NO_WINDOW ) # 设置超时(15分钟) timeout = 900 try: stdout, stderr = process.communicate(timeout=timeout) except subprocess.TimeoutExpired: process.kill() stdout, stderr = process.communicate() self.log_message(f"{report_type}生成超时({timeout}秒),已终止进程") return False elapsed = time.time() - start_time self.log_message(f"{report_type}生成完成,耗时: {elapsed:.2f}秒") # 记录详细输出 if stdout.strip(): self.log_message(f"WinMerge输出:\n{stdout[:2000]}") # 分析输出中是否包含报告文件信息 if "report" in stdout.lower(): self.log_message("检测到报告相关输出") # 提取可能的报告文件路径 report_lines = [line for line in stdout.split('\n') if "report" in line.lower()] for line in report_lines: self.log_message(f"报告线索: {line}") if stderr.strip(): self.log_message(f"WinMerge错误:\n{stderr[:1000]}") # 限制输出长度 if process.returncode == 0: self.log_message(f"{report_type}命令执行成功") return True elif process.returncode == 1: # WinMerge在发现差异时返回1 self.log_message(f"{report_type}命令执行完成(发现差异)") return True else: error_msg = f"{report_type}生成失败(退出码{process.returncode})" self.log_message(error_msg) return False except Exception as e: self.log_message(f"{report_type}生成错误: {str(e)}\n{traceback.format_exc()}") return False def open_excel_file(self, excel_path): """打开Excel文件并等待用户操作""" self.log_message("正在打开Excel文件...") try: # 验证Excel文件存在 if not os.path.exists(excel_path): self.log_message(f"错误: Excel文件不存在 {excel_path}") return False # 使用系统默认程序打开Excel文件 os.startfile(excel_path) self.log_message(f"Excel文件已打开: {excel_path}") # 提示用户手动操作 self.log_message("Excel已打开,请手动执行操作") messagebox.showinfo("Excel已打开", "Excel文件已打开,请手动执行所需操作") return True except Exception as e: self.log_message(f"打开Excel文件失败: {str(e)}\n{traceback.format_exc()}") return False def stop_processing(self): """停止处理""" self.processing = False self.stop_button.config(state=tk.DISABLED) self.run_button.config(state=tk.NORMAL) self.view_report_button.config(state=tk.NORMAL) self.update_status("操作已停止") if __name__ == "__main__": root = tk.Tk() app = DiffProcessorApp(root) root.mainloop() 请你将上述的思路整合到我的代码中生成文件比较报告
最新发布
07-12
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值