LUCI架构

LUCI 这个在百度上搜索除了一篇我的百度文库luci的介绍文章之外,前三页都是些不知所云的名词(足见百度在专业领域的搜索之烂),我却在大学毕业的大半年的大部分时间里与它纠结,由于开始的发懵到后来逐渐感觉到这家伙还很好玩的,现在就把我对luci的浅显认识介绍给大家。

官网:http://luci.subsignal.org/

  有关luci的各个方面,你几乎都可以从这里获得,当然,只是浅显的获得,luci的文档写的还算比较全,但是写的稍显简略,开始看的时候会有一点不知所措。

UCI  熟悉openwrt的人都会有所了解,就是Unied Conguration Interface的简称,而luci这个openwrt上的默认web系统,是一个独立的由严谨的德国人开发的web框架,是Lua  Conguration Interface的简称,如果在您的应用里,luci是对openwrt的服务,我们就有必要做一下uci的简介,我这里就不说了,见链接:

http://www.google.com.hk/url?sa=t&source=web&cd=5&ved=0CEMQFjAE&url=http%3A%2F%2Fnbd.name%2Fopenwrt-fosdem-09.pdf&ei=h52iTcXvOcrMcJ-xxOwD&usg=AFQjCNGFhumCIgS5tK_mDJ2dDFU4qsskfQ

 

有的时候,我们开发的luci是在自己的Linux PC上开发,在普通的linux上,一般是没有uci命令的,为了开发方便,可以手动编译一下,方法见链接:

https://forum.openwrt.org/viewtopic.php?id=15243

OK ,之前罗里罗嗦的说了很多,现在就进入正题,进入正题的前提是你已经make install正确的安装了lua luci,以及编译好链接了相关的so(如果你需要,比如uci.so nixio.so),以及make install正确web server,(我用的web serverthttpd,也编译过mongooselighttpd,在这三个之中,lighttpd是功能最完善的,mongoose是最小巧的)。

进入正题:

一:luci的启动

  在web server中的cgi-bin目录下,运行luci文件(权限一般是755),luci的代码如下:

[c-sharp]  view plain copy
  1. #!/usr/bin/lua      --cgi的执行命令的路径require"luci.cacheloader"    --导入cacheloader包require"luci.sgi.cgi"         --导入sgi.cgi包 luci.dispatcher.indexcache = "/tmp/luci-indexcache"   --cache缓存路径地址  
  2. luci.sgi.cgi.run()  --执行run方法,此方法位于*/luci/sgi/cgi.lua中  

 

 run方法的主要任务就是在安全的环境中打开开始页面(登录页面),在run中,最主要的功能还是在dispatch.lua中完成。

 运行luci之后,就会出现登录界面:

[xhtml]  view plain copy
  1. -bash-4.0# pwd  
  2. /var/www/cgi-bin  
  3. -bash-4.0# ./luci  
  4.   Status: 200 OK      
  5.   Content-Type: text/html;   
  6.   charset=utf-8       
  7.   Cache-Control: no-cache     
  8.   Expires: 0  
  9. <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"   
  10. "http://www.w3.org/TR/html4/strict.dtd">      
  11.  <html class=" ext-strict">  
  12.  /*some html code*/      
  13.  </html>  

   
     如果你成功的运行了luci  就说明你的luci框架成功的跑了起来。

二:LUCIMVC

1:用户管理:

      在luci的官方网站说明了luci是一个MVC架构的框架,这个MVC做的可扩展性很好,可以完全的统一的写自己的html网页,而且他对shell的支持相当的到位,(因为lucilua写的,luaC的儿子嘛,       与shell是兄弟)。在登录界面用户名的选择很重要,     luci是一个单用户框架,公用的模块放置在*/luci/controller/下面,各个用户的模块放置在*/luci/controller/下面对应的文件夹里面,比如             admin登录,最终的页面只显示/luci/controller/admin下面的菜单。这样既有效的管理了不同管理员的权限。

2:controller文件夹下的lua文件说明:(以mini用户为例)

     在mini目录下面,会有一个index.lua文件,简略的代码如下:

