第二周作业讲解

https://github.com/gyson/koa-simple-router

image.png

  1. 将YII文件夹复制过来,主要学习MVC框架结构


    image.png

    文件结构如下:
    wigdets = components都是组件的意思。


    image.png

    然后删除掉一些不需要用到的文件夹。
    assert里面是js css目录

    config里面是配置文件
    controller里面是路由
    models和后台请求有关(通信)
    将runtime更改为middleware,放中间件
    tests文件夹存放单元测试文件。
    views存放视图文件
    web存放前端的项目(前后端分离的时候)
    widgets改为components存放组件。

  2. controller里面新建index.js(路由的初始化)
    index.js就是我们的路由注册中心
    安装koa-simple-router
    npm install koa-simple-router
    将demo 里面的代码复制过去并且修改

     module.exports = (app) => {
         app.use(router(_ => {
             _.get('/', (ctx, next) => {
               ctx.body = 'hello'
             })
           }))
     }
    

但是我们需要把一些具体的操作都独自放在一个控制器里面,用class来控制。
所以,新建IndexController。

class IndexController{
    constructor(){

    }
    actionIndex(){
        return async(ctx, next) => {
            ctx.body = 'hello 大家好'
        }
    }
}

module.exports = IndexController;

定义 actionIndex并将IndexController导出。
此时,路由初始化文件可以更改为:

  const router = require('koa-simple-router')
  const IndexController = require('./IndexController');
  const indexController = new IndexController(); 

  module.exports = (app) => {
      app.use(router(_ => {
            _.get('/',indexController.actionIndex())
        }))
  }

app.js中的代码如下:

    const Koa = require('Koa');
    const app = new Koa();
    //注入我们的路由
    require('./controllers')(app);
    console.log('服务已启动');

    app.listen(3000);

启动服务,此时就可以看到


image.png
  1. 每次更改都要手动去重启服务,很麻烦,在node中可以使用supervisor进行设置,所以我们可以直接在package.json里面的script进行设置。

cross-env
能够提供一个设置环境变量的scripts,让你能够以unix方式设置环境变量,然后在windows上也能兼容运行。
https://blog.csdn.net/qq_26927285/article/details/78105510
他是运行跨平台设置和使用环境变量的脚本
是因为windows不支持NODE_ENV=development的设置方式。会报错,所以cross-env能够提供一个设置环境变量的scripts,让你能够以unix方式设置环境变量,然后在windows上也能兼容运行。
而且这个也能判断生产环境还是开发环境,后续能进行流清洗。

安装

    cnpm install cross-env --save

使用

  "dev": "cross-env NODE_ENV=development supervisor app.js"

设置当前环境是development,然后再执行supervisor app.js
执行之后就可以看见process.env.NODE_ENV打印出development

下面是package.json的标准教程
http://javascript.ruanyifeng.com/nodejs/packagejson.html#toc0

  1. 新建views视图
    view中新建index.html和test.html
    在layout中,对应的引入script和css文件应该是这样的


    image.png

一一对应的渲染,index.html继承于layout.html
swig模板的使用

Swig 使用 extends 和 block 来实现模板继承 layout.html
layout.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>{% block title %}{% endblock %}</title>
    {% block head %}{% endblock %}
    <!-- block head的位置是留给CSS -->
</head>
<body>
    {% block content %}{% endblock %}
    <!-- block content的位置是留给html的 -->
    {% block scripts %}{% endblock %}
    <!-- block scripts的位置是留给js的 -->
</body>
</html>

index.html

{% extends './layout.html' %}

{% block title %}新闻系统 {%endblock%}

{% block head %}
<link rel = "stylesheet" href="/styles/index.css">
{% endblock %}

{% block content %}
    {% include "../widgets/news/list.html" %}
{% endblock %}
{% block scripts %}
<script src = "/scripts/index.js"></script>
{% endblock %}

http://www.iqianduan.net/blog/how_to_use_swig
koa2-connect-history-api-fallback
https://www.npmjs.com/package/koa2-connect-history-api-fallback
koa2的一个中间件,用于处理vue-router使用history模式返回index.html,让koa2支持SPA应用程序。

https://www.npmjs.com/package/koa-swig
npm install koa-swig
cnpm install co(koa2.x需要加载co模块使得yield不是yield)
https://www.npmjs.com/package/koa-static
npm install koa-static
加载静态资源文件的。

  1. 容错
    利用洋葱模型的特性来做Koa的容错。
    在中间件中建立errorHandler.js

