回顾

哈喽大家好!又是元气满满的周~~~二哈哈,不知道大家中秋节过的如何,马上又是国庆节了,博主我将通过三天的时间,给大家把项目二的数据添上(这里强调下,填充数据不是最重要的,最重要的是要配合着让大家明白 nuxt.js 是如何一步步实现服务端渲染的),虽说是基于 Nuxt 的,但是数据源还是我们的老数据,就是 .net core api 的数据,和上一个项目中使用的是一样的,这里要说下,有的小伙伴说想要我的数据,这里暂时说抱歉,因为里边有一些我的私人的记录,还有一些网站密码啥的,我自己懒得一条一条的删除了,这里就不放出来了,多多包涵,不过接口地址可以随便使用http://apk.neters.club,有源代码和数据库表结构,相信大家还是可以搞定滴,因为是三天,所以今天我们就会把首页给处理出来,这里有俩个点,第一,为什么是三天呢,因为博主三天后要放假了哈哈( 这里要给大家说下心得,坚持学习和坚持写博客是完全不同的时间量,每天我光写博客的时间最少三个小时,加上工作的九个小时,每天我至少需要12个小时,所以如果想快速学习,建议还是要好好的写博客 ),第二,数据获取和之前的 vue 有点儿差别,虽然都是基于 axios ,但是 nuxt 框架做了一定的封装,而且还是异步的,ASync/Await ,大家看我今天的标题也能看的出来,至于为什么会是异步的呢,先留个神秘,大家看完今天的讲解应该就知道是为什么了~

书接上文,上周咱们说到了nuxt 的运行原理《二八║ Nuxt 基础:面向源码研究Nuxt.js》,不知道大家是否看了呢,主要通过源码的分析,来重点说明 Nuxt 是如何实现服务端渲染的,个人感觉写的比较羞涩难懂,我也是在慢慢的润色,尽量修改成通俗易懂的给大家展示,写成人话。这里我要说句题外话,大家有时间的话,还是应该把后端的注意力拿出来一点儿点儿放到前端了,以前我也是一个老后端,一直想着各种持久化ORM哪个更帅,各种框架哪个性价比更高,但是一直也技术平平,反而忽略了这两年的前端发展,经过这一个月的学习,我发现前端技术竟然发展如此之快,竟超出我的想象,有点儿追赶挑战后端的意思了,多语言化的发展,更有助于一个程序员的发展( 这个欢迎来喷,只会一种语言的话,嗯~ 会有局限性┑( ̄Д  ̄)┍ ),哈哈这个扯的有点儿远了。

对于昨天的文章,总结来说,nuxt 的核心就是在 vue.js 的基础上,封装了双端渲染模式(服务端和客户端),结合页面html片段缓存,来实现 SSR ,最终解决首屏快速渲染和 SEO 的问题,核心就是在如何实现双端渲染上,大家之前的教程中,对 Vue 的 SPA 很熟练了,这三天咱们就慢慢的研究下,如何实现双端渲染的,这个也就是我讲 nuxt 的核心 —— 渲染。

零、今天要完成蓝色的部分

一、重点温习框架中的几个部分文件 —— 铺垫

1、nuxt.config.js 文件

项目的核心文件,作为一个配置文件,它配合着 app.html 一起担当着之前我们的 index.html 的作用,只不过这里是更偏重于配置,对全局配置起到一个十分重要的作用,像我们的 webconfig 一样

