OA的学习--第七天的内容--论坛模块

本文详细介绍了OA系统中论坛模块的开发过程,包括帖子的查询、发布、排序及分页功能实现等内容。重点讲解了使用Hibernate进行数据查询的技巧,如复杂的排序逻辑实现和FCKeditor网页编辑器的应用。

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

    OA第七天的学习,是从第64集开始到81集,共18集.终于快结束了,真真是不容易啊.废话不多说,还是早弄完早了吧.第七天的主要内容就是完成论坛模块,其中比较重要的几个点就是,查询和发布帖子/主题/版块,以及排序等的问题,但是这些和基本的增删改查还是比较接近的,所以除了排序基本不介绍了.然后就是用FCKeditor网页编辑器发帖,还有就是分页了,分页有两种,一种是最简单的分页,还有一种是带条件的分页,并且最后按照惯例,都进行了封装,达到代码复用的效果.

排序

    排序的要求是这样子的,首先有3种帖子,一种置顶,一种精华,一种普通.置顶贴一定是放在最前面,但是多个置顶贴的先后顺序是按更新时间来排序的;精华帖是不影响顺序的,和普通帖一样,按更新时间来.
/**
 * 查询主题列表
 * 
 */
@Deprecated
public List<Topic> findByForum(Forum forum) {
	//排序:所有置顶贴在最上面,并按最后更新时间排序,让新状态的在最上面;但是精华帖不影响顺序
	//1.查询2次,先查置顶再查精华
	//2.将置顶帖作为字段,来解决
	//3.如何让他任务0和1是一样的,而2是不一样的,
	return getSession().createQuery(// 
			//TODO:怎么排序
			"FROM Topic t WHERE t.forum=? ORDER BY (CASE t.type WHEN 2 THEN 2 ELSE 0 END) DESC,t.lastUpdateTime DESC")//
			.setParameter(0,forum)
			.list();
}
    核心就是写这么一句查询语句:FROM Topic t WHERE t.forum=? ORDER BY (CASE t.type WHEN 2 THEN 2 ELSE 0 END) DESC,t.lastUpdateTime DESC").其中整体是按更新时间倒序排序,所以最新的在最上面;然后还有根据 (CASE t.type WHEN 2 THEN 2 ELSE 0 END) DESC 排序排序,其中t.type中,普通贴的type为0,精华为1,而置顶为2.所以只有置顶贴为2,而普通帖和精华帖为0,然后按照2和0倒序排序,则置顶贴放在最前面.

使用FCKeditor发帖

    首先看下效果,就是这样的一个工具.可以改变输入文字的样式,插入图片.若是没有这个我们可以自己写HTML代码加上样式.

    使用网页编辑器FCKeditor发帖,就是要准备FCKeditor相关包,放到项目中.做法就是,导入js文件,然后准备一个地方放FCKeditor,最后就是写js代码显示FCKeditor.FCKeditor的创建有两种,一种直接create创建,一种replaceTextarea.

    只是现在用的FCKeditor,工具栏有很多不需要的,以及表情太少了.如何自定义自己的FCKeditor工具栏.在FCKeditor的配置文件fckconfig.js中,添加自定义的一种工具栏.然后设置ToolbarSet为bbs就可以了.

    但是直接修改fckconfig.js文件不是很好,可以再写一个自定义的配置文件,然后先加载默认的,然后加载自定义的,自定义的就可以覆盖默认的.所以在fckconfig.js中加上自定义配置文件myconfig.js.

    然后在myconfig.js,添加ToolBarSet,以及对于图片表情,给一个路径,以及图片名.

简单分页

    简单分页的效果就是在这样,基本上加上jquery.pagination.js分页插件,然后再写些真分页的查询语句就可以了,对于这里还做了转到页码,以及显示最多显示前后共10个页码.这里的问题是,pageSize没有地方设置,所以写死了.

    先分析分页需要做的事,以及写好大概的代码,并且列出需要的参数.其中分页的记录是recordList,而控制显示最多显示前后共10个页码,由于需要计算,所以直接让传递算好的值beginPageIndex和endPageIndex。最后转到的下拉列表中的值,根据总页数从1开始遍历生成,而gotoPage的功能实现需要写js代码控制。
   
    然后封装一个PageBean实体,封装分页需要的所有数据.并将这些数据进行分类,一种是前台指定,接收前台的值就可以;一种是查询数据库,得出确切的值;还有就是根据上面的两种数据,可以计算得到的数据.而且在构造函数中,通过接收前4个属性,来自动计算后3个属性.
