已阅读5页,还剩11页未读, 继续免费阅读
版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
技术文档模板 华中师范大学教育信息技术工程研究中心一、简介Velocity是一个基于java的模板引擎(template engine)。它允许任何人仅仅简单的使用模板语言(template language)来引用由java代码定义的对象。当Velocity应用于web开发时,界面设计人员可以和java程序开发人员同步开发一个遵循MVC架构的web站点,也就是说,页面设计人员可以只关注页面的显示效果,而由java程序开发人员关注业务逻辑编码。Velocity将java代码从web页面中分离出来,这样为web站点的长期维护提供了便利,同时也为我们在JSP和PHP之外又提供了一种可选的方案。Velocity的能力远不止web站点开发这个领域,例如,它可以从模板(template)产生SQL和 PostScript、XML,它也可以被当作一个独立工具来产生源代码和报告,或者作为其他系统的集成组件使用。Velocity也可以为 Turbine web开发架构提供模板服务(template service)。Velocity+Turbine提供一个模板服务的方式允许一个web应用以一个真正的MVC模型进行开发。二、基本语法1 总括1.1 #用来标识Velocity 的脚本语句,包括#set、#if 、#else、#end、#foreach、#end、#iinclude、#parse、#macro 等,例如:#if($info.imgs)#else#end1.2 $用来标识一个对象(或理解为变量);如$i、$msg、$TagUtil.options(.)等。 1.3 用来明确标识Velocity 变量。比如在页面中,页面中有一个$someonename,此时,Velocity 将把someonename 作为变量名,若我们程序是想在someone 这个变量的后面紧接着显示name 字符,则上面的标签应该改成$someonename。1.4 !用来强制把不存在的变量显示为空白。如当页面中包含$msg,如果msg 对象有值,将显示msg 的值,如果不存在msg 对象同,则在页面中将显示$msg 字符。这是我们不希望的,为了把不存在的变量或变量值为null 的对象显示为空白,则只需要在变量名前加一个“!”号即可。如:$!msgd1 变量1.1 变量的定义Veloctiy是一种弱类型的语言,语法结构非常简单,但在jetspeed却非常实用。因为是一种弱类型语言,所以不用定义变量的类型,编译器根据赋值的类型自动进行判断,定义的关键字为set,set前要加#,变量名前必须加$,第一个字符必须为字母。变量可以包含的字符有以下内容:alphabetic (a . z, A . Z),numeric (0 . 9),hyphen (-),underscore (_)。当使用#set指令时,括在双引号中的字面字符串(string literal)将解析和重新解释,但是,当string literal被封装在单引号内时,它将不被解析。 上面这个特性可以通过修改perties文件的erpolate = false的值来改变上面的特性是否有效。#set($directoryRoot = www )#set($templateName = index.vm )#set($template = $directoryRoot/$templateName )$template#set ( $foo = “bar” )$foo#set ( $blargh = $foo )$blargh输出将会是:www/index.vm结果:bar$foo注:在velocity中使用$2.5这样的货币标识是没有问题得的,因为velocity中的变量总是以 $ 开头,第一个字符必须为字母。1.2 变量规范的写法(正式格式)$name,也可以写成:$name。提倡用前面的写法。例如:你希望通过一个变量$vice来动态的组织一个字符串。Jack is a $vicemaniac.本来变量是$vice现在却变成了$vicemaniac,这样Veloctiy就不知道您到底要什么了。所以,应该使用规范的格式书写 : Jack is a $vicemaniac现在Velocity知道变量是$vice而不是$vicemaniac。注意:当引用属性的时候不能加$与$!的区别:当找不到username的时候,$username返回字符串$username,而$!username返回空字符串。双引号与引号:#set($var=hello)test$var返回testhellotest$var返回test$var可以通过设置erpolate=false改变默认处理方式1.3 变量的赋值变量名和值的组合要放在小括号中,不必用分号结束语句。例如:#set($maxValue=5)#set($name=Bob)赋值的左边必须是一个变量或者是属性引用。右边可以是下面六种类型之一:变量引用,字面字符串,属性引用,方法引用,字面数字,数组列表。支技持算术运算符。下面的例子演示了上述的每种类型:#set($monkey=$bill)#variable reference#set($monkey.Friend=monica)#string#set($monkey.Blame=$whitehouse.Leak) #property reference#set($monkey.Plan=$spindoctor.weave($web) #method reference#set($monkey.Number=123) #number#set($monkey.Say=Not,$my,fault) #ArrayList注意:最后一个例子的取值方法为:$monkey.Say.get(0)算术表达式:#set ( $value = $foo + 1 )#set ( $value = $bar -1 )#set ( $value = $foo * $bar )#set ( $value = $foo / $bar )注意:如果上述例子中的右值是null, 则左值不会被赋值,也就是说会保留以前的值。在下面的例子中,左边的程序将不能智能的根据$result的值决定查询是否成功。在$result被#set后(added to the context),它不能被设置回null(removed from the context)。打印的结果将显示两次查询结果都成功了,但是实际上有一个查询是失败的。为了解决以上问题可以通过预先定义的方式,如右边程序:有问题的:修改后的:#set( $criteria = name, address )#foreach( $criterion in $criteria )#set( $result = $query.criteria($criterion) )#if( $result )Query was successful#end#end#set( $criteria = “name”, “address” )#foreach( $criterion in $criteria )#set( $result = false )#set( $result = $query.criteria( $criterion ) )#if( $result ) Query was successful#end#endvelocity模板中未被定义的变量将被认为是一个字符串。例如:#set($foo=gibbous)$moon=$foo输出结果为:$moon=gibbousvelocity模板中不会将reference解释为对象的实例变量。例如:$foo.Name将被解释为Foo对象的getName()方法,而不是Foo对象的Name实例变量。例如:$foo.getBar() 等同于$foo.Bar ;$data.getUser(jon) 等同于$data.User(jon) ;data.getRequest().getServerName() 等同于$data.Request.ServerName等同于$data.Request.ServerName2 属性$customer.Address$hashtable user中的name值。类似:user.get(name)$customer.Address有两种含义。它可以表示:查找hashtable对象customer中以Address为关键字的值;也可以表示调用customer对象的getAddress()方法。当你的页面被请求时,Velocity将确定以上两种方式选用那种,然后返回适当的值。正式格式:$customer.Address 3 方法object user.getName() = $user.getName()一个方法就是被定义在java中的一段代码,并且它有完成某些有用工作的能力,例如一个执行计算和判断条件是否成立、满足等。方法是一个由$开始并跟随VTL标识符组成的References,一般还包括一个VTL方法体。例如:$customer.getAddress()$purchase.getTotal()$page.setTitle( “My Home Page” )$person.setAttributes( “Strange”, “Weird”, “Excited” )前两个例子$customer.getAddress()和$purchase.getTotal()看起来挺想上面的属性$customer.Address 和 $purchase.Total。如果你觉得他们之间有某种联系的话,那你是正确的。VTL属性可以作为VTL方法的缩写。$customer.Address属性和使用$customer.getAddress()方法具有相同的效果。如果可能的话使用属性的方式是比较合理的。属性和方法的不同点在于你能够给一个方法指定一个参数列表。正式格式:$purchase.getTotal()4 Range OperatorRange operator可以被用于与#set和#foreach statement联合使用。对于处理一个整型数组它是很有用的,Range operator具有以下构造形式:n.m。m和n都必须是整型,而m是否大于n则无关紧要。例子:输出结果为:#foreach ( $foo in 1.5 ) $foo#end1 2 3 4 5#foreach($bar in 2.-2)$bar#end2 1 0 -1 -2#set($arr=0.1)#foreach($i in $arr)$i#end0 1注意:range operator只在#set和#foreach中有效。2 循环#foreach ($element in $list) This is $element. $velocityCount #end例子:输出的结果为:#set( $list = pine, oak, maple)#foreach ($element in $list)$velocityCountThis is $element.#end1 This is pine.2 This is oak.3 This is maple.每次循环$list中的一个值都会赋给$element变量。$list可以是一个Vector、Hashtable或者Array。分配给$element的值是一个java对象,并且可以通过变量被引用。例如:如果$element t是一个java的Product类,并且这个产品的名字可以通过调用他的getName()方法得到。现在我们假设$list是一个Hashtable,如果你希望得到它的key应该像下面这样:#foreach ( $key in $list.keySet()Key: $key - Value: $list.get($key) #end注意:velocity中大小写敏感。Velocity还特别提供了得到循环次数的方法,$velocityCount变量的名字是Velocity默认的名字。可以通过设置 = velocityCount来改变。计数从“1”开始,但是可以在perties设置是从“1”还是从“0”开始:directive.foreach.counter.initial.value = 1。例子:输出结果为:#foreach ( $foo in 1.5 ) $foo#end1 2 3 4 5#foreach($bar in 2.-2)$bar#end2 1 0 -1 -2#set($arr=0.1)#foreach($i in $arr)$i#end0 13 条件语句#if ($foo)#elseif (condition)#else#end当$foo为null或为Boolean对象的false值执行。这里需要注意一点:Velocity context仅仅能够包含对象,所以当我们说“boolean”时实际上代表的时一个Boolean对象。即便某个方法返回的是一个boolean值,Velocity也会利用内省机制将它转换为一个Boolean的相同值。if的例子#set($name=mary)#set($sex=female)#set($age=20)#set($coutry=America) #if($name=mary) hello mary!#end #if($sex=male) You are a boy!#else You are a girl!#end#if($age12 & $age18) You are not an adult!#else You are an adult!#end #if($country!=China) #if($country=Amemica) You are from America! #end#else #if($city=Beijing) You are from Beijing! #else You are not from Beijing! #end#end4 语句的嵌套#foreach($element in $list) #foreach($element in $list) #inner foreach内循环This is $element.$velocityCountinner#end # inner foreach内循环结束This is $element # outer foreach$velocityCountouter#end语句中也可以嵌套其他的语句,如#if#else#end等。5 注释5.1 单行注释: #这是一个注释:定义名字为mike5.2 多行注释: #* Thus begins a multi-line comment. Online visitors wont see this text because the Velocity Templating Engine will ignore it. *#5.3 文档格式: #* This is a VTL comment block and may be used to store such information as the document author and versioning information: version 1.1 author xiao *#6 关系和逻辑操作符Velocity具有逻辑AND(&), OR(|) 和 NOT(!) 操作符。# example for AND#if($foo & $bar) This AND that#end例子中#if() 指令仅在$foo 和$bar都为真的时候才为真。如果$foo 为假,则表达式也为假;并且 $bar 将不被求值(类似C语言)。如果$foo为真,Velocity 模板引擎将继续检查$bar的值,如果$bar为真,则整个表达式为真。并且输出This AND that 。如果 $bar为假,将没有输出因为整个表达式为假。7 Velocity 中的宏Velocity中的宏我们可以理解为函数。Velocimacros可以在Velocity模板内实现行内定义(inline),也就意味着同一个web site内的其他Velocity模板不可以获得Velocimacros的定义。定义一个可以被所有模板共享的Velocimacro显然是有很多好处的:它减少了在一大堆模板中重复定义的数量、节省了工作时间、减少了出错的几率、保证了单点修改。定义模板共享的Velocimacro在perties中定义,它可以被多次应用并且可以应用于不同的目的。宏的定义宏的调用#macro(宏的名称 $参数1 $参数2 ) 语句体(即函数体)#end#宏的名称($参数1 $参数2 )说明:参数之间用空格隔开。当将一个reference作为参数传递给Velocimacro时,请注意reference作为参数时是以名字的形式传递的。这就意味着参数的值在每次Velocimacro内执行时才会被产生。这个特性使得你可以将一个方法调用作为参数传递给Velocimacro,而每次Velocimacro执行时都是通过这个方法调用产生不同的值来执行的。例如:#macro ( callme $a )$a $a $a#end#callme( $foo.bar() )执行的结果是:reference $foo的bar()方法被执行了三次。如果不需要这样的特性#set ( $myval = $foo.bar() )#callme ( $myval )Velocimacro properties中有关宏的一些配置项: velocimacro.libraary:一个以逗号分隔的模板库列表。默认情况下,velocity查找唯一的一个库:VM_global_library.vm。你可以通过配置这个属性来指定自己的模板库。 velocimacro.permissions.allow.inline属性:有两个可选的值true或者false,通过它可以确定 Velocimacros是否可以被定义在regular template内。默认值是ture允许设计者在他们自己的模板中定义Velocimacros。 velocimacro.permissions.allow.inline.replace.global属性有两个可选值true和false,这个属性允许使用者确定inline的Velocimacro定义是否可以替代全局Velocimacro定义(比如在 velocimacro.library属性中指定的文件内定义的Velocimacro)。默认情况下,此值为false。这样就阻止本地 Velocimacro定义覆盖全局定义。 velocimacro.permssions.allow.inline.local.scale属性也是有true和false两个可选值,默认是 false。它的作用是用于确定你inline定义的Velocimacros是否仅仅在被定义的template内可见。换句话说,如果这个属性设置为 true,一个inline定义的Velocimacros只能在定义它的template内使用。你可以使用此设置实现一个奇妙的VM敲门:a template can define a private implementation of the second VM that will be called by the first VM when invoked by that template. All other templates are unaffected。 velocimacro.context.localscope属性有true和false两个可选值,默认值为false。当设置为true时,任何在 Velocimacro内通过#set()对context的修改被认为是针对此velocimacro的本地设置,而不会永久的影响内容。 velocimacro.library.autoreload属性控制Velocimacro库的自动加载。默认是false。当设置为ture时,对于一个Velocimacro的调用将自动检查原始库是否发生了变化,如果变化将重新加载它。这个属性使得你可以不用重新启动servlet容器而达到重新加载的效果,就像你使用regular模板一样。这个属性可以使用的前提就是resource loader缓存是off状态(file.resource.loader.cache = false)。注意这个属性实际上是针对开发而非产品的。Velocimacro 细节:Velocimacro必须被定义在他们被使用之前。也就是说,你的#macro()声明应该出现在使用Velocimacros之前。特别要注意的是,如果你试图#parse()一个包含#macro()的模板。因为#parse()发生在运行期,但是解析器在parsetiem决定一个看似VM元素的元素是否是一个VM元素,这样#parse()-ing一组VM声明将不按照预期的样子工作。为了得到预期的结果,只需要你简单的使用velocimacro.library使得Velocity在启动时加载你的VMs。8 #stop 停止执行模板引擎并返回,把它应用于debug是很有帮助的。9 #include与#parse#include和#parse的作用都是引入本地文件, 为了安全的原因,被引入的本地文件只能在TEMPLATE_ROOT目录下。你可以通过修改perties文件的parse_direcive.maxdepth的值来控制一个template可以包含的最多#parse的个数默认值是10。区别: 与#include不同的是,#parse只能指定单个对象。而#include可以有多个如果您需要引入多个文件,可以用逗号分隔就行:#include (one.gif,two.txt,three.htm )在括号内可以是文件名,但是更多的时候是使用变量的:#include ( “greetings.txt”,$seasonalstock )。 #include被引入文件的内容将不会通过模板引擎解析;而#parse引入的文件内容Velocity将解析其中的velocity语法并移交给模板,意思就是说相当与把引入的文件copy到文件中。#parse是可以递归调用的,例如: dofoo.vm包含parsefoo.vm模板包含结果显示Countdown.#set($count=8)#parse(parsefoo.vm)All done with dofoo.vm!$count#set($count = $count - 1)#if ( $count 0 )#parse( parsefoo.vm )#elseAll done with parsefoo.vm!#endCount down876543210All done with parsefoo.vm!All done with dofoo.vm!注意:在vm中使用#parse来嵌套另外一个vm时的变量共享问题。如:a.vm 里嵌套 b.vm,a.vm 里定义了变量 $param,b.vm 里可以直接使用$param,无任何限制。但需要特别注意的是,如果b.vm里同时定义有变量$param,则b.vm里将使用b.vm里定义的值。10 转义字符的使用如果reference被定义,两个意味着输出一个,如果未被定义,刚按原样输出。如:如果$email定义如果$email 未定义#set($email = foo )输出:输出:$emailfoo$email$email$email$email$email$email$emailfoo$email$email$email$email$email$email11 内置对象Velocity内置了一些对象,在vm模版里可以直接调用,列举如下:$request、$response、$session。另外,模板内还可以使用 $msg内的消息工具访问 Struts 的国际化资源,达到简便实现国际化的方法。12 数组访问对数组的访问在Velocity 中存在问题,因为Velocity只能访问对象的方法,而数组又是一个特殊的Array,所以虽然数组可以进行循环列举,但却不能定位访问特定位置的元素,如 strs2,数组对固定位置元素的访问调用了Array的反射方法get(Object array, int index),而Velocity没能提供这样的访问,所以数组要么改成List等其他类容器的方式来包装,要么就通过公用Util类的方式来提供,传入数组对象和要访问的位置参数,从而达到返回所需值的目的。三、Velocity Template Language(VTL)VTL意味着提供最简单、最容易并且最整洁的方式合并页面动态内容。VTL使用references来在web site内嵌套动态内容,一个变量就是一种类型的reference。变量是某种类型的refreence,它可以指向java代码中的定义,或者从当前页面内定义的VTL statement得到值。下面是一个VTL statement的例子,它可以被嵌套到HTML代码中:#set($a = “Velocity”)和所有的VTL statement一样,这个statement以字符开始并且包含一个directive:set。当一个在线用户请求你的页面时,Velocity Templating Engine将查询整个页面以便发现所有字符,然后确定哪些是VTL statement,哪些不需要VTL作任何事情。字符后紧跟一个directive:set时,这个set directive使用一个表达式(使用括号封闭)一个方程式分配一个值给变量。变量被列在左边,而它的值被列在右边,最后他们之间使用号分割。在上面的例子中,变量是$a,而它的值是Velocity。和其他的references一样以$字符开始,而值总是以双引号封闭。一旦某个变量被分配了一个值,那么你就可以在HTML文件的任何地方引用它。在下面的例子中,一个值被分配给$foo变量,并在其后被引用。#set ( $foo = “Velocity” )Hello $foo World!结果是在页面上打印“Hello Velocity World!”为了使包含VTL directives的statement更具有可读性,我们鼓励你在新行开始每个VTL statement,尽管你不是必须这么作。规则:使用$字符开始的references用于得到什么;使用#字符开始的directives用于作些什么。四 Velocimacro杂记1、Q:Can I user a directive or another VM as an argument to a VM?例如:#center ( #bold( “hello” ) )A:不可以。一个directive的参数使用另外一个directive是不合法的。但是,还是有些事情你可以作的。最简单的方式就是使用双引号:#set($stuff=“#bold(hello)”)缩写为: #center ( “#bold( hello ) )#center($stuff)2、请注意在下面的例子中参数被定义在Velocimacro内部,而不是在calling level。#macro ( inner $foo )inner : $foo#end#macro ( outer $foo )#set ( $bar = “outerlala” )outer : $foo#end#set ( $bar = calltimelala )#outer( “#inner($bar)” )输出结果为:outer : inner : outerlala3、can I register velocimacros via #parse()?目前,Velocimacros必须在第一次被模板调用前被定义。这就意味着你的#macro()声明应该出现在使用Velocimacros之前。如果你试图#parse()一个包含#macro() directive的模板,这一点是需要牢记的。因为#parse()发生在运行期,但是解析器在parsetime决定一个看似VM元素的元素是否是一个VM元素,这样#parse()-ing一组VM声明将不按照预期的样子工作。为了得到预期的结果,只需要你简单的使用 velocimacro.library使得Velocity在启动时加载你的VMs。4、What is velocimacro autoreloading?velocimacro.library.autoreload是专门为开发而非产品使用的一个属性。此属性的默认值是false。5、字符串连接开发人员最常问的问题是我如何作字符拼接?在java中是使用“”号来完成的。在VTL里要想实现同样的功能你只需要将需要联合的reference放到一起就行了。例如:#set ( $size = “Big” )#set ( $name = “Ben” )The clock is $size$name.#set ( $size = “Big” )#set ( $name = “Ben” )#set ( $clokc = “$size$name” )The clock is $clock.#set ( $size = “Big” )#set ( $name = “Ben” )#set ( $clock = “$sizeTall$name” )The clock is $clock.结果都将是:The clock is BigBen.结果是:The clock is BigTallBen.二、示例部分1 Hello world的示例代码:(1)Velocity模板(hello.html) New Document hello,$name! (注意:这里的name与VelocityTest.java中的名称要一致)16文档编号:07000-V1(2)将velocity模板的内容转换的类(VelocityTest.java)import java.io.File;import java.io.FileOutputStream;import java.io.PrintWriter;import java.io.Writer;import org.apache.velocity.Template;import org.apache.velocity.VelocityContext;import org.apache.velocity.app.Velocity;import org.apache.velocity.app.VelocityEngine;/* * Velocity转换 * author*/public class VelocityTest /* * 主函数 * param args */ public static void main(String args) VelocityEngine ve = new VelocityEngine(); /获取模板引擎 String path = D:/java/jproject/regedit/webroot; /模板文件所在的路径 ve.setProperty(Velocity.FILE_RESOURCE_LOADER_PATH, path); /设置参数 ve.setProperty(Velocity.INPUT_ENCODING,GBK); /处理中文问题 ve.setProperty(Velocity.OUTPUT_ENCODING,GBK); /处理中文问题 try ve.init(); /初始化模板 Template template = ve.getTemplate(hello.html); /获取模板(hello.html) VelocityContext root = new VelocityContext(); /获取上下文 root.put(name,world); /把数据填入上下文 (注意:与上面的对应) Strint outpath = e:/helloworld.html; /输出路径 Writer mywriter = new PrintWriter(new FileOutputStream(new File(outpath); /以下是输出 template.merge(root, mywriter); mywriter.flush(); catch (Exception e) e.printStackTrace(); (3)环境的搭建在lib目录内分别copy进:velocity-1.4.jar,velocity-dept.jar;下载地址:/velocity/(4)运行后的结果如下: New Document hello,world!2 Servlet和Velocity结合示例(1)example.html Velocity Welcom to Velocity! Heres the list of people Names: #foreach ($name in $theList) $name #end (2)servletpackage com.koal.velocity;import java.io.IOException;import java.io.FileNotFoundException;import java.util.ArrayList;import java.util.Properties;import java.util.Vector;import javax.servlet.ServletConfig;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import org.apache.velocity.Template;import org.apache.velocity.context.Context;import org.apache.velocity.servlet.VelocityServlet;import org.apache.velocity.app.Velocity;import org.apache.velocity.exception.ResourceNotFoundException;import org.apache.velocity.exception.ParseErrorException;public class SampleServlet extends VelocityServlet /* * 由VelocityServlet.init()调用, * 在此找出模版的路径 */protected Properties loadCo
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 房地产三方合作合同书合同
- 完善医院药品采购制度
- 冻品采购管理制度
- 学校食堂采购验收检制度
- 公司线上采购管理制度
- 成本采购部规章制度
- 浙江省杭州二中2025学年第二学期高三年级三月月考技术+答案
- 数字化转型下Y公司内部控制系统的优化与创新研究
- 2025 奇妙的植物光合作用原理讲解作文课件
- 数字化转型下AAAH公司成本管理优化策略研究
- DB11∕T 1567-2018 森林疗养基地建设技术导则
- 食堂购买蔬菜合同(标准版)
- 五六年级女生青春期健康讲座内容
- 境外旅游保险知识培训课件
- 实验室实验员述职报告
- 《义务教育数学课程标准(2022年版)》解读课件
- 医院环境清洁消毒与监测
- DB44∕T 2331-2021 公路混凝土桥梁火灾后安全性能评定技术规程
- 河南洛阳产融集团有限公司招聘笔试题库2025
- 香水标签管理办法
- 部编版八下历史期末复习常考观点速记(新考向)
评论
0/150
提交评论