深入学习jquery源码之上传附件插件的实现

深入学习jquery源码之上传附件插件的实现

/**
 * 上传附件通用JS (基于layerUI)
 */
;(function($){
	var defaults = {
			url : "/attach/upload",		
	    	fieldCode : "aboutDocument",						//业务字段编码
	    	fieldName : "相关文件",								//业务字段名称
	    	entityName : "template",							//业务实体
	};
	
	
	function renderUpload(options){
		this.settings = $.extend({},defaults,options);
		this.settings.elem = "#" + this.settings.fieldCode;
		this.settings.bindAction = "#" + this.settings.fieldCode + "_Action";				//上传操作按钮	
		this.settings.layuiUploadPreview = "#"+ this.settings.fieldCode +"_preview";
		this.settings.previewTableBody = "#"+ this.settings.fieldCode +"_Preview_Table";	//上传预览表格
		this.init();
	}
	//为函数添加原型链上的方法
	renderUpload.prototype = {
		init: function(){
			    var that = this;
				var uploadInst = layui.upload.render({
					    elem: that.settings.elem											//绑定元素
					    ,url: CONTEXT_PATH + that.settings.url 									//上传接口
					    ,accept:'file'
					    ,data:{																	//上传参数
					    	"entityName" : that.settings.entityName,							//业务实体
					    	"fieldCode" : that.settings.fieldCode,								//业务字段编码
					    	"fieldName" : that.settings.fieldName,								//业务字段名称
					    }								
					  	,auto:false												//是否选完文件后自动上传	如果设定 false,那么需要设置 bindAction 参数来指向一个其它按钮提交上传
					  	,size:0													//设置文件最大可允许上传的大小,单位 KB。不支持ie8/9;	0(即不限制)
					  	,multiple:true
					  	//是否允许多文件上传。设置 true即可开启。不支持ie8/9
					  	,bindAction: that.settings.bindAction					//指向一个按钮触发上传,一般配合 auto: false 来使用。值为选择器或DOM对象,如:bindAction: '#btn'
					    ,choose: function(res){									//选择文件后的回调函数
					    	var layIndex = layer.open({
					        	type: 1,
					        	title: "预览",
					        	maxmin: false,
					        	area : ['40%', '40%'],
					        	content:$(that.settings.layuiUploadPreview).removeAttr('style'),			//$.loadHtml(preUrl),
					        	success: function(layero){
					        		var demoListView = $(that.settings.previewTableBody).empty();
					        		var files = that.settings.selfFiles = res.pushFile(); //将每次选择的文件追加到文件队列
					        	      //读取本地文件
					        		res.preview(function(index, file, result){
					        	        var tr = $(['<tr id="upload-'+ index +'">'
					        	          ,'<td>'+ file.name +'</td>'
					        	          ,'<td>'+ (file.size/1024).toFixed(1) +' kb</td>'
					        	          ,'<td name="upload-status">等待上传</td>'
					        	          ,'<td>'
					        	            ,'<button class="layui-btn layui-btn-mini demo-reload layui-hide">重传</button>'
					        	            ,'<button class="layui-btn layui-btn-mini layui-btn-danger demo-delete">删除</button>'
					        	          ,'</td>'
					        	        ,'</tr>'].join(''));
					        	        
					        	        //单个重传
					        	        tr.find('.demo-reload').on('click', function(){
					        	        	res.upload(index, file);
					        	        });
					        	        
					        	        //删除
					        	        tr.find('.demo-delete').on('click', function(){
					        	        	
						        	        delete files[index]; //删除对应的文件
						        	        tr.remove();
						        	        uploadInst.config.elem.next()[0].value = ''; //清空 input file 值,以免删除后出现同名文件不可选
						        	        if(Object.getOwnPropertyNames(files).length == 0){			//当上传预览没有文件时,关闭弹出框
						        	        	 layer.close(layIndex);
						        	        }
					        	        });
					        	        demoListView.append(tr);
					        	      });
					        	},
					        	end : function(){	//layer销毁回调
					        		$(that.settings.layuiUploadPreview).attr('style','display:none;');
					        		$(that.settings.previewTableBody).empty();
					        	},
					        	cancel: function(){ 		  //右上角关闭回调
					        	}
					        });
					    	
					    }
					  	,before: function(res){					//文件提交上传前的回调
					  		$("tr",$(that.settings.previewTableBody)).find("td[name='upload-status']").text("上传中...");
					  	}
					    ,done: function(res, index, upload){					//执行上传请求后的回调。返回三个参数,分别为:res(服务端响应信息)、index(当前文件的索引)、upload(重新上传的方法,一般在文件上传失败后使用)
					    	//上传完毕回调		"dealPicture_list_table"
					    	if(res && res.status == WebConst.SUCCESS){
					    		$(that.settings.bindAction).text("").text("上传成功");
					    		delete that.settings.selfFiles[index];
					    		//改变预览状态,将上传附件id存在	隐藏域中
					    		$("tr[id='upload-"+ index +"']",$(that.settings.previewTableBody)).find("td[name='upload-status']").text("附件缓存完毕,请点击提交上传附件");
					    		var unupload = $("#uploadAttachmentIds").data("uploadAttachmentIds");
					    		if(unupload){
					    			unupload.push(res.data['attachmentId']);
					    			$("#uploadAttachmentIds").data("uploadAttachmentIds",unupload);
					    		}else{
					    			if(res.data){
					    				$("#uploadAttachmentIds").data("uploadAttachmentIds",[res.data['attachmentId']]);
					    				/**
					    				 * 第一个方法赋不了值,通过下面的赋值
					    				 */
					    				$("#uploadAttachmentIds").val([res.data['attachmentId']]);
					    			}
					    		}
					    		
					    		//处理卡片页面列表展示问题
					    		var afile = res.data;
					    		var attachImg = "";
					    		if(!$.isEmptyStr(afile.fileName) && (afile.fileName.indexOf('docx')!=-1 
										 || afile.fileName.indexOf('doc')!=-1))
								 {
									 //attachImg = '<td><img src="' + CONTEXT_PATH +"platform/common/images/word.png" /></td>';
									 attachImg = '<td><img src="'+CONTEXT_PATH+ '/platform/common/images/word.png"' +'width="100" height="60" style="cursor:pointer;"/></td>';
								 }
								 else if(!$.isEmptyStr(afile.fileName) && afile.fileName.indexOf('pdf')!=-1)
								 {
									 attachImg = '<td><img src="'+CONTEXT_PATH+ '/platform/common/images/pdf.png"' +'width="100" height="60" style="cursor:pointer;"/></td>';
								 }
								 else
								 {
									 attachImg = '<td><img src="' + CONTEXT_PATH + '/attach/showPicture?attachmentId=' 
					          		 + afile.attachmentId + '&isThumbnail=true" max-width="120" width="100" height="60" style="cursor:pointer;"/></td>';
								 }
					    		var $tr = $(['<tr id="upload-'+ afile.attachmentId +'" attatchmentId="'+ afile.attachmentId +'">'
							          , attachImg
							          ,'<td>'+ afile.fileName +'</td>'
							          ,'<td>'+ afile.fileSize +' kb</td>'
							          ,'<td>'+ afile.createDate +'</td>'
							          ,'<td>'
							            ,'<a class="layui-btn layui-btn-mini demo-reload" href="'+  CONTEXT_PATH +'/attach/download?attachmentId='+ afile.attachmentId +'">下载</a>&nbsp;&nbsp;'
							            ,'<button class="layui-btn layui-btn-mini layui-btn-danger">删除</button>'
							          ,'</td>'
							        ,'</tr>'].join(''));
								  	$tr.find('button').on('click',function(){
								  		var attId = $(this).closest("tr").attr('attatchmentId');
								  		var url = CONTEXT_PATH + '/attach/delete/datafile';
								  		var rlt = $.getData(url,{'attachmentIds':attId});
										if(rlt && rlt.status == WebConst.SUCCESS){
											  $(this).closest("tr").remove();
											  $("#uploadAttachmentIds").data("uploadAttachmentIds").removeByValue(attId);
										  }
										return false;
								  	});
								  $("#" + afile.fieldCode + "_list_table").append($tr);
					    	}
					    }
				    ,error: function(index, upload){						//执行上传请求出现异常的回调(一般为网络异常、URL 404等)。返回两个参数,分别为:index(当前文件的索引)、upload(重新上传的方法)
				      //请求异常回调
				    }
				});
			}
	}
	
	//扩展jquery类方法
	$.extend({
		renderUpload : function(options){
			return new renderUpload(options);
		}
	});
})(jQuery);


