前端_网页编程 Form表单与模板引擎(中)

目录

  • ... ...
  • (续上篇)
  • 四、模板引擎的基本概念
    • 1.定义
    • 2. 优点
  • 五、art-template模板引擎
    • 1.art-template模板引擎介绍
    • 2. art-template的安装
    • 3. art-template模板引擎的基本使用
      • 3.1 使用传统方式渲染UI结构
      • 3.2 art-template的使用步骤
    • 4. art-template语法
      • 4.1 输出
      • 4.2 原文输出
      • 4.3 条件输出
      • 4.4 循环输出
      • 4.5 定义变量
      • 4.6 过滤器
      • 4.7 调试
      • 4.8 模板变量
        • 4.8.1 导入变量
        • 4.8.2 内置变量
    • 5 案例 - 新闻列表
      • 5.1 实现步骤
      • 5.2 实现过程
        • 5.2.1 获取新闻数据
        • 5.2.2 定义 template 模板
        • 5.2.3 编译模板
      • 5.3 新闻列表案例完整`js`代码
    • 6. 模板引擎的实现原理


… …

(续上篇)

上一篇:前端_网页编程 Form表单与模板引擎(上)


四、模板引擎的基本概念

在上一篇中,引申出了模板引擎的概念,这一篇在继续介绍模板引擎的相关知识和使用前,再次理清它的概念或定义。

1.定义

模板引擎,顾名思义,它可以根据程序员指定的 模板结构数据,自动生成一个完整的HTML页面。

数 据
模板结构
模板引擎
HTML页面

2. 优点

  1. 减少了字符串的拼接操作
  2. 使代码结构更清晰
  3. 使代码更易于阅读与维护

五、art-template模板引擎

1.art-template模板引擎介绍

art-template 是一个简约、超快的模板引擎。中文官网首页为 http://aui.github.io/art-template/zh-cn/index.html

art-template 采用作用域预声明的技术来优化模板渲染速度,从而获得接近 JavaScript 极限的运行性能,并且同时支持 NodeJS 和浏览器。

常用模板引擎执行效率图示:

前端_网页编程 Form表单与模板引擎(中)_第1张图片

2. art-template的安装

在浏览器访问 http://aui.github.io/art-template/zh-cn/docs/installation.html 页面,在【在浏览器中编译】板块中,找到下载链接并鼠标右键,选择“链接另存为”,将 art-template 下载到本地,然后,通过 < script> 标签加载到网页上进行使用。

示图:
前端_网页编程 Form表单与模板引擎(中)_第2张图片
安装方法:

通过 < script> 标签加载到网页上进行使用

3. art-template模板引擎的基本使用

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Documenttitle>
    
    <script src="./lib/template-web.js">script>
head>

3.1 使用传统方式渲染UI结构

效果图_使用传统方式渲染UI结构
前端_网页编程 Form表单与模板引擎(中)_第3张图片
传统方式渲染的完整代码

HTML:

<div id="title">div>
    <div>姓名:<span id="name">span>div>
    <div>年龄:<span id="age">span>div>
    <div>会员:<span id="isVIP">span>div>
    <div>注册时间:<span id="regTime">span>div>
    <div style="margin-top:10px;">爱好:
        <ul id="hobby">
            <li>爱好1li>
            <li>爱好2li>
        ul>
    div>
div>

js代码