/**
 * 分页功能中一页的信息
 * @author liu
 *
 */
public class PageBean {
	//指定或是页面参数
	private int	currentPage;	  // 当前页;
	private int	pageSize;         // 每页显示多少条


	//查询数据库
	private int	recordCount;      // 总记录数
	private List recordList; 	  // 本页的数据列表;


	//计算
	private int	pageCount;        // 总页数;
	private int	beginPageIndex;   // 页码列表的开始索引
	private int endPageIndex;     // 页码列表的结束索引
	 
	public PageBean() {};
	
	/**
	 * 只接收前4个必要的shuxing,会自动计算其他3个属性的值
	 * @param currentPage
	 * @param pageSize
	 * @param recordCount
	 * @param recordList
	 */
	public PageBean(int currentPage, int pageSize, int recordCount,
			List recordList) {
		this.currentPage = currentPage;
		this.pageSize = pageSize;
		this.recordCount = recordCount;
		this.recordList = recordList;
		
		// 计算总页码 (若有余数,加一页)
		pageCount = (recordCount + pageSize -1)/pageSize;
		
		// 计算beginPageIndex 和end PageIndex
		// 总页数不多于10页,则全部显示
		if (pageCount <= 10) {
			beginPageIndex = 1;
			endPageIndex = pageCount;
		}
		// 总页数多于10页,则显示当前页附近的共10个页面
		else {
		   // 当前页附近的共10个页码(前4个 + 当前页 + 后5个)
		   beginPageIndex = currentPage - 4;
		   endPageIndex = currentPage + 5;
		   // 当前面的页码不足4个时,则显示最前面10个页码,1-10
		   if (beginPageIndex < 1) {
			   beginPageIndex = 1;
			   endPageIndex = 10;
		   }
		   // 当后面的页码不足5个时,则显示最后10个页码,倒数第10个-倒数第1个
		   if (endPageIndex > pageCount) {
			   endPageIndex = pageCount;
			   beginPageIndex = pageCount - 10 + 1;
		   }
		}
	}
           …
}
    最后形成的结构就是这样,对于JSP,Action和Service分析各做了什么?
    对于jsp就是在页面上显示这些数据,而Action,需要接收jsp传递的参数,然后调用service的方法获取到pageBean,最后回显到jsp中,(实现时为了显示方便,所以pageBean放到了对象栈中).而service提供getPageBean的方法,先是查询出符合条件的记录,然后与查询总记录数,最后调用pageBean的构造函数,传递4个属性,计算3个属性,最后返回的pageBean中7个属性就会填充好.


    首先可以看下JSP的代码,最开始的时候,是每个页面都写一份这样的代码,后来由于有很多分页功能的页面,所以优化,提取和封装了这样一段jsp片段pageView.jspf.
    那么,在JSP页面中需要写什么,才能引入这个分页片段.需要include分页文件,然后要设置一个form用于提交,因为转到的封装需要用到这个.

<!--分页信息-->
<%@ include file="/WEB-INF/jsp/public/pageView.jspf" %>
<s:form action="topic_show?id=%{id}"></s:form>
    然后再看下pageView.jspf片段文件的内容,首先需要struts的标签和编码utf-8.然后就可是开始建立自定义的分页控件了,里面的样式由pageCommon.css提供.
<%@ page language="java" import="java.util.*" pageEncoding="utf-8"%>
<%@ taglib prefix="s" uri="/struts-tags" %>