如何使用:

html

		    <@uploadPlugin fieldCode="dealPicture" fieldName="封面图片" editable="${edited}">
					
			</@uploadPlugin>     

freemarker的后台配置

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
	xmlns:context="http://www.springframework.org/schema/context"
	xmlns:mvc="http://www.springframework.org/schema/mvc"
	xmlns:aop="http://www.springframework.org/schema/aop"
	xsi:schemaLocation="http://www.springframework.org/schema/beans  
                        http://www.springframework.org/schema/beans/spring-beans-4.0.xsd  
                        http://www.springframework.org/schema/context  
                        http://www.springframework.org/schema/context/spring-context-4.0.xsd  
                        http://www.springframework.org/schema/mvc  
                        http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd
                        http://www.springframework.org/schema/aop  
                        http://www.springframework.org/schema/aop/spring-aop-4.0.xsd">
    
    <!-- 自动扫描 -->
    <context:component-scan base-package="com.**.controller">
    	<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>  
        <context:include-filter type="annotation" expression="org.springframework.web.bind.annotation.ControllerAdvice"/>  
    </context:component-scan>  
    
<!--     <mvc:annotation-driven /> -->
    
    <!-- 静态资源  开始 -->
    <mvc:resources location="/404Error.html" mapping="/404Error.html"/>
    <mvc:resources location="/view/**" mapping="/view/**"/>
    <mvc:resources location="/application/**" mapping="/application/**"/>
    <mvc:resources mapping="/platform/**" location="/platform/,classpath:/platform/" />
    <mvc:resources mapping="/favicon.ico" location="/favicon.ico,classpath:/favicon.ico" />
	<!-- 静态资源  结束 -->
	<mvc:interceptors>
		<mvc:interceptor>
			<mvc:mapping path="/**" />
			<mvc:exclude-mapping path="/" />
			<mvc:exclude-mapping path="/login" />
			<mvc:exclude-mapping path="/loginCheck" />
			<mvc:exclude-mapping path="/view/**" />
			<mvc:exclude-mapping path="/platform/**" />
			<mvc:exclude-mapping path="/application/**" />
		</mvc:interceptor>
	</mvc:interceptors>
	
	<!-- jsp 视图解析-->
	<bean id="jspResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
		<!-- 这里的配置我的理解是自动给后面action的方法return的字符串加上前缀和后缀,变成一个 可用的url地址 -->
		<property name="contentType" value="text/html;charset=UTF-8"/>
		<property name="prefix" value="/" />
		<property name="suffix" value=".jsp" />
		<property name="order" value="1"/> 
	</bean>
	
	<!-- freemarker的配置 -->
	<bean id="freemarkerConfigurer" class="org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer">
		<property name="templateLoaderPaths">
			<list>
				<value>/view</value>
			</list>
		</property>
		<property name="defaultEncoding" value="UTF-8" />
		<property name="freemarkerVariables">  
	        <map>  
	            <entry key="downSelector" value-ref="downSelectorDirective"/>  
	            <entry key="checkBox" value-ref="checkBoxDirective"/>  
	            <entry key="redioBox" value-ref="redioBoxDirective"/> 
	            <entry key="popSelector" value-ref="popSelectorDirective"/> 
	            <entry key="uploadPlugin" value-ref="uploadPluginDirective"/> 	            
	        </map>  
	    </property>
		<property name="freemarkerSettings">
			<props>
				<prop key="template_update_delay">10</prop>
				<prop key="locale">zh_CN</prop>
				<prop key="datetime_format">yyyy-MM-dd HH:mm:ss</prop>
				<prop key="date_format">yyyy-MM-dd</prop>
				<prop key="number_format">#.##</prop>
			</props>
		</property>
	</bean>
	
	<bean id="freemarkerResolver" class="org.springframework.web.servlet.view.freemarker.FreeMarkerViewResolver">
		<property name="cache" value="true" />
		<property name="suffix" value=".html" />
		<property name="order" value="0"/>
		<property name="contentType">
			<value>text/html;charset=UTF-8</value>
		</property>
		<property name="viewClass">
			<value>com.framework.web.view.CntenFreeMarkerView</value>
		</property>
		<property name="requestContextAttribute" value="rc"></property>
	</bean>
	
	<!-- 配置文件上传,如果没有使用文件上传可以不用配置,当然如果不配,那么配置文件中也不必引入上传组件包 -->
	<bean id="multipartResolver"  
        class="org.springframework.web.multipart.commons.CommonsMultipartResolver">  
        <!-- 默认编码 -->
        <property name="defaultEncoding" value="UTF-8" />
        <!-- 文件大小最大值 -->
        <property name="maxUploadSize" value="10485760000" />
        <!-- 内存中的最大值 -->
        <property name="maxInMemorySize" value="40960" />
    </bean>