<script>
        var data = {
            title: '

用户信息

'
, name: '张三疯', age: 200, isVIP: true, regTime: new Date(), hobby: ['唱歌', '跳舞', '学前端'] } // 立即执行函数 $(function() { $('#name').html(data.name) $('#title').html(data.title) $('#age').html(data.age) $('#isVIP').html(data.isVIP) $('#regTime').html(data.regTime) var rows =[] // 声明一个空数组用于存放获取的字符串。 $.each(data.hobby, function(i, item) { rows.push('
  • ' + item + '
  • '
    ) }) $('#hobby').html(rows.join('')) }) </script>

    3.2 art-template的使用步骤

    
    <html lang="en">
    
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Documenttitle>
        
        <script src="./lib/template-web.js">script>
        <script src="./lib/jquery.js">script>
    head>
    
    <body>
        <div id="container"></div>
        <!-- 3、定义模板 -->
        <!-- 3.1 模板的HTML结构 ,必须定义到script中 -->
        <script type="text/html" id="tpl-user">
            <h1>{{name}}-- {{age}}</h1>
        </script>
    
        <script>
            // 2、定义需要渲染的数据
            var data = {
                    name: '张三疯',
                    age: 200
                }
                // 4、调用template函数
            var htmlstr = template('tpl-user', data)
                // 渲染HTML结构
            $('#container').html(htmlstr)
        </script>
    </body>
    
    1. 导入art-template
    2. 定义数据;
    3. 定义模板;
    4. 调用template函数;
    5. 渲染HTML结构。

    4. art-template语法

    art-template 支持标准语法原始语法。标准语法可以让模板易读写,而原始语法拥有强大的逻辑表达能力。

    标准语法支持基本模板语法以及基本 JavaScript 表达式;原始语法支持任意 JavaScript 语句。

    art-template 提供了 {{ }} 这种语法格式,在 {{ }} 内可以进行 变量输出,或 循环数组 等操作,这种 {{ }} 语法在 art-template 中被称为标准语法

    原理:如果想要把数据填充到模板里,先通过双花括号来放一个点位符,将来只要调用template函数,双花括号所在的位置就会自动替换为真实的数据。这种双花括号{{}},在art-tmplate中叫做标准语法

    {{}}除了代表点位符外,还可以做一些其它复杂的操作:

    4.1 输出

    1)标准语法 - 输出:

    {{value}}
    {{data.key}}
    {{data['key']}}
    {{a ? b : c}}
    {{a || b}}
    {{a + b}}
    

    2)原始语法 - 输出:

    <%= value %>
    <%= data.key %>
    <%= data['key'] %>
    <%= a ? b : c %>
    <%= a || b %>
    <%= a + b %>
    

    4.2 原文输出

    1)标准语法 - 原文输出:

    {{@ value}}
    

    只要在表达式之前,加了“@”,就表示原文输出
    如果要输出的 value 值中,包含了 HTML 标签结构,则需要使用原文输出 语法,才能保证 HTML 标签被正常渲染。

    标准语法-原文输出示例:

    	// 定义需要渲染的数据
    	var data = {
    		name: '张三疯',
    		age: 20,
    		text: '

    测试原文输出

    '
    }
    	<script type="text/html" id="tpl-user">
            <h1>{{name}}-- {{age}}</h1>
            {{@ text}}
    	script>
    

    (注:@表达式之间有个空格不要漏掉)

    2)原始语法 - 原文输出:

    <%- value %>
    

    4.3 条件输出

    1)标准语法 - 条件输出:
    如果要实现条件输出,则可以在 {{ }} 中使用 ifelse if … /if 的方式,进行按需输出。

    <!--if 判断 -->
    {{if value}} 按需输出的内容 {{/if}}
    
    <!-- if ... else ... 判断 -->
    {{if v1}} 按需输出的内容 {{else if v2}} 按需输出的内容 {{/if}}
    

    第1个if代表条件输出的开始,/if代表条件输出的结束。

    2)原始语法 - 条件输出:

    <!--if 判断 -->
    <% if (value) { %>
    	按需输出的内容
    <% } %>
    
    <!-- if ... else ... 判断 -->
    <% if (v1) { %>
    	按需输出的内容
    <% else if (v2) { %>
    	按需输出的内容
    <% } %>
    

    4.4 循环输出

    1)标准语法 - 循环输出:
    如果要实现循环输出,则可以在 {{}} 内,通过 each 语法循环数组,当前循环的索引使用 $index 进行访问,当前的循环项使用 & value 进行访问。

    {{each arr}}
        {{$index}} {{$value}}
    {{/each}}
    

    {{ }} 语法中,可以进行变量的输出、对象属性的输出、三元表达式输出、逻辑或输出、加减乘除等表达。

    标准语法-循环输出示例:

    	<div id="container">div>
        
        
        <script type="text/html" id="tpl-user">
            <h1>{{name}}-- {{age}}</h1>
            {{@ text}}
            <ul>
                {{each hobby}}
                <li>索引{{$index}},item:{{$value}}</li>
                {{/each}}
            </ul>
        script>
    
    	<script>
            // 2、定义需要渲染的数据
            var data = {
                    name: '张三疯',
                    age: 20,
                    text: '

    测试原文输出

    '
    , hobby: ['吃饭', '睡觉', '写代码'] } // 4、调用template函数 var htmlstr = template('tpl-user', data) // 渲染HTML结构 $('#container').html(htmlstr) </script>

    页面渲染效果:
    前端_网页编程 Form表单与模板引擎(中)_第4张图片

    2)原始语法 - 循环输出:

    <% for (var i = 0; i < arr.length; i++) { %>
    <%= i %> <% = arr[i] %>
    <% } %>
    

    代码中,arr是目标数组,each是数组遍历,$index 是数组下标, $value是数组的值。

    注: arr 支持 arrayobject 的迭代,其默认值为 $data

    4.5 定义变量

    1)标准语法:

    {{set temp = data.sub.content}}
    

    2)原始语法:

    <% var temp = data.sub.content %>
    

    4.6 过滤器

    过滤器的本质,就是一个function处理函数。

    1)标准语法 - 过滤器:

    {{value | filterName}}
    

    语法注解】:“|”代表调用某个函数(也叫管道符)。把value当做参数,通过管道符,传递给后的函数filterName

    过滤器语法类似 管道操作符,它的上一个输出作为下一个输入。
    定义过滤器的基本语法如下:

    template.defaults.imports.filterName = function(value){/*return处理的结果*/}
    

    注意:调用函数时,函数名要与这里实际的filterName名称一致。最后必须return出去值,供后面的双花括号{{}}渲染使用。

    2)示例:

    
    <html lang="en">
    
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Documenttitle>
        
        <script src="./lib/template-web.js">script>
    head>
    
    <body>
    	<div class="container">div>
        <script type="text/html" id="filterTpl">
            <!-- 标准语法 -->
            <h3>{{date | dateformat 'YYYy-mM-dd' | addQuotationMarks}}</h3>
    
            <!-- 原始语法 -->
            <h3>
                <%= $imports.addQuotationMarks($imports.dateformat(date)) %>
            </h3>
        script>
    

    ① 定义一个格式化时间的过滤器 dateFormat, 如下:

    <script>
    	var data = {
    		date: Date.now(),
    	}
    	
    	template.defaults.imports.dateFormat = function(date,format) {
    		if (!format || format.toLowerCase() === 'yyyy-mm-dd') {
    			var dt = new Date(date);
        		var y = dt.getFullYear();
        		// 月份是从0开始的,所以这里 +1
        		var m = (dt.getMonth() + 1).toString().padStart(2, '0');
        		var d = dt.getDate().toString().padStart(2, '0');
        		// 注意,过滤器最后一定要 return 一个值
        		return `${y}-${m}-${d}`;		
        	} else {
    			return 'invalid date';
    		}
    	}
    

    ② 定义一个给字符串加引号过滤器 addQuotationMarks,如下:

    	// 定义给字符串加引号过滤器 addQuotationMarks 方法:
    	template.defaults.imports.addQuotationMarks = function (str) {
    		return `"${str}"`;
    	}
    

    注:

    {{date | format 'YYYy-mM-dd' | addQuotationMarks}}
    

    date 默认为 format 过滤器(方法)的第一个参数, 'YYYy-mM-dd' 才是format 过滤器的第二个参数,date 经过 format 过滤器过滤后,得到的结果又作为 addQuotationMarks 过滤器的默认参数,如果有更多的过滤器,那么就把前一层过滤器过滤的结果,作为下一个过滤器的参数一层层过滤下去。

    3)渲染模板和数据:

    	// 调用 template 方法,渲染模板和数据
    	var html = template('filterTpl', data);
    	document.querySelector('.container').innerHTML = html;
    

    过滤器 - 完整案例代码:

    
    <html lang="en">
    
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Documenttitle>
        
        <script src="./lib/template-web.js">script>
    head>
    
    <body>
        <div class="container">div>
        <script type="text/html" id="filterTpl">
            <!-- 标准语法 -->
            <h3>{{date | dateformat 'YYYy-mM-dd' | addQuotationMarks}}</h3>
    
            <!-- 原始语法 -->
            <h3>
                <%= $imports.addQuotationMarks($imports.dateformat(date)) %>
            </h3>
        script>
        <script>
            var data = {
                    date: Date.now(),
                }
                // 定义日期格式化过滤器 format 方法:
            template.defaults.imports.dateformat = function(date, format) {
                    if (!format || format.toLowerCase() === 'yyyy-mm-dd') {
                        var dt = new Date(date);
                        var y = dt.getFullYear();
                        var m = (dt.getMonth() + 1).toString().padStart(2, '0');
                        var d = dt.getDate().toString().padStart(2, '0');
                        // 注意,过滤器最后一定要 return 一个值:
                        // return `${y}-${m}-${d}`; 或下面这种写法
                        return y + '-' + m + '-' + d
                    } else {
                        return '无效的日期!';
                    }
                }
                // 定义给字符串加引号过滤器 addQuotationMarks 方法:
            template.defaults.imports.addQuotationMarks = function(str) {
                    return `"${str}"`;
                }
                // 调用 template 方法,渲染模板和数据
            var html = template('filterTpl', data);
            document.querySelector('.container').innerHTML = html;
        script>
    body>
    
    html>
    

    4.7 调试

    template.defaults.debug
    

    art-template 内建调试器,能够捕获到语法与运行错误,并且支持自定义的语法。在 NodeJS 中调试模式会根据环境变量自动开启:process.env.NODE_ENV !== 'production',设置 template.defaults.debug=true 后,等同于:

    {
        "cache": false,
        "minimize": false,
        "compileDebug": true
    }
    

    4.8 模板变量

    template.defaults.imports
    

    模板通过 $imports 可以访问到模板外部的全局变量导入的变量

    4.8.1 导入变量

    1)标准语法:

    template.defaults.imports.log = console.log;
    

    2)原始语法:

    <% $imports.log('Hello, template.defaults.imports.log') %>;
    

    3)示例代码:

    
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Documenttitle>
        
        <script src="./lib/template-web.js">script>
    head>
    
    <body>
        <div class="container">
        div>
        <script type="text/html" id="importsTpl">
            <% $imports.log('Hello, template.defaults.imports.log') %>
            <%= $imports.date %>
        script>
        <script>
            var data = {};
            template.defaults.imports.log = console.log;
            template.defaults.imports.date = new Date();
            template.defaults.debug = true;
            var html = template('importsTpl', data);
            document.querySelector('.container').innerHTML = html;
        script>
    body>
    html>
    

    4.8.2 内置变量

    变量清单

    变量名 描述
    $data 传入模板的数据
    print 字符串输出函数
    include 子模板载入函数
    extend 模板继承模板导入函数
    block 模板块生命函数

    5 案例 - 新闻列表

    前端_网页编程 Form表单与模板引擎(中)_第5张图片

    5.1 实现步骤

    1. 获取新闻数据;
    2. 定义 template 模板;
    3. 编译模板;
    4. 定义时间过滤器;
    5. 定义补零函数。

    本案例素材:https://pan.baidu.com/s/1VDaCu6MgoLMLGucNy7Mqkw
    提取码:sm5a
    前端_网页编程 Form表单与模板引擎(中)_第6张图片
    素材中,HTML结构、CSS样式代码已具备,只需要写js实现代码。

    5.2 实现过程

    5.2.1 获取新闻数据

    1. 在素材的 js 文件中,新建news.js并引入到HTML页面中。
    <script src="./js/news.js">script>
    
    1. 书写 news.js 代码
    $(function() { // jQurey入口函数
    
        // 发起请求获取新闻列表数据的函数
        function getNewsList() {
            $.get('http://www.liulongbin.top:3006/api/news', function(res) {
                // res接收服务器响应回来的结果
                if (res.status !== 200) return alert('新闻列表获取失败!');
                console.log(res.data); // 打印获取到的数据
            });
        };
    	// 调用请求函数
        getNewsList()
        
    })
    

    console.log获取到的数据如下:
    前端_网页编程 Form表单与模板引擎(中)_第7张图片

    5.2.2 定义 template 模板

    1)定义模板的步骤:

    1. 要创建 < script>标签;
    2. 给这个标签指定type属性(type="text/html");
    3. < script >标签内部填充一些HTML的模板结构。

    2)定义模板
    在HTML中的< /body>标签结束之前定义模板:

    1. < div class="news-item"> ... < /div>这部分全部剪切,粘贴到上一步建好的模板< script type="text/html"> < /script>内。
    
        <script type="text/html">
          <div class="news-item">
            <img class="thumb" src="" alt="" />
            <div class="right-box">
                <h1 class="title">5G商用在即,三大运营商营收持续下降</h1>
                <div class="tags">
                    <span>三大运营商</span>
                    <span>中国移动</span>
                    <span>5G商用</span>
                </div>
                <div class="footer">
                    <div>
                        <span>胡润百富</span>&nbsp;&nbsp;
                        <span>2019-10-28 10:14:38</span>
                    </div>
                    <span>评论数:66</span>
                </div>
            </div>
        </div>
        script>
    

    定义好模板,接下来要做的就是编译模板。

    5.2.3 编译模板

    1. 给模板添加 id(值为"tpl-news")。
    <script type="text/html" id="tpl-news">
    	<!--... ...-->
    script>
    
    1. news.js中,调用template函数并将返回的字符串,赋值给变量htmlstr
    var htmlstr = template('tpl-news', res);
    

    然后将得到的数据填充到< div id="news-list"> < /div>的内部。

    $(function() {
        // 发起请求获取新闻列表数据的函数
        function getNewsList() {
            $.get('http://www.liulongbin.top:3006/api/news', function(res) {
                // res接收服务器响应回来的结果
                if (res.status !== 200) return alert('新闻列表获取失败!');
                var htmlstr = template('tpl-news', res);
    
                // DOM操作
                $('#news-list').html(htmlstr)
            });
        };
    
        getNewsList()
    })
    

    运行效果如图:
    前端_网页编程 Form表单与模板引擎(中)_第8张图片
    由前面5.2.1中用console.log控制台输出的数据可知,新闻有9条数据,而这里只渲染了1条。

    原因:
    因为虽然把数据传到模板里了,但是我们并没有拿到数据去进行循环渲染。所以接下来重点是放在模板内部

    思路:

    1. 首先,要看下能否拿到传过来的data数据;
      验证方法: 在模板内部写上标准语法双花括号{{data.length}},运行后,在页面正确的显示data数组的长度为9。这就说明新闻列表数据能够被访问到。
    1. each循环创建新闻的item项。
     {{each,data}}
     {{/each}}
    

    将模板除< script>标签外,全部放到 each循环内,并完善图片imgsrc属性:

    完善前:

    <img class="thumb" src="" alt="" />
    

    完善后 :

    <img class="thumb" src="{{'http://www.liulongbin.top:3006'+ $value.img}}" alt="" />
    

    由接口文档可知,data返回值中的img的地址示例"img": "/images/0.webp"是不完整的,因此模板中加上了它的根路径。

    1. 标签的处理
      接口文档中,标签的返回结果,是一个字符串"tags": "三大运营商,中国移动,5G商用",渲染每个标签,是要拿到标签的数组,因此,在渲染前需要将字符串改造成数组(split函数):
    	// 把每项的tags属性,从字符串改为数组
    	for (var i = 0; i < res.data.length; i++) {
    		res.data[i].tags = res.data[i].tags.split(',')
    	}
    	console.log(res);
    

    控制台输出结果如下,可见每项的tags都变成了长度为3的一个数组。
    前端_网页编程 Form表单与模板引擎(中)_第9张图片

    1. 有了tags数组后,就可以在每一个新闻的item项里,再写一个子循环,循环tags属性。每循环一次就创建一个标签。
      具体实现如下:

    1) 改造HTML文件中的结构代码:

    对HTML页面中写死的部分进行改造。

    改造前:

    	<div class="tags">
    		<span>三大运营商span>
    		<span>中国移动span>
    		<span>5G商用span>
    	div>
    

    改造后:

    	<div class="tags">
    		{{each $value.tags}}
    		<span>{{$value}}span> 
    		{{/each}}
    	div>
    

    注意: < span>标签中的$ value和each循环中的$ value不一样,后者是表示当前新闻的信息,循环的是当前新闻的这个$value.tags数组;每循环一次,拿到的项,就是< span> 中的$value

    再分别替换信息来源、日期,改造后的代码如下:

    
        <script type="text/html" id="tpl-news">
            {{each data}}
            <div class="news-item">
                <img class="thumb" src="{{'http://www.liulongbin.top:3006'+$value.img}}" alt="" />
                <div class="right-box">
                    <h1 class="title">{{$value.title}}</h1>
                    <div class="tags">
                        {{each $value.tags}}
                        <span>{{$value}}</span>
                        {{/each}}
                    </div>
                    <div class="footer">
                        <div>
                            <span>{{$value.source}}</span>&nbsp;&nbsp;
                            <span>{{$value.time}}</span>
                        </div>
                        <span>评论数:{{$value.cmtcount}}</span>
                    </div>
                </div>
            </div>
            {{/each}}
        script>
    

    至此,基本结构已完成。剩下时间格式美化,定义时间过滤器补零函数

    2)美化新闻列表中的时间格式

    ① 编写时间过滤器 dateFormat:

    // 定义格式化时间过滤器
    template.defaults.imports.dateFormat = function(dtstr) {
                var dt = new Date(dtstr);
                var y = dt.getFullYear();
                var m = dt.getMonth() + 1;
                var d = dt.getDate();
                var hh = dt.getHours();
                var mm = dt.getMinutes();
                var ss = dt.getSeconds();
                return y + '-' + m + '-' + d + ' ' + hh + ':' + mm + ':' + ss
            }
    

    调用过滤器

    <div class="footer">
    	<div>
    		<span>{{$value.source}}span>  
    		<span>{{$value.time | dateFormat}}span> // 调用过滤器
    	div>
    	<span>评论数:{{$value.cmtcount}}span>
    div>
    

    渲染后的效果:
    前端_网页编程 Form表单与模板引擎(中)_第10张图片
    因此,我们是再次对时间进行处理,即补零。

    ② 补零函数

    	// 时间补零函数
        function padZero(n) {
            if (n < 10) {
                return '0' + n;
            } else {
                return n;
            }
        }
    

    然后再在时间过滤器当中调用padZero函数:

    template.defaults.imports.dateFormat = function(dtstr) {
                var dt = new Date(dtstr);
                var y = dt.getFullYear();
                var m = padZero(dt.getMonth() + 1);
                var d = padZero(dt.getDate());
                var hh = padZero(dt.getHours());
                var mm = padZero(dt.getMinutes());
                var ss = padZero(dt.getSeconds());
                return y + '-' + m + '-' + d + ' ' + hh + ':' + mm + ':' + ss
            }
    

    5.3 新闻列表案例完整js代码

    $(function() {
        // 时间补零函数
        function padZero(n) {
            if (n < 10) {
                return '0' + n;
            } else {
                return n;
            }
        }
        // 定义格式化时间的过滤器
        template.defaults.imports.dateFormat = function(dtstr) {
                var dt = new Date(dtstr);
                var y = dt.getFullYear();
                var m = padZero(dt.getMonth() + 1);
                var d = padZero(dt.getDate());
                var hh = padZero(dt.getHours());
                var mm = padZero(dt.getMinutes());
                var ss = padZero(dt.getSeconds());
                return y + '-' + m + '-' + d + ' ' + hh + ':' + mm + ':' + ss
            }
            // 发起请求获取新闻列表数据的函数
        function getNewsList() {
            $.get('http://www.liulongbin.top:3006/api/news', function(res) {
                // res接收服务器响应回来的结果
                if (res.status !== 200) return alert('新闻列表获取失败!');
                // 把每项的tags属性,从字符串改为数组
                for (var i = 0; i < res.data.length; i++) {
                    res.data[i].tags = res.data[i].tags.split(',')
                }
                console.log(res);
                var htmlstr = template('tpl-news', res);
    
                // DOM操作
                $('#news-list').html(htmlstr)
            });
        };
    
        getNewsList()
    })
    

    6. 模板引擎的实现原理

    ( 转下一篇 )


    上一篇:前端_网页编程 Form表单与模板引擎(上)

    下一篇:前端_网页编程 Form表单与模板引擎(下)

    你可能感兴趣的:(前端)