<div id=PageSelectorBar>
	<div id=PageSelectorMemo>
		页次:${currentPage}/${pageCount }页  
		每页显示:${pageSize }条  
		总记录数:${recordCount }条
	</div>
	<div id=PageSelectorSelectorArea>
	
		<a href="javascript: gotoPage(1)" title="首页" style="cursor: hand;">
			<img src="${pageContext.request.contextPath}/style/blue/images/pageSelector/firstPage.png"/>
		</a>
		
		<s:iterator begin="%{beginPageIndex}" end="%{endPageIndex}" var="num">
			<s:if test="#num == currentPage"> <%-- 当前页 --%>
				<span class="PageSelectorNum PageSelectorSelected">${num}</span>
			</s:if>
			<s:else> <%-- 非当前页 --%>
				<span class="PageSelectorNum" style="cursor: hand;" onClick="gotoPage(${num});">${num}</span>
			</s:else>
			
		</s:iterator>
		
		<a href="javascript: gotoPage(${pageCount})" title="尾页" style="cursor: hand;">
			<img src="${pageContext.request.contextPath}/style/blue/images/pageSelector/lastPage.png"/>
		</a>
			 
		转到:
			<select onchange="gotoPage(this.value)" id="_pn">
				<s:iterator begin="1" end="%{pageCount}" var="num">
					<option value="${num}">${num}</option>
				</s:iterator>
			</select>
			<script type="text/javascript">
				$("#_pn").val("${currentPage}");
			</script>
	</div>
</div>

<script type="text/javascript">
	function gotoPage( pageNum ){
	            //方法1:路径写死,不能复用
		// window.location.href = "forum_show.action?id=${id}&pageNum=" + pageNum;
		//方法2:在form中中添加隐藏域,记录pageNum值,然后提交
		$(document.forms[0]).append("<input type='hidden' name='pageNum' value='" + pageNum +"'>");
		document.forms[0].submit();
	}
</script>

    其中,当前页码的样式有PageSelectorSelected,选中样式.首页和尾页是<<和>>图片显示,并且所有都是调用gotoPage,转到页面的js方法.

    而gotoPage的js跳转代码,之前用window.location.href的方式,写死路径,不能复用;所以改成在页面的form中加隐藏域,并提交.所以js页面一定要有为分页写一个form,而且若页面中有多个form,则该form必须放在最上面.
    接着Action的代码,由于多个页面都有分页功能,所以优化,所以将pageNum和pageSize放在BaseAction中了,但是实际这应该是页面接收过来的.
    然后分页最开始是在replyService中写了一个getPageBeanByTopic方法.后来由于带参数的分页中也有这么一段,于是优化写了一个getPageBean的传hql语句的方法(在下面的带参数分页中会介绍).

public abstract class BaseAction<T> extends ActionSupport implements ModelDriven<T> {
            …
	protected int pageNum = 1; // 当前页
	protected int pageSize = 10; //每页显示多少条记录
	…
}
public class TopicAction extends BaseAction<Topic> {
	/**
	 * 显示单个主题(主贴+回帖列表)
	 * @return
	 * @throws Exception
	 */
	public String show() throws Exception {
		//准备Topic
		Topic topic = topicService.getById(model.getId());
		ActionContext.getContext().put("topic",topic);
		
		//准备分页信息v1
		PageBean pageBean = replyService.getPageBeanByTopic(pageNum,pageSize,topic);
		ActionContext.getContext().getValueStack().push(pageBean);
		
		//准备分页信息v2
//		String hql = "FROM Reply r WHERE r.topic = ? ORDER BY r.postTime ASC";
//		List<Object> parameters = new ArrayList<Object>();
//		parameters.add(topic);
//		
//		PageBean pageBean = replyService.getPageBean(pageNum,pageSize,hql,parameters);
// 		ActionContext.getContext().getValueStack().push(pageBean);	
	        return "show";
	}
}
    Service层的代码
public class ReplyServiceImpl extends DaoSupportImpl<Reply> implements ReplyService {
           /**
	 * 查询分页信息
	 * @param pageNum
	 * @param pageSize
	 * @param topic
	 * @return
	 */
	public PageBean getPageBeanByTopic(int pageNum, int pageSize, Topic topic) {
		
		//查询所有回复记录
		List list = getSession().createQuery(//
					"FROM Reply r WHERE r.topic = ? ORDER BY r.postTime ASC")//
					.setParameter(0,topic)//
					.setFirstResult((pageNum -1) * pageSize)//
					.setMaxResults(pageSize)
					.list();
		//查询总记录数
		Long count = (Long) getSession().createQuery(//
				"SELECT COUNT(*) FROM Reply r WHERE r.topic = ? ")//
				 .setParameter(0,topic)//
				.uniqueResult();
		
		return new PageBean(pageNum,pageSize,count.intValue(),list);
	}       
}     
    这样简单分页就做好了.然后看看带条件的分页