如果是404,就接入腾讯404小孩回家页面
https://www.qq.com/404/

  const errorHandler = {
    error(app){
        app.use(async (ctx,next) => {
            await next();
            //先让代码往前走
            if(404 != ctx.status){
                return 
            }
            //不承认网站404,百度会降权,将ctx.status修改为200,并修改ctx.body为找不到页面的页面
            //腾讯404脚本
            ctx.status = 404;
            ctx.body = '<script type="text/javascript" src="//qzonestyle.gtimg.cn/qzone/hybrid/app/404/search_children.js" charset="utf-8"></script>';
        })
    }
}

module.exports = errorHandler;

app.js

    const errorHandler = require('./middleware/errorHandler');

在路由之前加上

    errorHandler.error(app);

如果去营造服务器错误??就应该执行一个不存在的函数,这样就不是在编译时出错,而是可以跑起来。

在errorHandler.js里面增加

    app.use(async (ctx,next) => {
        try{
            await next();
        }catch(error){
            ctx.status = 500;
            console.log(error);
            ctx.body = '( ▼-▼ )';
        }
    });

在indexController.js里面添加一个不存在的函数(例如我们没有定义indexAction()),那么就会出现500错误,是服务器这边的错误。
这个时候在localhost里面可以看到( ▼-▼ )
在命令行可以看到下面的提示indexAction is not defined
ReferenceError: indexAction is not defined
at C:\document\frontEnd\京城一灯\note\0204homework_second_week\controllers\IndexController.js:8:13
at dispatch (C:\document\frontEnd\京城一灯\note\0204homework_second_week\node_modules_koa-simple-router@0.2.0@koa-simple-router\index.js:186:18)
at Router._lookup (C:\document\frontEnd\京城一灯\note\0204homework_second_week\node_modules_koa-simple-router@0.2.0@koa-simple-router\index.js:198:12)
at C:\document\frontEnd\京城一灯\note\0204homework_second_week\node_modules_koa-simple-router@0.2.0@koa-simple-router\index.js:138:21
at dispatch (C:\document\frontEnd\京城一灯\note\0204homework_second_week\node_modules_koa-compose@4.1.0@koa-compose\index.js:42:32)
at app.use (C:\document\frontEnd\京城一灯\note\0204homework_second_week\middleware\errorHandler.js:13:19)
at dispatch (C:\document\frontEnd\京城一灯\note\0204homework_second_week\node_modules_koa-compose@4.1.0@koa-compose\index.js:42:32)
at app.use (C:\document\frontEnd\京城一灯\note\0204homework_second_week\middleware\errorHandler.js:5:23)
at dispatch (C:\document\frontEnd\京城一灯\note\0204homework_second_week\node_modules_koa-compose@4.1.0@koa-compose\index.js:42:32)
at serve (C:\document\frontEnd\京城一灯\note\0204homework_second_week\node_modules_koa-static@5.0.0@koa-static\index.js:53:15)

error的捕获是特别重要的,最好可以打印出日志。
log4js
https://www.npmjs.com/package/log4js
在服务器上面也应该打印出日志。

  1. 先安装log4js
    app.js里面定义

     const log4js = require('log4js');
    

再添加配置

log4js.configure({
  appenders: {
    cheese: { 
      type: 'file', filename: 'cheese.log'
    }
  },
  categories: { 
    default: { 
      appenders: ['cheese'], level: 'error' 
    } 
  }
});
const logger = log4js.getLogger('cheese');
errorHandler.error(app,logger);

此时,在errorHandler里面添加logger