[python]  view plain copy
  1. module("luci.controller.mini.index", package.seeall)  
  2. 17    
  3. 18  function index()  
  4. 19      luci.i18n.loadc("admin-core")  
  5. 20      local i18n = luci.i18n.translate  
  6. 21    
  7. 22      local root = node()  
  8. 23      if not root.lock then  
  9. 24          root.target = alias("mini")  
  10. 25          root.index = true  
  11. 26      end  
  12. 27       
  13. 28      entry({"about"}, template("about")).i18n = "admin-core"  
  14. 29       
  15. 30      local page   = entry({"mini"}, alias("mini""index"), i18n("essentials""Essentials"), 10)  
  16. 31      page.i18n    = "admin-core"  
  17. 32      page.sysauth = "root"  
  18. 33      page.sysauth_authenticator = "htmlauth"  
  19. 34      page.index = true  
  20. 35       
  21. 36      entry({"mini""index"}, alias("mini""index""index"), i18n("overview"), 10).index = true  
  22. 37      entry({"mini""index""index"}, form("mini/index"), i18n("general"), 1).ignoreindex = true  
  23. 38      entry({"mini""index""luci"}, cbi("mini/luci", {autoapply=true}), i18n("settings"), 10)  
  24. 39      entry({"mini""index""logout"}, call("action_logout"), i18n("logout"))  
  25. 40  end  
  26. 41    
  27. 42  function action_logout()  
  28. 43      luci.http.header("Set-Cookie""sysauth=; path=/")  
  29. 44      luci.http.redirect(luci.dispatcher.build_url())  
  30. 45  end  

 

这个文件定义了node,最外面的节点,最上层菜单的显示等等。在其他的lua文件里,定义了其他菜单的显示和html以及业务处理路径。每个文件对应一个菜单相。

例如 system.lua文件

[c-sharp]  view plain copy
  1. function index()  
  2. 19      luci.i18n.loadc("admin-core")  
  3. 20      local i18n = luci.i18n.translate  
  4. 21    
  5. 22      entry({"mini""system"}, alias("mini""system""index"), i18n("system"), 40).index = true  
  6. 23      entry({"mini""system""index"}, cbi("mini/system", {autoapply=true}), i18n("general"), 1)  
  7. 24      entry({"mini""system""passwd"}, form("mini/passwd"), i18n("a_s_changepw"), 10)  
  8. 25      entry({"mini""system""backup"}, call("action_backup"), i18n("a_s_backup"), 80)  
  9. 26      entry({"mini""system""upgrade"}, call("action_upgrade"), i18n("a_s_flash"), 90)  
  10. 27      entry({"mini""system""reboot"}, call("action_reboot"), i18n("reboot"), 100)  
  11. 28  end  

mudel是对应文件的,function index定义了菜单,比如这一句entry({"mini", "system", "reboot"}, call("action_reboot"), i18n("reboot"), 100)

1项为菜单入口:

{"mini", "system", "reboot"},mini是最上层的菜单,即为用户项,system为一个具体的菜单,reboot为这个菜单的子菜单,如果reboot还需要加上子菜单的话,可以这样写:

entry({"mini", "system", "reboot","chreboot"}, call("action_chreboot"), i18n("chreboot"), 1),这样就会在reboot上产生一个新的子菜单,以此类推,可以产生N层菜单。

第二项为菜单对应的页面,可以是lua的源代码文件,也可以是html页面。

alias cgi form call 等定义了此菜单相应的处理方式,formcgi对应到model/cbi相应的目录下面,那里面是对应的定制好的htmllua业务处理。

alias是等同于别的链接,call调用了相应的action_function。还有一种调用,是template,是直接链接到view相应目录下面的htm页面。(说明:luci框架下面的htm都是可以嵌入lua语句的,做业务处理,相当于jsp页面的内部的Java语句)。

问价查找对应简介:

entry({"mini", "system", "reboot"}, call("action_reboot"), i18n("reboot"), 100)  :对应此文件的action_reboot function

entry({"mini", "system", "index"}, cbi("mini/system", {autoapply=true}), i18n("general"), 1):对应*/model/cbi/mini/system.lua  {autoapply=true}  这个失传的参数。

。。。。。

第三项为i18n显示,比如entry({"mini", "system", "reboot"}, call("action_reboot"), i18n("reboot"), 100),菜单的名字为admin-core文件内的对应显示。此处也可以这样写, i18n("reboot","重启"),即直接做了国际化。菜单上显示的就是“重启”。