</beans>
import java.io.IOException;
import java.io.Writer;
import java.util.Map;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import freemarker.core.Environment;
import freemarker.template.SimpleScalar;
import freemarker.template.TemplateDirectiveBody;
import freemarker.template.TemplateDirectiveModel;
import freemarker.template.TemplateException;
import freemarker.template.TemplateModel;

/**
 * 附件上传组件
 * @author Administrator
 */
@Component
public class UploadPluginDirective implements TemplateDirectiveModel{
	@Override
	public void execute(Environment env, @SuppressWarnings("rawtypes") Map params, TemplateModel[] loopVars, 
			TemplateDirectiveBody body) throws TemplateException, IOException {
		
		SimpleScalar fieldCode = (SimpleScalar) params.get("fieldCode"); 				//上传附件编码
		SimpleScalar fieldName = (SimpleScalar) params.get("fieldName"); 				//上传附件名称
		SimpleScalar editable = (SimpleScalar) params.get("editable");
		
		Writer out = env.getOut();
		
		if (fieldCode != null && fieldName != null) {
			StringBuilder builder = new StringBuilder();
			builder.append("<!-- 文件上传 开始 -->");
			builder.append("<div class='layui-form-item layui-form-text'>")
				   .append("    <label class='layui-form-label fwb'>").append(fieldName).append("</label>")
				   .append("    <div class='layui-input-block'>");
			
			if(editable != null && StringUtils.isEmpty(editable.getAsString())) {
				builder.append("        <button type='button' class='layui-btn' id='").append(fieldCode).append("'>")
					   .append("            <i class='layui-icon'>&#xe67c;</i>上传图片")
					   .append("        </button>");
			}
			
			builder.append("        <div class='layui-upload' id='").append(fieldCode).append("_list'>")
				   .append("            <div class='layui-upload-list'>")
				   .append("                <table class='layui-table'>")
				   .append("                    <thead>")
				   .append("                        <tr><th width='25%'>缩略图</th>")
				   .append("                            <th width='30%'>文件名</th>")
				   .append("                            <th width='10%'>文件大小</th>")
				   .append("                            <th width='25%'>上传时间</th>")
				   .append("                            <th width='20%'>操作</th>")
				   .append("                        </tr></thead>")
				   .append("                     <tbody id='").append(fieldCode).append("_list_table'></tbody>")
				   .append("                 </table>")
				   .append("             </div>")
				   .append("         </div>")
				   .append("    </div>")
				   .append("</div>")
				   .append("<!-- 文件预览 开始 -->")
				   .append("<div class='layui-upload' id='").append(fieldCode).append("_preview'").append(" style='display:none;'>")
				   .append("    <div class='layui-upload-list'>")
				   .append("        <table class='layui-table'>")
				   .append("            <thead>")
				   .append("                <tr><th>文件名</th>")
				   .append("                    <th>大小</th>")
				   .append("                    <th>状态</th>")
				   .append("                    <th>操作</th>")
				   .append("                </tr></thead>")
				   .append("            <tbody id='").append(fieldCode).append("_Preview_Table'").append(" ></tbody>")
				   .append("       </table>")
				   .append("    </div>")
				   .append("    <button type='button' class='layui-btn' id='").append(fieldCode).append("_Action' ").append(">开始上传</button>")
				   .append("</div> ")
				   .append("<!-- 文件预览 结束 -->")
				   .append("<!-- 文件上传 结束 -->");
			out.write(builder.toString());
			
		}else {
			out.write("freemarker自定义命令初始化参数缺失...");
		}
		
		if(body != null) {
			body.render(out);
		}else {
			throw new RuntimeException("freemarker自定义标签内部至少要加一个空格");
		}
	}

}