带条件分页

    先看下带条件分页的效果.就是这样,在简单的分页插件上面,有3个下拉框和一个提交按钮.其中第一个下拉框,可以选择主题(帖子)的类型,是全部显示,还是只显示精华.第二个是控制排序的条件,其中默认排序的话,置顶贴在最前面;还有只按最后更新时间,以及只按回复数量,还有只按主题发表时间;最后一个下拉框就是选择降序还是升序.最后只有点击提交才有效果.

    首页,做法是和上面其实差不多,都要引入分页插件,但是这里还要多写些下拉框和按钮,然后对于后台,之前只要papeSize和当前页,现在还需要接收前台的参数,最后根据参数拼凑sql语句,查询出结果,返回.
    先看下JSP的代码,主要就是用s:select标签的list属性,写好各个下拉框中的value和text,还有提交按钮.以及引入pageView.jspf文件.而提交会提交到forum_show,并且会传递版块的id.
<s:form action="forum_show?id=%{id}">
<div id="MainArea">
	<div id="PageHead"></div>
	<center>
		 ...
		<div class="ForumPageTableBorder">
			 ...
			<!--其他操作-->
			<div id="TableTail">
				<div id="TableTail_inside">
					<table border="0" cellspacing="0" cellpadding="0" height="100%" align="left">
						<tr valign=bottom>
							<td></td>
							<td>	
								<s:select name="viewType" list="#{0:'全部主题', 1:'全部精华贴'}"/>
								<s:select name="orderBy" onchange="onSortByChange(this.value)"
									list="#{0:'默认排序(所有置顶帖在前面,并按最后更新时间降序排列)', 1:'只按最后更新时间排序', 2:'只按主题发表时间排序', 3:'只按回复数量排序'}"/>
								<s:select name="asc" list="#{false:'降序', true:'升序'}"/>
								<input type="IMAGE" src="${pageContext.request.contextPath}/style/blue/images/button/submit.PNG" align="ABSMIDDLE"/>
							</td>
						</tr>
					</table>
				</div>
			</div>
		</div>
	</center>
</div>
</s:form>

<!--分页信息-->
<%@ include file="/WEB-INF/jsp/public/pageView.jspf" %>
    然后 Action的代码,没有建立字典表,自己定义3个属性,接收前台下拉框中的值.然后直接写hql语句,来查询.最后调用service的getPageBean方法,查询出结果,并放到对象栈中回显到前台
/**
 * 版块
 * @author liu
 *
 */
@Controller
@Scope("prototype")
public class ForumAction extends BaseAction<Forum> {
	 	 
	/**
	 * 0:查看全部主题 ; 
	 * 1:只查看精华帖
	 */
	private int viewType = 0;
	
	/**
	 * 0.默认排序(所有置顶帖在前面,并按最后更新时间降序排列,
	 * 1:'只按最后更新时间排序', 
	 * 2:'只按主题发表时间排序', 
	 * 3:'只按回复数量排序'}"									
	 */
	private int orderBy = 0;
	/**
	 * true:表示升序
	 * false:表示降序
	 */
	private boolean asc = false;
 
	/**
	 * 显示单个版块(主题列表)
	 * @return
	 * @throws Exception
	 */
	public String show() throws Exception {
		 //准备数据,forum
		Forum forum = forumService.getById(model.getId());
		ActionContext.getContext().put("forum",forum);


                      //准备分页信息v3 拼接
		String hql = " FROM Topic t WHERE t.forum = ? ";
		
		List<Object> parameters = new ArrayList<Object>();
		parameters.add(forum);
		
		if(viewType == 1) { //1.表示只看精华帖
			hql = hql + " AND t.type = ? ";
			parameters.add(Topic.TYPE_BEST);
		}
		
		if (orderBy == 1) {
			// 1:'只按最后更新时间排序', 
			hql = hql + " ORDER BY t.lastUpdateTime " + (asc ? "ASC" : "DESC");				
		}else if(orderBy == 2){
			// 2:'只按主题发表时间排序', 
			hql = hql + " ORDER BY t.postTime " + (asc ? "ASC" : "DESC");
		}else if(orderBy == 3){
			// 3:'只按回复数量排序'}"
			hql = hql + " ORDER BY t.replyCount " + (asc ? "ASC" : "DESC");
		}else {
			// 0.默认排序(所有置顶帖在前面,并按最后更新时间降序排列,
			hql = hql + " ORDER BY (CASE t.type WHEN 2 THEN 2 ELSE 0 END) DESC,t.lastUpdateTime DESC";
		}
		
		PageBean pageBean = replyService.getPageBean(pageNum,pageSize,hql,parameters);
                       ActionContext.getContext().getValueStack().push(pageBean);


		return "show";
	}
        …
       …//getter,setter
}

    最后,就是Service的代码.为了提取优化,所以将getPageBeanByTopic和getPageBeanByForum提取为getPageBean放在了DaoSupportImpl中。先是查询出所有的Topic主题(帖子)记录,然后填充条件,查询,查询出需要的分页后的记录,以及查询出总记录数,最后就是PageBean的构造方法了.