//这些配置在你的项目里可能不一定都有,但是我都会提到
module.exports = {
cache: {},
css: [
// 加载一个 node.js 模块
// 'hover.css/css/hover-min.css',
// // 同样加载一个 node.js 模块,不过我们定义所需的预处理器
// { src: 'bulma', lang: 'sass' },
// // 项目中的 CSS 文件
// '~assets/css/main.css',
// // 项目中的 Sass 文件
// { src: '~assets/css/main.scss', lang: 'scss' } // 指定 scss 而非 sass
],
// 默认 true
dev: process.env.NODE_ENV !== 'production',//不是生产环境 // 创建环境变量
env: {}, // 配置 Nuxt.js 应用生成静态站点的具体方式。
genetate: {
dir: '',
minify: '',
routes: [],
},
/*
* vue-meta
* Headers of the page
*/
head: {
title: '老张的哲学',
meta: [
{ charset: 'utf-8' },
{ name: 'viewport', content: 'width=device-width, initial-scale=1' },
{ hid: 'description', name: 'description', content: 'Nuxt.js project' }
],
link: [
{ rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }
]
},
/*
** Customize the progress bar color
*/
loading: { color: '#3B8070' },
/*
** Build configuration
*/
build: {
/*
** Run ESLint on save
*/
extend (config, { isDev, isClient }) {
if (isDev && isClient) {
config.module.rules.push({
enforce: 'pre',
test: /\.(js|vue)$/,
loader: 'eslint-loader',
exclude: /(node_modules)/
})
}
}
},
performance: {
gzip: false,
prefetch: true
},
// 引入 Vue.use 的插件
plugins: [],
// 默认当前路径
rootDir: process.cwd(),
router: {
base: '',
mode: 'history',
linkActiveClass: 'nuxt-link-active',
scrollBehavior: (to, from, savedPosition) => {
// savedPosition 只有在 popstate 导航(如按浏览器的返回按钮)时可以获取。
if (savedPosition) {
return savedPosition
} else {
let position = {}
// 目标页面子组件少于两个
if (to.matched.length < ) {
// 滚动至页面顶部
position = { x: , y: }
}
else if (to.matched.some((r) => r.components.default.options.scrollToTop)) {
// 如果目标页面子组件中存在配置了scrollToTop为true
position = { x: , y: }
}
// 如果目标页面的url有锚点, 则滚动至锚点所在的位置
if (to.hash) {
position = { selector: to.hash }
}
return position
}
},
// default
middleware: 'user-agent',
// 扩展路由
extendRoutes: () => {}, // 默认同 rootDir
srcDir: this.rootDir, transition: {
name: 'page',
mode: 'out-in'
},
watchers: {
chokidar: {}, // 文件监控
webpack: {
aggregateTimeout: ,
poll:
}
}
}
}

2、视图模板

默认的 html 模版: 应用根目录下的 app.html 文件, 如果你没有找到这个文件, 则采用默认的模版,当然你也可以自己新增,配置,

这个更像是我们之前的 index.html 页面,它配合着 nuxt.config.js 一起作为项目承载页面,更偏重于模板,把 <div id='app'></div> 挂载,变成了填充的方式。

<!DOCTYPE html>
<html {{ HTML_ATTRS }}>
<head>
{{ HEAD }}
</head>
<body {{ BODY_ATTRS }}>
{{ APP }}
</body>
</html>

3、layouts 布局目录

可以修改该目录下的 default.vue 来修改默认布局 , 这个就是类似于我们之前的 app.vue 页面,

<template>
<div class="layout-default">
<cl-header></cl-header>
<nuxt class="layout-main"/>
<cl-footer></cl-footer>
<div class="layout-bg"></div>
</div>
</template>

其中 <nuxt/> 是必需的,页面的主体内容会显示在这里 (类似于根节点的 <router-view/>)

此外还可以在目录下新增 error.vue 作为错误页面,具体的写法可以参考官方文档

4、pages 页面 路由

路由, 约定大于配置, 支持动态, 嵌套, 动态嵌套路由, 过渡效果和中间件,通过文件夹目录名称, 组件名称, 生成路由配置,默认的 transitionName 为 page, 可在 assets 中添加全局的过渡效果,

在匹配页面之前执行;

页面组件实际上是 Vue 组件,只不过 Nuxt.js 为这些组件添加了一些特殊的配置项(对应 Nuxt.js 提供的功能特性)以便你能快速开发通用应用。

<template>
<h1 class="red">Hello {{ name }}!</h1>
</template> <script>
export default {
asyncData (context) {
// called every time before loading the component
return { name: 'World' }
},
fetch () {
// The fetch method is used to fill the store before rendering the page
},
head () {
// Set Meta Tags for this Page
},
// and more functionality to discover
...
}
</script> <style>
.red {
color: red;
}
</style>

nuxt.config.js --> 执行middleware --> 匹配布局 --> 匹配页面

用于存放页面级别的组件,nuxt 会根据该目录下的页面结构生成路由

比如上图中的页面结构,会生成这样的路由配置:

const _7b01ffaa = () => import('..\\pages\\post\\index.vue' /* webpackChunkName: "pages_post_index" */).then(m => m.default || m)
const _2b7fe492 = () => import('..\\pages\\post\\_id.vue' /* webpackChunkName: "pages_post__id" */).then(m => m.default || m)
const _4f14dfca = () => import('..\\pages\\index.vue' /* webpackChunkName: "pages_index" */).then(m => m.default || m) export function createRouter () {
return new Router({
mode: 'history',
base: '/',
linkActiveClass: 'nuxt-link-active',
linkExactActiveClass: 'nuxt-link-exact-active',
scrollBehavior,
routes: [
{
path: "/post",
component: _7b01ffaa,
name: "post"
},
{
path: "/post/:id",
component: _2b7fe492,
name: "post-id"
},
{
path: "/",
component: _4f14dfca,
name: "index"
}
], fallback: false
})
}

5、使用插件 plugins 文件夹

如果项目中还需要引入其他的第三方插件,可以直接在页面中引入,这样在打包的时候,会将插件打包到页面对应的 js 里面,但要是别的页面也引入了同样的插件,就会重复打包。如果没有需要分页打包的需求,这时候可以配置 plugins,然后在根目录的 nuxt.config.js 中添加配置项 build.vendor 和 plugins,这里的 plugins 属性用来配置 vue.js 插件,也就是 可以用 Vue.use() 方法 的插件

默认只需要 src 属性,另外还可以配置 ssr: false,让该文件只在客户端被打包引入,如果是像 axios 这种第三方 (不能 use) 插件,只需要在 plugins 目录下创建 axios.js,然后在 build.vendor 中添加配置 (不需要配置 plugins)

这样在打包的时候,就会把 axios 打包到 vendor.js 中。

二、配置页面,实现首页的数据获取 —— 异步

1、在 static 文件中,新增样式 vue-blog-sq.css 文件

提醒,这里的文件,不会被打包,所以会在页面中呈现原有格式,如果想每次都被打包压缩,需要写到 assets 资源文件夹中

2、在 components 中,新建 layout 文件夹,然后新增页头页脚 组件

这个很简单,就是普通的 *.vue 组件写法,大家可以自行下载浏览

3、在 layouts 页面布局文件夹中,新增 blog.vue 布局

提醒:以后也可以单给用户增加布局,比如 user.vue

<template>
<div class="layout-default">
<cl-header></cl-header>
<nuxt class="layout-main"/>//注意,<nuxt />,必须有,类似一个 <router-view/>
<cl-footer></cl-footer>
</div>
</template> <script type="text/javascript">
import clHeader from "~/components/layout/header.vue";
import clFooter from "~/components/layout/footer.vue";
export default {
data () {
return {};
},
mounted () {
},
components: {
clHeader,
clFooter
}
};
</script>

很简单的一个布局入口,是不是很像我们之前的 App.vue 中的 路由容器 —— <router-view /> ,但是又比其更丰富,因为我们之前的 app.vue 只能有一个入口,但是 nuxt 可以提供多个自定义 模板layouts,更方便。

4、根目录新增 config 文件夹,添加 index.js ,作为我们以后的配置文件,类似 .net core api 中的 appsetting.json 文件

const config = {
//开发环境配置,开发的时候
development: {
//api: "http://localhost:58427/api/",
api: "http://apk.neters.club/api/"
},
//生产环境配置,部署的时候
production: {
api: ""
}
};
//获取当前环境变量,是 production或者development
module.exports = config[process.env.NODE_ENV];

5、在 plugins 插件中,新增 server_site 文件夹,然后添加 http.js 和 index.js

1、为什么要使用插件?

我们可以在应用中使用第三方模块,一个典型的例子是在客户端和服务端使用 axios 做 HTTP 请求。

首先我们需要安装 npm 包:

npm install --save axios

然后在页面内可以这样使用:

<template>
<h1>{{ title }}</h1>
</template> <script>
import axios from 'axios' export default {
async asyncData ({ params }) {
let { data } = await axios.get(`https://my-api/posts/${params.id}`)
return { title: data.title }
}
}
</script>

有一个值得注意的问题是,如果我们在另外一个页面内也引用了 axios,那么在应用打包发布的时候 axios 会被打包两次,而实际上我们只需要打包一次。这个问题可以通过在 nuxt.config.js 里面配置 build.vendor 来解决:

module.exports = {
build: {
vendor: ['axios']
}
}

经过上面的配置后,我们可以在任何页面里面引入 axios 而不用担心它会被重复打包。

2、为什么要分 server_site服务端 与 client_site客户端 插件

有些插件可能只是在浏览器里使用,所以你可以用 ssr: false 变量来配置插件只从客户端还是服务端运行。

举个栗子:

nuxt.config.js:

module.exports = {
plugins: [
{ src: '~/plugins/vue-notifications', ssr: false }//设置ssr 为false
]
}
plugins/vue-notifications.js://定义一个插件 import Vue from 'vue'
import VueNotifications from 'vue-notifications' Vue.use(VueNotifications)

同样地,如果有些脚本库你只想在服务端使用,在 Webpack 打包 server.bundle.js 文件的时候会将 process.server 变量设置成 true。

3、配置服务端 http.js 和 index.js 的内容

// http.js 封装 axios,防止多处打包
import Axios from "axios";
import config from "~/config";//引入配置文件 // 实例化 axios()
const http = Axios.create({
baseURL: config.api,//根url
timeout: ,
validateStatus: function (status) {
return status >= ;
}
}); // 定义错误异常方法
function LogicError (message, code, data) {
this.message = message;
this.code = code;
this.data = data;
this.name = "LogicError";
} LogicError.prototype = new Error();
LogicError.prototype.constructor = LogicError; // http 的request 请求
http.interceptors.request.use((data, headers) => {
return data;
}); // http 的response 命令,失败的调用上边的失败异常方法
http.interceptors.response.use(response => {
const data = response.data;
switch (data.success) {
case true:
return data.data;
default:
throw new LogicError(data.msg);
}
}, err => {
throw new LogicError("网络请求错误");
}); export default http;// 输出http
// 定义 http 插件,是一个全局变量
import Vue from "vue";
import http from "./http.js";// 引入http封装的axios const install = function (VueClass, opts = {}) { // http method
VueClass.http = http;
VueClass.prototype.$http = http;
};
Vue.use(install);// 在vue 中,使用该插件

6、在 nuxt.config.js 中引用我们的服务端插件

这样添加以后,我们就可以全局使用请求命令了,打包的时候,也只会打包一个

提示:1、记得需要按照提示安装 axios npm install --save axios

2、引入的组件库必须配置 plugins, 但是有的组件库不支持 ssr.

7、设计修改 pages 下的 index.vue 页面,异步获取接口数据

提示:这个就是文章开头提到的问题

1、为什么要异步?

asyncData方法会在组件(限于页面组件,也就是pages 文件夹下的vue文件 )每次加载之前被调用。它可以在服务端或路由更新之前被调用。 在这个方法被调用的时候,第一个参数被设定为当前页面的上下文对象,你可以利用 asyncData方法来获取数据,Nuxt.js 会将 asyncData 返回的数据融合组件 data 方法返回的数据一并返回给当前组件。

注意:由于asyncData方法是在组件 初始化 前被调用的,所以在方法内是没有办法通过 this 来引用组件的实例对象。

Nuxt.js 提供了几种不同的方法来使用 asyncData 方法,你可以选择自己熟悉的一种来用:

  1. 返回一个 Promise, nuxt.js会等待该Promise被解析之后才会设置组件的数据,从而渲染组件.
  2. 使用 async 或 await (推荐使用)

返回 Promise

export default {
asyncData ({ params }) {
return axios.get(`https://my-api/posts/${params.id}`)
.then((res) => {
return { title: res.data.title }
})
}
}

使用 async或await

export default {
async asyncData ({ params }) {
let { data } = await axios.get(`https://my-api/posts/${params.id}`)
return { title: data.title }
}
}