第四项为现实的顺序,这个数字越小,显示越靠前,靠上。

 

现在说一下这些文件的解析是怎么解析的呢?你当然是说dispatch.lua中,你说对了,但是真正解析成菜单的递归算法确实在header.htm中  位置:*/view/themes/openwrt/
代码如下:
[php]  view plain copy
  1. <%  
  2. require("luci.sys")  
  3. local load1, load5, load15 = luci.sys.loadavg()  
  4. local request  = require("luci.dispatcher").context.path  
  5. local category = request[1]  
  6. local tree     = luci.dispatcher.node()  
  7. local cattree  = category and luci.dispatcher.node(category)  
  8. local node     = luci.dispatcher.context.dispatched  
  9. local hostname = luci.sys.hostname()  
  10. local c = tree  
  11. for i,r in ipairs(request) do  
  12.      if c.nodes and c.nodes[r] then  
  13.           c = c.nodes[r]  
  14.           c._menu_selected = true  
  15.      end  
  16. end  
  17. require("luci.i18n").loadc("default")  
  18. require("luci.http").prepare_content("application/xhtml+xml")  
  19. -%>  
  20. <?xml version="1.0" encoding="utf-8"?>  
  21. <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">  
  22. <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="<%=luci.i18n.context.lang%>" lang="<%=luci.i18n.context.lang%>">  
  23. <head>  
  24. <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />  
  25. <meta http-equiv="Content-Script-Type" content="text/javascript" />  
  26. <link rel="stylesheet" type="text/css" media="screen" href="<%=media%>/cascade.css" />  
  27. <!--[if lt IE 7]><link rel="stylesheet" type="text/css" media="screen" href="<%=media%>/ie6.css" /><![endif]-->  
  28. <!--[if IE 7]><link rel="stylesheet" type="text/css" media="screen" href="<%=media%>/ie7.css" /><![endif]-->  
  29. <% if node and node.css then %><link rel="stylesheet" type="text/css" media="screen" href="<%=resource%>/<%=node.css%>" />  
  30. <% end -%>  
  31. <mce:script type="text/javascript" src="<%=resource%><!--  
  32. /VarType.js">  
  33. // --></mce:script>  
  34. <mce:script type="text/javascript" src="<%=resource%><!--  
  35. /XHTML1.js">  
  36. // --></mce:script>  
  37. <mce:script type="text/javascript" src="<%=resource%><!--  
  38. /Dropdowns.js">  
  39. // --></mce:script>  
  40. <title><%=striptags( hostname .. ( (node and node.title) and ' - ' .. node.title or '')) %> - LuCI</title>  
  41. </head>  
  42. <body class="lang_<%=luci.i18n.context.lang%>">  
  43. <p class="skiplink">  
  44. <span id="skiplink1"><a href="#navigation" mce_href="#navigation"><%:skiplink1 Skip to navigation%></a></span>  
  45. <span id="skiplink2"><a href="#content" mce_href="#content"><%:skiplink2 Skip to content%></a></span>  
  46. </p>  
  47. <div id="header">  
  48. <h1><%=luci.version.distname%></h1>  
  49. <p>  
  50. <%=luci.version.distversion%><br />  
  51. <%:load%>: <%="%.2f" % load1%> <%="%.2f" % load5%> <%="%.2f" % load15%><br />  
  52. <%:hostname%>: <%=hostname%>  
  53. </p>  
  54. </div>  
  55. <div id="menubar">  
  56. <h2 class="navigation"><a id="navigation" name="navigation"><%:navigation Navigation%></a></h2>  
  57. <ul id="mainmenu" class="dropdowns">  
  58. <%-  
  59. local function submenu(prefix, node)  
  60.      if not node.nodes or node.hidden then  
  61.           return false  
  62.      end  
  63.      local index = {}  
  64.      local count = 0  
  65.      for k, n in pairs(node.nodes) do  
  66.           if n.title and n.target then  
  67.                table.insert(index, {name=k, order=n.order or 100})  
  68.                count = count + 1  
  69.           end  
  70.      end  
  71.      table.sort(index, function(a, b) return a.order < b.order end)  
  72.      if count > 0 then  
  73. %>  
  74. <ul id="submenu_<%=string.gsub(string.gsub(prefix, "/", "_"), "^_(.-)_$", "%1")%>">  
  75. <%-  
  76.           for j, v in pairs(index) do  
  77.                if #v.name > 0 then  
  78.                     local nnode = node.nodes[v.name]  
  79.                     local href = controller .. prefix .. v.name .. "/"  
  80.                     href = (nnode.query) and href .. luci.http.build_querystring(nnode.query) or href  
  81.                       
  82.                     if nnode.nodes then  
  83.                          for k1, n1 in pairs(nnode.nodes) do  
  84.                               href = "#"  
  85.                            
  86.                          end  
  87.                     end       
  88. %>  
  89. <li><a<% if nnode._menu_selected then %> class="active"<%end%> href="<%=luci.util.pcdata(href)%>"><%=nnode.title%></a><%-  
  90. submenu(prefix .. v.name .. "/", nnode)  
  91. %></li>  
  92. <%-  
  93.                end  
  94.           end  
  95. %>  
  96. </ul>  
  97. <%  
  98.      end  
  99. end  
  100. if cattree and cattree.nodes then  
  101.      local index = {}  
  102.      for k, node in pairs(cattree.nodes) do  
  103.           table.insert(index, {name=k, order=node.order or 100})  
  104.      end  
  105.      table.sort(index, function(a, b) return a.order < b.order end)  
  106.      for i, k in ipairs(index) do  
  107.           node = cattree.nodes[k.name]  
  108.           if node.title and node.target and not node.hidden then  
  109.                local href = controller.."/"..category.."/"..k.name.."/"  
  110.                href = (k.query) and href .. luci.http.build_querystring(k.query) or href  
  111.                  
  112.                for k1, n1 in pairs(node.nodes) do  
  113.                     if n1.title and n1.target then  
  114.                          href = " #"  
  115.                            
  116.                     end  
  117.                end       
  118. %>  
  119. <li><a<% if node._menu_selected then %> class="preactive"<%end%> href="<%=href%>"><%=node.title%></a><%  
  120. submenu("/" .. category .. "/" .. k.name .. "/", node)  
  121. %></li><% end  
  122.      end  
  123. end  
  124. %>  
  125. </ul>  
  126. <ul id="modemenu"><%  
  127. for k,node in pairs(tree.nodes) do  
  128.      if node.title and not node.hidden then %>  
  129. <li><a<% if request[1] == k then %> class="active"<%end%> href="<%=controller%>/<%=k%>/"><%=node.title%></a></li><%  
  130.      end  
  131. end  
  132. %>  
  133. </ul>  
  134. <%  
  135. if tree.nodes[category] and tree.nodes[category].ucidata then  
  136.      local ucic = 0  
  137.      for i, j in pairs(require("luci.model.uci").cursor():changes()) do  
  138.           for k, l in pairs(j) do  
  139.                for m, n in pairs(l) do  
  140.                     ucic = ucic + 1;  
  141.                end  
  142.           end  
  143.      end  
  144. -%>  
  145. <ul id="savemenu" class="dropdowns">  
  146. <li><% if ucic > 0 then %><a class="warning" href="<%=controller%>/<%=category%>/uci/changes/"><%:unsavedchanges%>: <%=ucic%></a><%  
  147. submenu("/" .. category .. "/uci/", tree.nodes[category].nodes["uci"])  
  148. else -%>  
  149. <a href="#" mce_href="#"><%:changes%>: 0</a><% end -%>  
  150. </li>  
  151. </ul><% end %>  
  152. <div class="clear"></div>  
  153. </div>  
  154. <div id="maincontent">  