@Service
@Transactional
@SuppressWarnings("unchecked")
public class ReplyServiceImpl extends DaoSupportImpl<Reply> implements ReplyService {
     ....
}

@Transactional  //注解可以继承
@SuppressWarnings("unchecked")
public class DaoSupportImpl<T> implements DaoSupport<T> {
	/**
	 * 公共的查询分页信息方法
	 */
	@Deprecated
	public PageBean getPageBean(int pageNum, int pageSize, String hql,
			List<Object> parameters) {
		System.out.println("---------->DAOSupportImpl.getPageBean()");
		//查询所有记录
		Query listQuery = getSession().createQuery(hql); //创建查询对象
		if (parameters != null) { //设置参数
			for (int i = 0; i < parameters.size(); i ++ ) {
				listQuery.setParameter(i,parameters.get(i));
			}
		}
			
		listQuery.setFirstResult((pageNum -1) * pageSize);
		listQuery.setMaxResults(pageSize);
		List list = listQuery.list();
		
		//查询总记录数
		Query countQuery = getSession().createQuery("SELECT COUNT(*)" + hql);
		if (parameters != null) { //设置参数
			for (int i = 0; i < parameters.size(); i ++ ) {
				countQuery.setParameter(i,parameters.get(i));
			}
		}
		Long count = (Long) countQuery.uniqueResult(); //执行查询
		
		return new PageBean(pageNum,pageSize,count.intValue(),list);
	} 
}
    这样,带参数的分页也完成了,难度主要就是要接收参数,然后感觉参数对应的值,拼凑hql语句,来查询.但是可以看出hql语句都写在Action中了,这样是很不好的.所以需要进一步的优化.

封装分页

    建立一个QueryHelper类,里面封装hql语句.将hql分为3部分,先是查什么,fromClause,以及怎么查,whereClause,以及查完如何排序orderByClause.
    在QueryHelper中,封装FROM子句,WHERE和ORDERBY子句.对于构造方法,提供别名参数,对于where子句,需要使用addCondition,添加条件,里面可以放各种参数.并且为了优化,设置了重载,只要append传入true,则可以一直将多个addCondition写成一条语句.还有就是添加orderBy属性的.中间的都还比较简单,以及最后的preparePageBean,调用service查询出分页数据,然后放到对象栈中.
public class QueryHelper {
	private String fromClause; // FROM子句
	private String whereClause = ""; // WHERE子句
	private String orderByClause = ""; // ORDERBY子句
	
	private List<Object> parameters = new ArrayList<Object>();
	/**
	 * 生成FROM子句
	 * @param clazz
	 * @param alias
	 */
	public QueryHelper(Class clazz,String alias) {
		fromClause = "FROM " + clazz.getSimpleName() + " " + alias;
	}
	
	/**
	 * 拼接WHERE子句
	 * @param condition
	 * @param param
	 */
	public QueryHelper addCondition(String condition,Object... params) {
		if(whereClause.length() == 0) {
			whereClause = " WHERE " + condition;	
		}else {
			whereClause = whereClause + " AND " + condition;
		}


		if (params != null) {
			for (Object p : params) {
				parameters.add(p);
			}
		}
		
		return this;
	}
	