<!-- index.vue 页面 -->
<template>
<div class="u-marginBottom40 js-collectionStream">
<div class="streamItem streamItem--section js-streamItem">
<div class="u-clearfix u-maxWidth1000 u-marginAuto">
<div class="row u-marginTop30 u-marginBottom20 u-sm-marginLeft20 u-sm-marginRight20 u-xs-marginTop0 u-xs-marginRight0 u-xs-marginLeft0"> <div v-for="post in blogs" :key="post.bID" class="postArticle postArticle--short is-withAccentColors">
<!--......-->
</div> </div>
</div>
</div>
</div> </template> <script>
import Vue from "vue";//引入 vue 实例,获取全局变量
export default {
layout: "blog",//这个就是我们自定义的模板布局
async asyncData (ctx) {// asyncData() 异步获取数据方法
let blogs = [];
try {
console.log(1)
const blogData = await Vue.http.get("Blog?page=1&bcategory=技术博文");
console.log(blogData);
blogs = blogData; return {
blogs: blogs,
};
} catch (e) {
//ctx.error({ statusCode: 500, message: "出错啦" });//自定义错误页面
}
},
data () {
return {};
},
head () {//针对每一个页面,进行封装 head
return {
meta: [
{
name: "description",
content: "老张的哲学是个人博客,利用NUXT.js的服务端渲染方案"
}
]
};
},
mounted () {},
filters: {//过滤器,用来过滤时间字符串
timeFormat: function (time) {
if (!time) return "";
return time;
}
},
methods: {},
components: {
}
};
</script>

8、启动项目,就能看到我们的数据了

提示:如果报 less 错误,请安装

  npm install less less-loader

三、页面是如何一步步加载数据的 ? —— 存疑

这里先给大家抛出几个问题:

1、我们通过第一次编译的时候,生成 .nuxt 临时文件夹,是服务端渲染还是客户端渲染?

2、我们打开浏览器的 调试工具,发现每次修改,会都生成一些提示,当然这都是 webpack 的热加载,那这些又是什么含义呢?

3、既然是服务端和客户端一起渲染,我们的 页面路由 是如何匹配到的呢?

4、打开我们的页面 network 网络请求,发现有很多不知道的文件,都是怎样生成的呢?

其实这几个问题我们在之前的都通过文字的形式说过,因为时间的问题,今天暂时就说到这里,明天咱们再继续深入研究这个问题,顺便填充下详情页的数据。

四、CODE

https://github.com/anjoy8/Blog.Vue.Nuxt

从壹开始前后端分离 [ vue + .netcore 补充教程 ] 二九║ Nuxt实战:异步实现数据双端渲染的更多相关文章

  1. 从壹开始前后端分离 [ vue + .netcore 补充教程 ] 三十║ Nuxt实战:动态路由+同构

    上期回顾 说接上文<二九║ Nuxt实战:异步实现数据双端渲染>,昨天咱们通过项目二的首页数据处理,简单了解到了 nuxt 异步数据获取的作用,以及亲身体验了几个重要文件夹的意义,整篇文章 ...

  2. 从壹开始前后端分离 [ vue + .netcore 补充教程 ] 二七║ Nuxt 基础:框架初探

    缘起 哈喽大家好,又是周四了,俗话说周四来了,周末还远么哈哈,老张我也想下周请假,来个16天的大长假哟,不知道大家是怎么请假的,近来发现文章下边已经没有人评论了,赶脚比较凄凉了,大家看到的麻烦点个赞呀 ...

  3. 从壹开始前后端分离 [ vue + .netcore 补充教程 ] 二八║ Nuxt 基础:面向源码研究Nuxt.js

    前言 哈喽大家周五好,又是一个开开心心的周五了,接下来就是三天小团圆啦,这里先祝大家节日快乐咯,希望都没有加班哈哈,今天公司发了月饼,嗯~时间来不及了,上周应该搞个活动抽中几个粉丝发月饼的,下次吧,这 ...

  4. 从壹开始前后端分离 [ vue + .netcore 补程 ] 三十一║ Nuxt终篇:基于Vuex的权限验证探究

    缘起 哈喽大家好,今天周四啦,楼主明天要正式放假了,这里先祝大家节日快乐咯,希望在家里能继续研究点儿东西吧,今天呢是 nuxt 的最后一篇,主要是对权限登录进行研究,这一块咱们之前在说第一个项目的时候 ...

  5. 从壹开始前后端分离 [ Vue2.0+.NetCore2.1] 二十六║Client渲染、Server渲染知多少{补充}

    前言 书接上文,昨天简单的说到了 SSR 服务端渲染的相关内容<二十五║初探SSR服务端渲染>,主要说明了相关概念,以及为什么使用等,昨天的一个小栗子因为时间问题,没有好好的给大家铺开来讲 ...

  6. [转帖]从壹开始前后端分离【重要】║最全的部署方案 & 最丰富的错误分析

    从壹开始前后端分离[重要]║最全的部署方案 & 最丰富的错误分析 https://www.cnblogs.com/laozhang-is-phi/p/beautifulPublish-most ...

  7. 从壹开始前后端分离 [ Vue2.0+.NET Core2.1] 二十五║初探SSR服务端渲染(个人博客二)

    缘起 时间真快,现在已经是这个系列教程的下半部 Vue 第 12 篇了,昨天我也简单思考了下,可能明天再来一篇,Vue 就基本告一段落了,因为什么呢,这里给大家说个题外话,当时写博文的时候,只是想给大 ...

  8. 采用异步来实现重新连接服务器或者重新启动服务 C#中类的属性的获取 SignalR2简易数据看板演示 C#动态调用泛型类、泛型方法 asp .net core Get raw request. 从壹开始前后端分离[.NetCore 不定期更新] 38 ║自动初始化数据库

    采用异步来实现重新连接服务器或者重新启动服务 开启异步监听,不会导致主线程的堵塞,在服务异常断开后一直检测重新连接服务,成功连接服务后通知各个注册的客户端! #region 检测断线并重连OPC服务 ...

  9. 从壹开始前后端分离 [ Vue2.0+.NET Core2.1] 十五 ║Vue基础:JS面向对象&字面量& this字

    缘起 书接上文<从壹开始前后端分离 [ Vue2.0+.NET Core2.1] 十四 ║ VUE 计划书 & 我的前后端开发简史>,昨天咱们说到了以我的经历说明的web开发经历的 ...