3:model业务处理和页面生成简介

    我认为model的业务处理和html生成,是luci框架的精华,但是想用好它,最终扩展定义自己的页面也是最难的,但是一旦定义好了,后面的工作就会轻松高效简介统一,不失为一种好的解决方案。但是它又有缺点,就是写页面虽然统一,但是不够灵活。

下面以SimpleForm为例,讲解一下。

具体文件 */luci/model/cbi/passwd.lua


[javascript]  view plain copy
  1. f = SimpleForm("password", translate("a_s_changepw"), translate("a_s_changepw1"))  --调用SimpleForm页面  当然还是I18N从中捣乱,看上去没那么直观,不理他  
  2. pw1=f:field(Value,"pw1",translate("password")) -- SimpleForm 里面加一个field  至于SimpleForm 和 fiemd是什么,一会去看SimpleForm页面去  
  3. pw1.rmempty=false -- 把SimpleForm的rmempty为不显示  后面就不做注释了 应该看得懂了  
  4. pw2 = f:field(Value, "pw2", translate("confirmation"))  
  5. pw2.rmempty = false  
  6. function pw2.validate(self, value, section)  
  7.      return pw1:formvalue(section) == value and value  
  8. end  
  9. function f.handle(self, state, data)  
  10.      if state == FORM_VALID then   --这个就是业务处理了  你懂得  呵呵  
  11.           local stat = luci.sys.user.setpasswd("admin", data.pw1) == 0  -- root --> admin      
  12.           if stat then  
  13.                f.message = translate("a_s_changepw_changed")  
  14.           else  
  15.                f.errmessage = translate("unknownerror")  
  16.           end  
  17.            
  18.           data.pw1 = nil  
  19.           data.pw2 = nil  
  20.      end  
  21.      return true  
  22. end  
  23. return f  