	/**
	 * 如果第一个参数为true,则拼接WHERE语句
	 * @param append
	 * @param condition
	 * @param params
	 */
	public QueryHelper addCondition(boolean append,String condition,Object... params) {
		if (append) {
			addCondition(condition,params);
		}
		
		return this;
	}
	
	/**
	 * 拼接OrderBy子句
	 * @param propertyName
	 * 			参与排序的属性名
	 * @param asc
	 * 			true,表示升序;false表示降序
	 */
	public QueryHelper addOrderProperty(String propertyName,boolean asc) {
		if(orderByClause.length() == 0) {
			orderByClause = " ORDER BY " + propertyName + (asc ? " ASC":" DESC");
		}else {
			orderByClause += ", " + propertyName + (asc ? " ASC":" DESC");
		}
		return this;
	}
	
	/**
	 * 如果第一个参数为true,则拼接ORDER BY语句
	 * @param append
	 * @param propertyName
	 * @param asc
	 */
	public QueryHelper addOrderProperty(boolean append,String propertyName,boolean asc) {
		if (append) {
			addOrderProperty(propertyName,asc);
		}
		return this;
	}


	/**
	 * 获取生成的用于查询数据列表的HQL语句
	 * @return
	 */
	public String getListQueryHql() {
		return fromClause + whereClause + orderByClause;
	}
	
	/**
	 * 获取生成的用于查询总记录数的HQL语句
	 * @return
	 */
	public String getCountQueryHql() {
		return "SELECT COUNT(*) " + fromClause + whereClause ;
	}


	/**
	 * 获取HQL中的参数值列表
	 * @return
	 */
	public List<Object> getParameters() {
		return parameters;
	}
	
	/**
	 * 查询分页信息,并放到值栈栈顶
	 * @param service
	 * @param pageNum
	 * @param pageSize
	 */
	public void preparePageBean(DaoSupport<?> service,int pageNum,int pageSize) {
		PageBean pageBean = service.getPageBean(pageNum, pageSize, this);
		ActionContext.getContext().getValueStack().push(pageBean);
	}
}
    这样Action中就用QueryHelper就能完成查询分页的功能.不要看这代码这么长,实际只有一句,就是不停的添加各种情况.若viewType为1,就是只显示精华帖,如果viewType为0,显示全部,那就不用条件了.如果orderby为0,1,2,3各种情况,其中默认除了要置顶贴在最前面,还有按最后更新时间降序排列.最后preparePageBean()查询数据,放到对象栈中.
new QueryHelper(Topic.class, "t") 
      //过滤条件
	 .addCondition("t.forum=?", forum)
	 .addCondition(viewType == 1, "t.type=?", Topic.TYPE_BEST)
	  //排序条件
	 .addOrderProperty(orderBy == 1, "t.lastUpdateTime", asc) // 1:'只按最后更新时间排序',
	 .addOrderProperty(orderBy == 2, "t.postTime", asc) // 2:'只按主题发表时间排序',
	 .addOrderProperty(orderBy == 3, "t.replyCount", asc) // 3:'只按回复数量排序'}"
	 .addOrderProperty(orderBy == 0, "(CASE t.type WHEN 2 THEN 2 ELSE 0 END)", false) // 0.默认排序(所有置顶帖在前面,并按最后更新时间降序排列,
	 .addOrderProperty(orderBy == 0, "t.lastUpdateTime", false)
	 .preparePageBean(replyService, pageNum, pageSize);
    而简单的分页,也可以简化成.
//准备分页信息v4 QueryHelper 最终版
new QueryHelper(Reply.class, "r") 
	 .addCondition("r.topic = ?", topic)
	 .addOrderProperty("r.postTime", true)
	 .preparePageBean(replyService, pageNum, pageSize); 
    经过这一系列的优化,现在写一个分页,已经非常简单了.只要引入pageView.jspf,然后在Action中写这么一句就可以了.
    到目前为止,汤阳光的OA视频,全部完成了,总结也全部完成了.至于说的用思维导图和一份大总结,我先暂时失忆了.
    写这份总结,之前对我来说,让我深入了解各种知识不少,到后期就感觉不想写了,但是好在坚持写完了,后期主要觉得收获有点小,而且这样写感觉有些太细了,有些没有重点,不过好在都完成了,一切都不是事了。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值