zoukankan      html  css  js  c++  java
  • 使用react+redux+react-redux+react-router+axios+scss技术栈从0到1开发一个applist应用

    摘要:

    先看效果图

    github地址

    github仓库

    在线访问

    初始化项目

    #创建项目
    create-react-app applist
    #如果没有安装create-react-app的话,先安装
    npm install -g create-react-app
    

    目录结构改造

    |--config    
    |--node_modules
    |--public
    |--scripts
    |--src
        |-----api   //api接口
        |-----components  //组件
        |-----pages  //页面
        |-----plugins   //插件相关 axios
        |-----router  //路由
        |-----store   //redux
        |-----styles   //公共样式
        |-----utils   //工具包
        |-----index.js   //入口
    |--package.json
    

    Vscode插件安装

    所谓工欲善其事,必先利其器。这里我们推荐一些好用的vscode插件

    1. 代码提示类插件
    1.1 Reactjs code snippets
    1.2 React Redux ES6 Snippets
    1.3 React-Native/React/Redux snippets for es6/es7
    1.4 JavaScript (ES6) code snippets(es6代码片段)
    1.5 Typescript React code snippets(这是tsx的react组件片段)
    
    2. 美化类插件
    2.1 One Dark Pro(atom风格主题)
    2.2 vscode-icons(文件图标)
    
    3. 其他实用类插件
    3.1 Beautify css/sass/scss/less(样式代码格式化)
    3.2 npm Intellisense(对package.json内中的依赖包的名称提示)
    3.3 Path Intellisense(文件路径补全)
    3.4 cssrem(px转换为rem)
    3.5 CSS Modules(对使用了css modules的jsx标签的类名补全和跳转到定义位置)
    
    4.vscode配置设备同步
    Settings Sync
    有了它就不用每次换个开发环境又重新配置一遍vscode了
    
    
    5.另外,react的jsx补全html标签需要在vscode单独设置一下
    
    首选项-设置-搜索‘includeLanguages’-编辑settings.json添加如下代码即可
    "emmet.includeLanguages": {
            "javascript": "javascriptreact"
        }
    

    最后,安装完插件之后,以下两个快捷键可能会经常使用

    rcc 生成有状态的组件代码块
    rfc 生成无状态的组件代码块

    使用axios插件请求数据并封装api请求

    1、安装

    npm isntall axios --save
    

    2、创建axios.js文件

    主要是用来创建axios实例,添加请求拦截,全局处理一些业务逻辑,例如全局loading展示,返回状态码处理等 。
    具体的配置可查看axios

    3、创建api目录,并新建index.js文件

    import axios from '../plugins/axios';
    
    let api = {
      // app列表
      appListData(params){
        return axios.get('/mock/appListData.json', params);
      },
      // 推荐
      recommendData(params) {
        return axios.get('/mock/recomendData.json', params);
      },
      // 搜索
      lookUp(params) {
        return axios.get('/mock/lookUp.json', params);
      }
    }
    
    export default api
    

    4、组件中使用

    import $api from '../api/index.js';
    
    $api.recommendData({}).then((response) => {
          let feed = response.feed;
          this.setState({
            recommendList: feed.entry
          })
        }).catch(err => {
          console.log(err)
        })
    

    axios拦截器添加全局loading,多个请求合并一个loading

    通过配置axios的过滤器,可以拦截用户请求,我们在这里添加全局loading,返回时在隐藏loading的显示。这里有个问题需要解决的是,如果同一时刻我们发起多个请求,那么会出现多个loading的问题,解决办法就是,通过设定一个count变量来记录当前接口请求数量,当count为0时再结束loading。

    showFullScreenLoading、tryHideFullScreenLoading要干的事儿就是将同一时刻的请求合并,声明一个变量needLoadingRequestCount,每次调用showFullScreenLoading方法 needLoadingRequestCount + 1。调用tryHideFullScreenLoading()方法,needLoadingRequestCount - 1。needLoadingRequestCount为 0 时,结束 loading。
    另外还可以通过参数形式设定不需要显示loading的请求,在拦截处通过判断来显示

    1、在common.js文件中添加如下代码

    import { Toast } from 'antd-mobile'
    /**
     * 显示loading
     */
    function showLoading(){
      Toast.loading('加载中...', 0);
    }
    
    /**
     * 隐藏loading
     */
    function hideLoading(){
      Toast.hide();
    }
    
    
    /**
     * 合并请求,同一时刻只显示一个loading
     */
    let needLoadingRequestCount = 0
    export function showFullScreenLoading() {
      if (needLoadingRequestCount === 0) {
        showLoading()
      }
      needLoadingRequestCount++
    }
    
    export function hideFullScreenLoading() {
      if (needLoadingRequestCount <= 0){
        return
      }
      needLoadingRequestCount--
      if (needLoadingRequestCount === 0) {
        hideLoading()
      }
    }
    

    2、在axios中使用

    import { showFullScreenLoading, hideFullScreenLoading} from '../utils/commons'
    
    // Add a request interceptor
    _axios.interceptors.request.use(function (config) {
      // Do something before request is sent
      showFullScreenLoading();
      return config;
    }, function (error) {
      // Do something with request error
      return Promise.reject(error);
    });
    
    // Add a response interceptor
    _axios.interceptors.response.use(function (response) {
      // Do something with response data
      setTimeout(() => {
        hideFullScreenLoading();
      }, 1000);
      return response.data;
    }, function (error) {
      // Do something with response error
      return Promise.reject(error);
    });
    

    配置react-router

    在React中,常用的有两个包可以实现这个需求,那就是react-router和react-router-dom,这两个不同之处就是后者比前者多出了 这样的 DOM 类组件,所以我们只需要使用react-router-dom就可以了

    1、安装

    npm install react-router-dom --save-dev
    

    2、创建路由组件router/index.js

    import React from 'react';
    import { HashRouter, Route, Switch } from 'react-router-dom';
    import Home from '../pages/Home';
    import Profile from '../pages/profile/Profile';
    
    
    const BasicRoute = () => (
      <HashRouter>
        <Switch>
          <Route exact path="/" component={Home} />
          <Route exact path="/profile" component={Profile} />
        </Switch>
      </HashRouter>
    );
    
    export default BasicRoute;
    

    将两个页面组件Home和Detail使用Route组件包裹,外面套用Switch作路由匹配,当路由组件检测到地址栏与Route的path匹配时,就会自动加载响应的页面

    3、入口文件index.js引入router组件

    import React from 'react';
    import ReactDOM from 'react-dom';
    import Router from './router/router';
    
    ReactDOM.render(
      <Router/>,
      document.getElementById('root')
    );
    

    4、路由跳转

    this.props.history.push("/search/result");
    

    添加vw适配手机屏幕

    1、默认webpack的配置是隐藏的,通过eject 显示webpack配置(此操作不可逆)

    npm run eject  
    
    

    2、安装postcss

    npm install --save postcss-aspect-ratio-mini postcss-px-to-viewport postcss-write-svg postcss-cssnext postcss-viewport-units cssnano
    

    3、webpack配置

    修改webpack.config.js,添加如下代码:

    {
            // Options for PostCSS as we reference these options twice
            // Adds vendor prefixing based on your specified browser support in
            // package.json
            loader: require.resolve('posREtcss-loader'),
            options: {
              // Necessary for external CSS imports to work
              // https://github.com/facebook/create-react-app/issues/2677
              ident: 'postcss',
              plugins: () => [
                require('postcss-flexbugs-fixes'),
                require('postcss-preset-env')({
                  autoprefixer: {
                    flexbox: 'no-2009',
                  },
                  stage: 3,
                }),
                // Adds PostCSS Normalize as the reset css with default options,
                // so that it honors browserslist config in package.json
                // which in turn let's users customize the target behavior as per their needs.
                postcssNormalize(),
                // 添加vw配置 start
                postcssAspectRatioMini({}),
                postcssPxToViewport({
                  viewportWidth: 750, // (Number) The width of the viewport.
                  viewportHeight: 1334, // (Number) The height of the viewport.
                  unitPrecision: 3, // (Number) The decimal numbers to allow the REM units to grow to.
                  viewportUnit: 'vw', // (String) Expected units.
                  selectorBlackList: ['.ignore', '.hairlines', '.list-row-bottom-line', '.list-row-top-line'], // (Array) The selectors to ignore and leave as px.
                  minPixelValue: 1, // (Number) Set the minimum pixel value to replace.
                  mediaQuery: false // (Boolean) Allow px to be converted in media queries.
                }),
                postcssWriteSvg({
                  utf8: false
                }),
                postcssPresetEnv({}),
                // postcssViewportUnits({
                //  filterRule: rule => rule.selector.indexOf('::after') === -1 && rule.selector.indexOf('::before') === -1 && rule.selector.indexOf(':after') === -1 && rule.selector.indexOf(':before') === -1
                // }),
                postcssViewportUnits({}),
                cssnano({
                  "cssnano-preset-advanced": {
                    zindex: false,
                    autoprefixer: false
                  },
                })
                // 添加vw配置 end
              ],
              sourceMap: isEnvProduction && shouldUseSourceMap,
            },
          },
    

    这里,配置之后运行项目会发现有个报错
    ReferenceError: postcssPresetEnv is not defined
    是因为我们没有引入postcssPresetEnv

    安装并添加以下依赖

    npm install postcss-preset-env --save-dev
    const postcssPresetEnv = require('postcss-preset-env');
    

    配置好了之后,再访问我们的页面,可以发现已经自动转成vw了

    4、兼容低版本android,加入viewport-units-buggyfill hack

    下载viewport-units-buggyfill.min.js到public文件夹下面,修改index.html添加如下代码:

    <script src='%PUBLIC_URL%/viewport-units-buggyfill.min.js'></script>
        <script>
          window.onload = function () {
            window.viewportUnitsBuggyfill.init({
              hacks: window.viewportUnitsBuggyfillHacks
            });
          }
        </script>
    
    或者使用cdn的方式引入
    <script src="//g.alicdn.com/fdilab/lib3rd/viewport-units-buggyfill/0.6.2/??viewport-units-buggyfill.hacks.min.js,viewport-units-buggyfill.min.js"></script>
    

    安装scss

    npm install node-sass sass-loader --save
    
    

    在React中的几种样式写法

    行内样式、声明样式、引入样式、CSS Modules模块化

    1、行内样式

    <div style={{ background: '#eee',  '200px', height: '200px'}}>
            <p style= {{color:'red', fontSize:'40px'}}>行内样式</p>
          </div>
    

    2、声明样式

    const style1={    
          background:'#eee',
          '200px',
          height:'200px'
        }
    
    
    <div style={style1}>
            <p style= {style2}>行内样式</p>
          </div>
    

    3、引入样式

    .person{
         60%;
        margin:16px auto;
    }
    import './Person.css';
    <div className='person'>
            <p>person:Hello world</p>
          </div> 
    

    4、css module
    CSS Modules 的做法就是通过配置将.css文件进行编译,编译后在每个用到css的组件中的css类名都是独一无二的,从而实现CSS的局部作用域。
    在create-react-app2.0之前的版本,配置CSS Modules是需要eject弹出webpack来配置的,幸运的是,create-react-app自从2.0.版本就已经开始支持CSS Modules了

    (1)局部样式

    命名规则: xxx.module.css     
    
                      
                     引入方式 import xxx from 'xxx.module.css'
    
                      用法:<div className={xxx.styleName}>
    

    (2)全局样式

    命名规则: xxx.css   
    
                       引入方式 import ‘xxx.css’
    
                       用法:<div className='styleName'>
    

    全局样式与局部样式混合使用:

    <div className={`styleName ${xxx['styleName']}`} >  
    

    其中styleName表示全局样式 ${xxx['styleName']表示局部样式,注意{ }内使用模板字符串 ·

    5、css多类名写法

    (1) css module模块中写法

    <div className={[`${styles.sideInBox}`,`${styles.sideTitleBox}`].join(' ')}></div>    
    

    (2) 如果是全局样式写法

    className={`title ${index === this.state.active ? 'active' : ''}`}
    

    React条件渲染的几种方式

    参考https://www.cnblogs.com/xiaodi-js/p/9119826.html

    1、条件表达式

    <div>
          {isLoggedIn ? (
            <LogoutButton onClick={this.handleLogoutClick} />
          ) : (
            <LoginButton onClick={this.handleLoginClick} />
          )}
        </div>
    

    2、&&操作符

    <div>
          <h1>Hello!</h1>
          {unreadMessages.length > 0 &&
            <h2>
              You have {unreadMessages.length} unread messages.
            </h2>
          }
        </div>
    

    3、列表遍历

    jxs的语法,js代码要放在{}里面,html标签使用return ()包裹

    return (
                <div className='appList-container'>
                    <ul className='list'>
                        {
                            this.props.list.map((item, index) => {
                                return (
                                    <li className='list-item' key={index}>
                                        <div className='app-index'>{index+1}</div>
                                        <img className='app-icon' src={item['im:image'][0].label} alt="" />
                                        <div className='app-info'>
                                            <div className='app-name'>{item['im:name'].label}</div>
                                            <div className='app-categray'>{item.category.attributes.label}</div>
                                        </div>
                                    </li>
                                )
                            })
                        }
                    </ul>
                </div>
            );
    

    事件处理

    <button onClick={this.handleClick}>ck me
          </button>
    

    两种事件传参方式

    <button onClick={(e) => this.deleteRow(id, e)}>Delete Row</button>
    <button onClick={this.deleteRow.bind(this, id)}>Delete Row</button>
    

    获取input表单值

    两种方法,受控组件和非受控组件。
    推荐使用受控组件,即通过this.state获取,因为其符合react规范;

    非受控组件,给标签指定ref属性

    <input className='search-bar' type="text" ref='keyword' onKeyUp={this.appSearch.bind(this)}>
    appSearch(e){
            let keyword = this.refs.keyword.value
        }
    

    react中使用防抖

    appSearch = debounce(() => {
        }, 500);
    

    组合组件

    参考https://www.jianshu.com/p/0b005dc60bda

    在react开发中,在某些场景会遇到如下组件嵌套形式的开发,例如group和cell或者RadioGroup、RadioOption

    <RadioGroup name="option">
        <RadioOption label="选项一" value="1" />
        <RadioOption label="选项二" value="2" />
      </RadioGroup>,
    

    state定义及赋值

    constructor(props) {
        super(props);
        this.state = { 
          appList:[]
        };
      }
    this.setState({
            appList: feed.entry
          })
    

    父子组件传参

    1、父传子

    <AppList list={this.state.appList}></AppList>
    

    在子组件获取值

    this.props.list
    

    2、子传父

    触发父组件事件

            this.props.appSearch(keyword);
    

    父组件监听事件

     <Search appSearch={this.appSearch.bind(this)}></Search>
    

    引入redux和react-redux、redux-thunk

    文档
    https://react-redux.js.org/introduction/quick-start
    http://cn.redux.js.org/docs/introduction/ThreePrinciples.html

    类似vuex,redux是一个数据状态管理工具,但是用法和vuex有些区别

    react-redux帮助你完成数据订阅,redux-thunk可以放你实现异步action,redux-logger是redux的日志中间件

    redux-thunk 是一个比较流行的 redux 异步 action 中间件。redux-thunk 帮助你统一了异步和同步 action 的调用方式,把异步过程放在 action 级别解决,对 component 没有影响

    import { createStore, applyMiddleware } from 'redux';
    import thunk from 'redux-thunk';
    import rootReducer from './reducers';
    // 创建store的时候,第二个参数是中间件,redux-thunk提供了一个thunk中间件,用于处理异步的action
    export default createStore(
      rootReducer,
      applyMiddleware(thunk)
    );
    

    1、对redux的理解

    (1)单一数据源:
    整个应用的 state 被储存在一棵 object tree 中,并且这个 object tree 只存在于唯一一个 store 中
    (2)State只读:
    唯一改变 state 的方法就是触发 action,action 是一个用于描述已发生事件的普通对象
    (3)执行修改:
    为了描述 action 如何改变 state tree ,你需要编写 reducers
    Reducer 只是一些纯函数,它接收先前的 state 和 action,并返回新的 state
    随着应用变大,你可以把它拆成多个小的 reducers,分别独立地操作 state tree 的不同部分

    2、对mapStateToProps和mapDispatchToProps的理解

    使用 React Redux 库的 connect() 方法来生成容器组件前,需要先定义 mapStateToProps 这个函数来指定如何把当前 Redux store state 映射到展示组件的 props 中。
    除了读取 state,容器组件还能分发 action。类似的方式,可以定义mapDispatchToProps() 方法接收 dispatch() 方法并返回期望注入到展示组件的 props 中的回调方法。它可以是一个函数,也可以是一个对象。

    // 将state 映射到展示组件的 props 中
    const mapStateToProps = state => {
      return {
        searchList: state.searchList
      }
    }
    
    const mapDispatchToProps = dispatch => {
      return {
        saveSearchList: searchList => dispatch(saveSearchList(searchList))
      }
    }
    
    // export default SearchResult;
    // 通过connect生成容器组件
    export default connect(
      mapStateToProps,
      mapDispatchToProps
    )(SearchResult)
    

    3、安装redux react-redux redux-thunk

    npm install --save redux react-redux redux-thunk
    npm install --save-dev redux-logger
    

    4、使用react-hot-loader实现局部热更新

    #安装
    npm install --save-dev react-hot-loader
    #使用
    import { AppContainer } from 'react-hot-loader';
    import Route from './router/';
    const render = Component => {
    ReactDOM.render(
        <AppContainer>
            <Component />
        </AppContainer>,
        document.getElementById("root"));
    }
    render(Route);
    

    引入antd-mobile移动端UI框架

    antd-mobile文档
    https://mobile.ant.design/index-cn

    1、安装依赖

    npm install antd-mobile --save
    

    2、安装 babel-plugin-import

    npm install babel-plugin-import --save
    

    3、在 package.json 配置 antd-mobile 的按需加载(在babel下添加)

    "plugins": [
        [
          "import",
          {
            "libraryName": "antd-mobile",
            "style": "css"
          }
        ]
      ],
    

    4、组件中使用

    import { Toast,Button } from 'antd-mobile'
    <Button type="primary">primary</Button>
    

    上拉刷新及加载更多

    这里使用react-pullload这个库

    1、安装

    npm install --save react-pullload
    

    2、使用

    import ReactPullLoad, { STATS } from "react-pullload";
    import "react-pullload/dist/ReactPullLoad.css";
    
    
    constructor(props) {
        super(props);
        this.state = { 
          appList: [],
          appListAll: [],
          recommendList:[],
          hasMore: true,
          action: STATS.init,
          pageSize:10,
          page:1
        };
      }
    
    handleAction = action => {
        //new action must do not equel to old action
        if (action === this.state.action) {
          return false;
        }
        if (action === STATS.refreshing) {
          this.handRefreshing();
        } else if (action === STATS.loading) {
          this.handLoadMore();
        } else {
          //DO NOT modify below code
          this.setState({
            action: action
          });
        }
      };
    
      // 刷新
      handRefreshing = ()=>{
        this.setState({
          action: STATS.refreshing
        });
        this.getAppList();
      }
    
      // 加载更多
      handLoadMore = ()=>{
        if (STATS.loading === this.state.action) {
          return false;
        }
        //无更多内容则不执行后面逻辑
        if (!this.state.hasMore) {
          return;
        }
        // 显示正在加载
        this.setState({
          action: STATS.loading
        });
        let page = this.state.page+1;
        setTimeout(() => {
          this.getPageData(page);
        }, 1500);
      }
    
    
    render() {
        return (
          <div className='container'>
            <div className='search-bar'>
              <Search onFoucs={this.onFoucs.bind(this)}></Search>
            </div>
            <ReactPullLoad
              className="block"
              isBlockContainer={true}
              downEnough={100}
              action={this.state.action}
              handleAction={this.handleAction}
              hasMore={this.state.hasMore}
              distanceBottom={100}>
              <Recommend list={this.state.recommendList}></Recommend>
              <AppList list={this.state.appList}></AppList>
            </ReactPullLoad>
          </div>
        );
      }
    

    因为是使用的mock数据,获取的是全部数据,所以这里采用前端分页的方式加载更多

    // 分页加载
      getPageData(page){
        let resultList = [], list = [];
        let appListAll = this.state.appListAll;
        let pageSize = this.state.pageSize;
        let totalPage = Math.ceil(appListAll.length / pageSize);//总页数
        let startIndex = pageSize * (page - 1);
        let endIndex = pageSize * page;
        for (let i = startIndex; i < endIndex; i++) {
          resultList.push(appListAll[i]);
        }
        if (page >= totalPage){
          this.setState({ hasMore: false});
        }
        if (page===1){
          list = resultList;
        }else{
          list = this.state.appList.concat(resultList);
        }
        this.setState({
          appList: list,
          page: page,
          pageSize: pageSize,
          action: STATS.reset
        })
      }
    

    图片懒加载

    http://npm.taobao.org/package/react-lazy-load

    1、安装

    npm install --save react-lazy-load
    

    2、使用

    import LazyLoad from 'react-lazy-load';
    
    <LazyLoad offsetVertical={100}>
      <img className='app-icon' src={item['im:image'][0].label} alt="" />
    </LazyLoad>
    

    问题总结

    1、在react中进入页面自动获取input输入焦点 ,弹出键盘
    input中设置ref属性(非受控组件),通过 this.refs.keyword调用

    <input className='search-input' type="text" ref='keyword' onChange={this.appSearch.bind(this)} onFocus={this.onFoucs.bind(this)} placeholder="搜索应用" />
    
    也可以写成ref={(input) => { this.textInput = input; }}方式
    <input type="text" ref={(input) => { this.textInput = input; }} />
    使用this.textInput.focus();方式调用
    
    钩子函数中中调用
    componentDidMount(){
      this.refs.keyword.focus();
        }
    

    2、父组件调用子组件方法(搜索组件,有两个地方使用到,首页和搜索页,其中首页不需要自动获取焦点,进入搜索页时需要自动获取焦点)
    通过在搜索结果页里面获取搜索子组件的实例并调用foucs方法进行聚焦,从而不影响其他使用搜索组件的父组件状态

    (1)子组件中定义foucs方法
    focus(){
            this.refs.keyword.focus();
        }
    (2)设置input的ref属性
    <input className='search-input' type="text" ref='keyword' onChange={this.appSearch.bind(this)} onFocus={this.onFoucs.bind(this)} placeholder="搜索应用" />
    
    
    (3)父组件中调用foucs方法
    componentDidMount(){
        this.manualFocusInst.focus();
      }
    
    <Search appSearch={this.appSearch.bind(this)} ref={(ref)=>this.manualFocusInst = ref} onCancel={this.onCancel.bind(this)} onFoucs={this.onFoucs.bind(this)} showCancelBtn={true}></Search>
    

    3、react build的时候报错

    throw new BrowserslistError('Unknown browser query `' + selection + '`')
    

    解决办法是找到packpage.json里的browserslist,然后修改

    "browserslist": [
        "last 2 versions",
        "android 4",
        "opera 12"
      ],
    

    build开启静态服务访问

     npm install -g serve
     serve -s build
    

    4、组件上面不能直接添加className,如
    解决方式使用一个父div进行包裹

    <div className='search-bar'>
              <Search onFoucs={this.onFoucs.bind(this)}></Search>
            </div>
    

    5、ios 系统下img不显示问题,解决方案如下:

    /*兼容ios不显示图片问题*/
    img {
        content: normal !important
    }
    

    6、1px问题,解决方案

    /*伪元素1px*/
    .row-cell:before {
        content: " ";
        position: absolute;
        left: 0;
        top: 0;
        right: 0;
        height: 1px;
        border-top: 1px solid #e5e5e5;
        color: #e5e5e5;
        transform-origin: 0 0;
        transform: scaleY(0.5);
        z-index: 2;
    }
    

    相关文档

    https://react.docschina.org/
    https://www.redux.org.cn/
    https://react-redux.js.org/
    http://react-guide.github.io/react-router-cn
    https://mobile.ant.design

    最后

    代码我已经提交到github上去了,如果觉得还可以,欢迎star或者fork

    github仓库

    在线访问

    参考阅读

    https://www.jianshu.com/p/8954e9fb0c7e
    https://blog.csdn.net/z9061/article/details/84619309
    https://www.jianshu.com/p/f97aa775899f
    https://www.cnblogs.com/jack-liu6/p/9927336.html


    文章出处:https://www.cnblogs.com/fozero
  • 相关阅读:
    OSPF
    【今日CS 视觉论文速览】 24 Jan 2019
    【今日CS 视觉论文速览】Wed, 23 Jan 2019
    【今日CS 视觉论文速览】 21 Jan 2019
    【Processing学习笔记】安装与入门
    【今日CS 视觉论文速览】Part2, 18 Jan 2019
    【今日CS 视觉论文速览】Fri, 18 Jan 2019
    【今日CS 视觉论文速览】Thu, 17 Jan 2019
    【今日CS 视觉论文速览】Part2, 16 Jan 2019
    【今日CS 视觉论文速览】Wed, 16 Jan 2019
  • 原文地址:https://www.cnblogs.com/onesea/p/13169911.html
Copyright © 2011-2022 走看看