随机推荐

  1. 一个靠谱的phpredisadmin文件

    http://download.csdn.net/detail/newjueqi/7227487

  2. mongoDB的安装及基本使用

    1.mongoDB简介 1.1 NoSQL数据库 数据库:进行高效的.有规则的进行数据持久化存储的软件 NoSQL数据库:Not only sql,指代非关系型数据库 优点:高可扩展性.分布式计算.低 ...

  3. Java 学习笔记 (七) Java 参数

    head first java page 74 实参: argument 形参: parameter 方法会运用形参, 调用的一方会传入实参. 实参是传给方法的值.当它传入方法后就成了形参, 参数跟局 ...

  4. 用beego开发服务端应用

    用beego开发服务端应用 说明 Quick Start 安装 创建应用 编译运行 打包发布 代码生成 开发文档 目录结构说明 使用配置文件 beego默认参数 路由设置 路由的表述方式 直接设置路由 ...

  5. BZOJ_2161_布娃娃_权值线段树

    BZOJ_2161_布娃娃_权值线段树 Description 小时候的雨荨非常听话,是父母眼中的好孩子.在学校是老师的左右手,同学的好榜样.后来她成为艾利斯顿第二 代考神,这和小时候培养的良好素质是 ...

  6. 为什么说 Java 程序员到了必须掌握 Spring Boot 的时候?

    Spring Boot 2.0 的推出又激起了一阵学习 Spring Boot 热,就单从我个人的博客的访问量大幅增加就可以感受到大家对学习 Spring Boot 的热情,那么在这么多人热衷于学习 ...

  7. ReentrantLock之公平锁源码分析

    本文分析的ReentrantLock所对应的Java版本为JDK8. 在阅读本文前,读者应该知道什么是CAS.自旋. 本文大纲 1.ReentrantLock公平锁简介 2.AQS 3.lock方法 ...

  8. SprintBoot的@ComponentScan“踩坑”

    主要的话说在前面:在启动日志中没有看到Controller对应的URL被映射,那么请检查你的Controller是否被Spring管理了.此次踩坑就是忘了SpringBoot在没配置@Componen ...

  9. 启动VMware虚拟机时总是出现许可证到期提示怎么办?

    不知道大家有没有遇到过这个问题,每次当你打开VMware虚拟机时它总是会提示许可证到期,需要注册,还会出现许多报红的代码,下面就让小编带大家解决一下吧~ VMware教程 如果Fusion本身无法启动 ...

  10. Gatling简单测试SpringBoot工程

    前言 Gatling是一款基于Scala 开发的高性能服务器性能测试工具,它主要用于对服务器进行负载等测试,并分析和测量服务器的各种性能指标.目前仅支持http协议,可以用来测试web应用程序和RES ...