js 执行实例调用附件上传

                 //执行实例
		$.renderUpload({
			fieldName : "上传附件",							//业务字段名称
	    	fieldCode : "dealPicture",						//业务字段编码
	    	entityName : "add",						//业务实体
	    	selfFiles : _self.files
		});


   
var ShowNews = CardPager.extend({
	init: function(filter, url){
		this._super(filter, url);
		//界面需要引用的插件
		this.plugins = ['element', 'form', 'layedit', 'upload', 'laydate', 'layer'];
		this.primaryKey = "newsId";
		this.primaryValue = $.QueryString("entityId");
		this.dataUrl = CONTEXT_PATH + '/gasNews/get';
		this.entityName = "news";
	},
	initLayuiPlugin: function(){
		this._super(); 
		var _self = this;
		_self.files = null;
		
		//执行实例
		$.renderUpload({
			fieldName : "上传附件",							//业务字段名称
	    	fieldCode : "dealPicture",						//业务字段编码
	    	entityName : "news",						//业务实体
	    	selfFiles : _self.files
		});
	},
	initEvent: function(){
		this._super();
		var _self = this;
		//TODO 父类只定义了 保存事件,其他事件在这里进行定义
	},
	initThirdPlugin: function(){
		var _self = this;
		KindEditor.ready(function(K) {
			_self.gasNewsContent = K.create('#newsContent');
		});
	}
});