说明:( simpleForm 位于view/cbi  下面,可以研究一下,各个元素是如何定义的)

现在在给一个小例子:

.*/luci/model /cbi/admin_system/version_manage.lua为例,介绍一下luciweb页面lua代码
 

[javascript]  view plain copy
  1. 6 local h = loadfile("/usr/local/luci/help.lua")  
  2.   7 if h then  
  3.   8     h()  
  4.   9 end  
  5.  10 local help_txt = help_info and  help_info.version  


加载帮助帮助文件help.lua,关于loadfile()的用法可以查看lua的手册(我还没完全弄明白,先用了)
help_txt 是一个全局变量

12 appadmin_path = "/usr/local/appadmin/bin/"
定义一个全局变量,其实跟功能跟宏一样,定义appadmin的绝对路径

[javascript]  view plain copy
  1. 14 versionlist = {}  
  2. 15  
  3. 16 function getline (s)  
  4. .........  
  5. 32 end  
  6. 33  
  7. 34 function get_versionlist()  
  8. .........  
  9. 68 end  
  10. 69  
  11. 70 versionlist = get_versionlist()  



定义一个全局变量和两个函数,并初始化此变量

接下来就是和最终展现的Web页面直接相关的代码了,大部分都是对luci封装好的一些html控件(代码)的使用和扩展。luci 封装好的html控件

类可以在以下文件查看:./host/usr/lib/lua/luci/cbi.lua
[javascript]  view plain copy
  1. 71 m = SimpleForm("version", translate("版本管理"))  
  2. 72 m.submit = false  
  3. 73 m.reset = false  
  4. 74 m.help = help_txt and true or false  
  5. 75 m.helptxt = help_txt or ""  


使用了一个SimpleForm的控件,SimpleForm实际上对应一个html表单,是整个页面最大的"容器",本页面内的绝大部分控件都处于SimpleForm

,是它的子控件 。我知道的可以做>页面最大"容器"的控件还有一个Map,但它需要./host/etc/config/目录下的一个配置文件,我没有使用。
submit reset是luci默认就封装好的一个属性,分别控制html表单的"提交""重置"按钮;help helptxt是我扩充的表单属性,分别控制web页面的

"帮助"功能和帮助内容。关于控件属
性的意义、实现和扩充可以按以下步骤进行:
    在文件./host/usr/lib/lua/luci/cbi.lua中查找控件名SimpleForm, 然后可以找到以下行 664     self.template = "cbi/simpleform"

表明SimpleFormhtml模版文件为./host/usr/lib/lua/luci/view/cbi /simpleform.htm,通过研究simpleform.htm文件内容可以知道各属性的

功能以及模版中使用lua代码的方法,然后可以按类似的方法添加自定义的
属性。
77 s = m:section(Table, versionlist)
新建了一个section,section内定义了一个表格类,versionlist是与其相关的变量(lua的所有变量都可归类于 table类型)
Table关联的table变量应该是这种结构的:
[javascript]  view plain copy
  1. t = {  
  2.     row1 = {column1 = "xxx", column2 = "xxx", .... },  
  3.     row2 = {column1 = "xxx", column2 = "xxx", .... },  
  4.     row3 = {column1 = "xxx", column2 = "xxx", .... },  
  5.     row4 = {column1 = "xxx", column2 = "xxx", .... },  
  6. }  