error(app,logger){
    app.use(async (ctx,next) => {
        try{
            await next();
        }catch(error){
            ctx.status = 500;
            //console.log(error);
            logger.error(error);
            ctx.body = '( ▼-▼ )';
        }
    });

将error打印出来。

  1. model和php连接
    在model里面新建一个index.js
    新建一个utils文件夹,工具包的意思。

为什么要注释?因为要输出文档,我们可以用jsdoc来生成相关的说明文档,避免撕逼
https://www.npmjs.com/package/jsdoc

  • 安装
    cnpm install --save-dev jsdoc

  • 在package.json里面的scripts里面加上一个命令

       "docs": "jsdoc ./**/*.js -d ./docs/jsdocs"
    

执行npm run docs
在linux系统上面的**是可以起作用的,但是在windows上面是不起作用的。
所以改成下面:

     "docs": "jsdoc ./models/index.js -d ./docs/jsdocs"

才可以在window上面跑。
执行npm run docs,可以看到docs目录下面生成了一个jsdocs的文件夹,打开index_.html用浏览器打开就可以看到文档了。
controller可以不写文档,但是model就要写文档。

image.png

论写docs邀功的重要性
在安装依赖的时候注意到了一个问题:
npm install 在安装 npm 包时,有两种命令参数可以把它们的信息写入 package.json 文件,一个是npm install--save另一个是 npm install –save-dev,他们表面上的区别是--save 会把依赖包名称添加到 package.json 文件 dependencies 键下,--save-dev 则添加到 package.json 文件 devDependencies 键下,譬如:

{
 "dependencies": {
    "vue": "^2.2.1"
  },
  "devDependencies": {
    "babel-core": "^6.0.0",
    "babel-lo
}
}

它们真正的区别是,npm自己的文档说dependencies是运行时依赖,devDependencies是开发时的依赖。即devDependencies 下列出的模块,是我们开发时用的,比如 我们安装 js的压缩包gulp-uglify 时,我们采用的是 “npm install –save-dev gulp-uglify ”命令安装,因为我们在发布后用不到它,而只是在我们开发才用到它。dependencies 下的模块,则是我们发布后还需要依赖的模块,譬如像jQuery库或者Angular框架类似的,我们在开发完后后肯定还要依赖它们,否则就运行不了。

另外需要补充的是:
正常使用npm install时,会下载dependencies和devDependencies中的模块,当使用npm install –production或者注明NODE_ENV变量值为production时,只会下载dependencies中的模块。

models下面的index.js代码如下:

/**
 * @fileoverview 实现index的数据模型
 * @author zty
 */
const SafeRequest = require('../utils/SafeRequest.js')
/**
 * Index类 获取后台关于图书相关的数据类
 * class
 */
class Index{
/**
 * @constructor 
 * @param{string} app 参数是字符串,是Koa执行上下文
 */
constructor(app){

}
/**
 * 获取后台全部图书的数据方法
 * @param{*} option 配置项
 * @example
 * return new Promise
 * getData(options)
 */
getData(options){
    return {};
}
}
module.exports = Index;

一直把ladash拼错了尴尬。

  1. 多个路由的配置
    在controller里面新建一个文件TestController,类名与文件名称相对应

     class TestController{
         constructor(){
    
     }
         actionIndex(){
             return async(ctx, next) => {
                 //ctx.body = 'hello 我是zty'
                 ctx.body = await ctx.render("index",{
                     data:'jjjj'
                 })
             }
         }
     }
    
     module.exports = TestController;
     //将模块暴露出去。
    

    然后在路由的注册中心index.js上面引入,并对新路由进行定义。

     const TestController = require('./TestController');
     const testController = new TestController(); 
    
    
     module.exports = (app) => {
         app.use(router(_ => {
             _.get('/',indexController.actionIndex())
           }))
         app.use(router(_ => {
             _.get('/test',testController.actionIndex())
           }))
     }
    

    之后输入相对应的路由就能到不同的页面了

  2. 生成JSON接口
    在我们之前的yii项目里面的controller里面的libaryController进行更改就行了。

     /**
      * Lists all Library models.
      * @return mixed
      */
     public function actionIndex()
     {
         $searchModel = new LibrarySearch();
         $dataProvider = $searchModel->search(Yii::$app->request->queryParams);
         //将输出指定为JSON格式
         Yii::$app->response->format = Response::FORMAT_JSON;
         return $dataProvider->getModels();
     }
    

重点就在于

    Yii::$app->response->format = Response::FORMAT_JSON;
    return $dataProvider->getModels();

看到了一个错误:Class 'app\controllers\Response' not found
原来是没有引入这个东西
应该在libaryController头部加上
use yii\web\Response;
然后就没有问题了。
之后在浏览器地址栏输入http://localhost/Yii/test3/web/index.php?r=library
library对应LibrartController这个控制器

image.png

  1. 容错
    安装node-fetch

     cnpm install node-fetch --save
    

https://www.npmjs.com/package/node-fetch

继续编辑utils中SafeRequest,对一些请求进行容错。

    const fetch = require("node-fetch");
    const config = require("../config");

    //设置传输进来的url和baseURL
    class SafeRequest{
        constructor(url){
            this.url = url;
            this.baseURL = config.baseURL;
        }
        fetch(options){
            let ydfetch = fetch(this.baseURL + this.url);
            return new Promise((resolve,reject) =>{
                let result = {
                    code:0,
                    message:"",
                    data:[]
                }
                ydfetch
                .then(res => res.json())
                .then((json) => {
                    result.data = json;
                    resolve(result);
                }).catch((error) => {
                    result.code = 1;
                    result.message = "node-fetch与后端通讯异常";
                    
                })
            })
        }
    }


    module.exports = SafeRequest;

将indexController中的actionIndex修改为

    const Index = require("../models/index");
    class IndexController{
        constructor(){

        }
        actionIndex(){
            //SSR 
            return async(ctx, next) => {
                const index = new Index();
                const result = await index.getData();
                ctx.body = await ctx.render("index",{
                    // data: '欢迎来到新世界'
                    data:result.data
                })
            }
        }
    }

    module.exports = IndexController;

将取得的data渲染出来。

好尴尬,把assets写错了

  1. 在views里面添加add.html文件(swig模板)
    只有这个add.html需要用到vue,所以在add.html的swig模板里面引入这个src。

    {% extends './layout.html' %}
    
    {% block title %}新增新闻页面{%endblock%}
    
    {% block head %}
    <link rel="stylesheet" type="text/css" href="/styles/index.css" /> 
    {% endblock %}
    
    {% block content %}
        {% include "../components/news/add.html" %}
    {% endblock %}
    {% block scripts %}
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <script src = "/scripts/add.js"></script>
    {% endblock %}
    

在components里面添加add.html文件(纯纯的html文件)

<div>
<p>新增新闻页面</p>
<div id="app-6">
    <p>{{ message }}</p>
    <input v-model="message">
</div>
</div>

add.js代码

var app6 = new Vue({
    el: '#app-6',
    data: {
      message: 'Hello Vue!'
    }
  })

但是这里的swig和vue的{{}}冲突了,所以我们的p不能被渲染出来
这个时候就可以在app.js里面进行设置了,在app.context.render里面加上

    varControls:["[[","]]"],

将swig中的渲染{{}}改成[[ ]],

http://www.staticfile.org/
里面存放了很多开源的库
可以搜索你想要的一些CDN资源,jquery等

还有一个问题
V8对一些新的API都是有优化的,比如map,set等
如果不管三七二十一就把ES6代码用babel转换为ES5代码,那就白瞎了敲那些ES6代码
所以就可以利用type=module来进行判断,如果支持的话就可以直接加载ES6模块不然就可以加载babel转换的ES5模块。
https://babeljs.io/repl(babel相关的网站)
浏览器加载ES6模板,也是使用<script>标签,但是要加入type = “module”属性。

    <script type= 'module' src = "foo.js"> </script>

网页中插入一个模块foo.js,由于type属性设为module,所以浏览器知道这是一个 ES6 模块。
浏览器对于带有type="module"的<script>,都是异步加载,不会造成堵塞浏览器,即等到整个页面渲染完,再执行模块脚本,等同于打开了<script>标签的defer属性。

https://www.imooc.com/article/20630?block_id=tuijian_wz

<script type="module" src="foo.js"></script>

<script type="module" src="foo.js" defer></script>
所以,我们可以这样解决这个问题:

    <script type="module">
        import("http://localhost:3000/scripts/add.js").then((_) =>{
            new _.default.fn()
        })
    </script>

    <script type="nomodule" src="scripts/add-bundle.js">
    </script>

其中add-bundle.js就是我们用Babel编译之后的文件。
swig一段时间内会缓存模板,
如果在调试的时候需要不保留缓存,应该怎么做?
打开app.js文件
我们就会看到


image.png

这里面的cache的memory就是缓存,
在config里面进行设置,在这里我们设置为
cache: config.cacheModel


image.png

system.js与微前端
https://www.javascriptcn.com/read-32574.html
微前端:
https://blog.csdn.net/qappleh/article/details/80928434
还有@babel/plugin-transform-modules-systemjs这个东西
https://www.npmjs.com/package/@babel/plugin-transform-modules-systemjs
让babel支持systemjs

add.html

    {% extends './layout.html' %}

    {% block title %}新增新闻页面{%endblock%}

    {% block head %}
    <link rel="stylesheet" type="text/css" href="/styles/index.css" /> 
    {% endblock %}

    {% block content %}
        {% include "../components/news/add.html" %}
    {% endblock %}
    {% block scripts %}
    <!-- <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> -->
    <script src="https://cdn.staticfile.org/jquery/3.3.1/jquery.js"></script>
    <script type="module">
        console.log("我支持module");
        import("/scripts/add.js").then((_)=>{
            const create = new _.default;
            create.fn();
        })
    </script>
    {% endblock %}

add.js

    class Create{
        constructor(){
            this.btn = $("#js-btn");
        }
        fn(){
            this.btn.click(function(){
                fetch("添加新闻页面");
            })
        }
    }

    export default Create;
  1. 函数式编程之防抖动
    针对一些会频繁触发的事件如scroll、resize,如果正常绑定事件处理函数的话,有可能在很短的时间内多次连续触发事件,十分影响性能。

    因此针对这类事件要进行防抖动或者节流处理
    防抖动

    它的做法是限制下次函数调用之前必须等待的时间间隔。正确实现 debouncing 的方法是将若干个函数调用合成 一次,并在给定时间过去之后仅被调用一次。
    underscore.js
    https://www.liaoxuefeng.com/wiki/001434446689867b27157e896e74d51a89c25cc8b43bdb3000/001450372452505599881a3debd4becbe1591a94950fbb8000

出现了这个错


image.png

原来是参数写错位置了。
此时的add.js应该是这样的

    class Create{
      constructor(){
          this.btn = $("#js-btn");
      }
      fn(){
          this.btn.click(common.throttle(function(){
              fetch("添加新闻页面");
          },2000))
        }
  }

common.js作为全局的js,在layout.html就引入了
common.js

    function common(){}
    common._version = 0.1;
    common.throttle = function(fn,wait){
        var timer;
        return function (...args){
            if(!timer){
                timer = setTimeout(()=>timer = null,wait);
                //方便改变this指向,改变更加灵活
                return fn.apply(this,args);
            }
        }
    }

export default Create;
验证之后发现防抖是有用的。~~

  1. rize.js
    https://github.com/g-plane/rize
    https://rize.js.org/zh-CN/#%E5%BF%AB%E9%80%9F%E5%BC%80%E5%A7%8B

  2. node 跑post接口
    在safeRequest中,添加对option参数的判断

    fetch(options){
    let ydfetch = fetch(this.baseURL + this.url);
    if(options.params){
        ydfetch = fetch(this.baseURL + this.url,{
            method:params.method,
            body:options.params
        })
    }
    }
    

在model中的index.js中要添加一个saveData的方法。

   saveData(options){
    //create对应actionCreate(YII中的控制器里面的)
    const safeRequest = new SafeRequest("library/create");
    return safeRequest.fetch({
        method:POST,
        params:options.params
    });
}

fetch:
https://developer.mozilla.org/zh-CN/docs/Web/API/Fetch_API/Using_Fetch

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,463评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,868评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,213评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,666评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,759评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,725评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,716评论 3 415
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,484评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,928评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,233评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,393评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,073评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,718评论 3 324
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,308评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,538评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,338评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,260评论 2 352

推荐阅读更多精彩内容

  • 原文链接:http://www.jianshu.com/p/2a9367afe9e7 1510997059(1)....
    悬笔e绝阅读 5,459评论 0 0
  • 框架提出的背景 ES6/7带来的变革 自ES6确定和ES7中async/await开始普及,Node的发展变得更加...
    宫若石阅读 8,497评论 1 14
  • 概要 64学时 3.5学分 章节安排 电子商务网站概况 HTML5+CSS3 JavaScript Node 电子...
    阿啊阿吖丁阅读 9,172评论 0 3
  • 今天是宝宝的第4天拉肚子了,下午已经做好了第二天要去看医生的准备了,医院都挂好了两个,一个是中大五院,一个是人民医...
    EvaingWu阅读 143评论 1 0
  • 作业呀,作业 你为什么要布置那么多呢? 作业说: 我必须要布置多。 因为我不布置多你们怎么变成天才呢。 哦原来是这...
    李秉宸Andy阅读 145评论 0 1