$(function(){
	showNews = new ShowGasNews('news', CONTEXT_PATH + '/news/save');
	showNews.render();
	//关闭卡片页面之前回调
	function beforeClose (){
		//如果涉及到文件上传,关闭卡片页面将未上传的附件进行删除
		var unuploadFiles = $("#uploadAttachmentIds").data("uploadAttachmentIds");
		if(unuploadFiles && unuploadFiles.length>0){
			var url = CONTEXT_PATH + '/attach/delete/datafile';
			$.getData(url,{'attachmentIds':unuploadFiles.join(",")});
		}
	}
	
});

查看图片回显

(function(scope){
	var CardPager = Class.extend({
		init: function(filter, url){
			this.filter = filter;
			this.url = url;
		},
		render: function(){
			var _self = this;
			_self.initThirdPlugin(); 		//初始化第三放插件
			
			//plugins 这个可以不传,但是请不要传空数组过来
			var plugins = _self.plugins || ['form'];
			layui.use(plugins,function(){
				_self.initEvent();
				_self.initLayuiPlugin();	//初始化layui组件		
				//渲染表单数据
				var params = {};
					params[_self.primaryKey] = _self.primaryValue;
				$.ajaxReq(_self.dataUrl, params, function(data){
					if(data && data.status == WebConst.SUCCESS && data.data){
						var cardForm = $("#"+_self.filter+"Form"),
							formDoms = cardForm[0];
						cardForm.setValues(data.data);
						//处理富文本赋值问题
						for(i=0,len=formDoms.length; i<len; i++){
							var idom = formDoms[i],
								idomName = idom.name,
								tagName = idom.tagName,
								idomId = idom.id,
								keditor = _self[idomName];
							if(idom.tagName == "TEXTAREA" && keditor){
								keditor.html(data.data[idomName]);
							}
						}	
						layui.form.render();
					}
				},WebConst.AJAX_FORM,null,false);
				
				
			});
			
			_self.initFiles();
		},
		//附件上传查看图片回显
		initFiles: function(){
			var _self = this,
				attachParam = {};
				attachParam.entityId = _self.primaryValue;
				attachParam.entityName = _self.entityName;
			$.ajaxReq(CONTEXT_PATH + "/attach/entity/files", attachParam, function(data){
				if(data && data.status == WebConst.SUCCESS && data.data){
					var files = data.data;
					$("div[class='layui-upload']").each(function(index,obj){
						var containerId = $(obj).attr('id'),
							attachField = containerId.split("_")[0],
							tableBody = containerId + "_table";
						for(var i=0,len=files.length; i<len; i++){
							var cFile = files[i];
							if(cFile.fieldCode == attachField){
								 var $tr = $(['<tr id="upload-'+ cFile.attachmentId +'" attatchmentId="'+ cFile.attachmentId +'">'
							          ,'<td><img src="' + CONTEXT_PATH + '/attach/showPicture?attachmentId=' 
							          		 + cFile.attachmentId + '&isThumbnail=true" width="100" height="60" style="cursor:pointer;"/></td>'
							          ,'<td>'+ cFile.fileName +'</td>'
							          ,'<td>'+ cFile.fileSize +' kb</td>'
							          ,'<td>'+ cFile.createDate +'</td>'
							          ,'<td>'
							            ,'<a class="layui-btn layui-btn-mini demo-reload" href="'+  CONTEXT_PATH +'/attach/download?attachmentId='+ cFile.attachmentId +'">下载</a>&nbsp;&nbsp;'
							            ,'<button class="layui-btn layui-btn-mini layui-btn-danger">删除</button>'
							          ,'</td>'
							        ,'</tr>'].join(''));
								  $tr.find('button').on('click',function(){
									  var rlt = $.getData(CONTEXT_PATH + "/attach/delete/updflag",{'attachmentId':$(this).closest("tr").attr('attatchmentId')});
									  if(rlt && rlt.status == '1000'){
										  $(this).closest("tr").remove();
									  }
									  return false;
								  });
								 $("#" + tableBody).append($tr);
							}
						}
					})
				}
			},WebConst.AJAX_FORM,null,false);
		},
		initThirdPlugin: function(){
			
		},
		initLayuiPlugin: function(){
			var _self = this;
		},
		initEvent: function(){
			var _self = this;
			layui.form.on('submit(' + _self.filter + ')', function(data){
				//处理 	KindEditor 富文本取值问题
				var formDoms = data["form"];
				for(i=0,len=formDoms.length; i<len; i++){
					var idom = data["form"][i],
						idomName = data["form"][i].name,
						tagName = idom.tagName,
						idomId = data["form"][i].id,
						keditor = _self[idomName];
					if(idom.tagName == "TEXTAREA" && keditor){
						keditor.sync();
						data.field[idomName] = $('#'+idomId ).val();	// jQuery
					}
				}
				
				var retData = $.getData(_self.url, data.field);
				if(retData.status == WebConst.FAIL){
					layer.msg('保存失败', {icon: 5});
					return false;
				}
				//如果有附件,上传附件
				var uploadAttachmentIds = $("#uploadAttachmentIds").data("uploadAttachmentIds"),
					entityId = retData.data;
					$("input[name='"+ _self.primaryKey +"']").val(retData.data);
				if(true && uploadAttachmentIds && entityId){
					var params = {};
						params.attachmentIds = uploadAttachmentIds.join(",");
						params.entityId = entityId;
					$.getData(CONTEXT_PATH + "/attach/updateEntityId", params);
				}
				 $("#uploadAttachmentIds").data("uploadAttachmentIds",null);
				layer.msg('保存成功', {icon: 6});
				return false;
			});
		}
	});
	scope.CardPager = CardPager;
})(window);

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

wespten

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值