然后定义Table的列控件
[javascript]  view plain copy
  1. 79 enable = s:option(DummyValue, "_enabled", translate("软件状态"))  
  2. 83 appid  = s:option(DummyValue, "_appid", translate("软件版本"))  
  3. 84 appname = s:option(DummyValue, "_appname", translate("软件名称"))  


DummyValue是只读的文本框,只输出不输入。Value是单行文本框,可输出也可输入。Flag是一个checkbox,值为"1"时被选中,为"0"时未选中。

ListValue是列表框...具体的用法可
以看./host/usr/lib/lua/luci /model/cbi/下的文件(find ./host/usr/lib/lua/luci/model/cbi/ -name "*.lua" |xargs grep

"ListValue")
对于table内普通的字符串类的值,只需要把列控件的id(括号内第二个值,如"_appid")定义为table内对应的变量名(比如column1 )
对于非变通字符串类的值,或者为字符串但需要进行一定的处理然后再显示的值,可以按以下方法显示:定义该控件的cfgvalue函数属性
[javascript]  view plain copy
  1. 127     newinfo = up_s:option(TextValue, "_newifo", translate("新版本信息"))  
  2. 128     newinfo.readonly = true  
  3. 129     newinfo.rows = 11  
  4. 130     newinfo.cfgvalue = function(self, section)  
  5. 131         local t = string.gsub(info, "Archive:[^/n]*""")  
  6. 132         return t  
  7. 133     end  


定义cfgvalue后,luci的处理函数会调用此函数为此控件赋值,(传入的section参数值为row1/row2/row3等,当处理到row几时值就为row)
对于DummyValue等只输出不输入的类,还有一种赋值方法: 控件实例名(如enable).value = xxx
对于有输入的控件Value等, .value方法赋值在处理输入里会有一些问题,有什么问题以及如何解决可以做实验试试 ,也许是我使用方法不对造
成的
对有输入控件的处理有两种方法:
1 定义控件的.write属性
    这种方法对处理比较独立的输入(与其它控件输入关系不大)比较适用
[javascript]  view plain copy
  1. 88 up_s = m:section(SimpleSection)  
  2. 89 up_version = up_s:option(Button, "_up_version", translate("上传新版本"))  
  3. 90 up_version.onlybutton = true  
  4. 91 up_version.align = "right"  
  5. 92 up_version.inputstyle = "save"  
  6. 93 up_version.write = function(self, section)  
  7. 94     luci.http.redirect(luci.dispatcher.build_url("admin""system""version_manage""upload"))  
  8. 95 end  


ps:只有当Valuermempty == false时,Value输入为空也会触发write函数需要对rmemtpy显示赋值为false ( xx.rmempty = false)

4:view下面的html简介

这个是最好理解的  例:passwd.htm

[javascript]  view plain copy
  1. <%+header%>  
  2.   
  3. <h2><a id="content" name="content"><%:system%></a></h2>  
  4.   
  5. <h3><%:reboot%></h3>  
  6.   
  7. <p><%:a_s_reboot1%></p>  
  8.   
  9. <%-  
  10.   
  11. local c = require("luci.model.uci").cursor():changes()  
  12.   
  13. if c and next(c) then  
  14.   
  15. -%>  
  16.   
  17.        <p class="warning"><%:a_s_reboot_u%></p>  
  18.   
  19. <%-  
  20.   
  21. end  
  22.   
  23. if not reboot then  
  24.   
  25. -%>  
  26.   
  27. <p><a href="<%=controller%>/admin/system/reboot?reboot=1"><%:a_s_reboot_do%></a></p>  
  28.   
  29. <%- else -%>  
  30.   
  31. <p><%:a_s_reboot_running%></p>  
  32.   
  33. <script type="text/javascript">setTimeout("location='<%=controller%>/admin'", 60000)</script>  
  34.   
  35. <%- end -%>  
  36.   
  37. <%+footer%>  
说明一下一些元素的意义:
[javascript]  view plain copy
  1. <%+header%> <%+footer%>  加载公用的头部和尾部  
  2.   
  3. <% lua code%>  
  4.   
  5. <%:i18n%>  
  6.   
  7. <%lua code%>  
  8.   
  9. <%=lua 变量%>  
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值