博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
react 项目实战(十)引入AntDesign组件库
阅读量:6150 次
发布时间:2019-06-21

本文共 24266 字,大约阅读时间需要 80 分钟。

本篇带你使用  组件库为我们的系统换上产品级的UI!

安装组件库

  • 在项目目录下执行:npm i antd@3.3.0 -S 或 yarn add antd 安装组件包
  • 执行:npm i babel-plugin-import -D 安装一个babel插件用于做组件的按需加载(否则项目会打包整个组件库,非常大)
  • 根目录下新建.roadhogrc文件(别忘了前面的点,这是roadhog工具的配置文件,下面的代码用于加载上一个命令安装的import插件),写入:
{  "extraBabelPlugins": [    ["import", {      "libraryName": "antd",      "libraryDirectory": "lib",      "style": "css"    }]  ]}

改造HomeLayout

我们计划把系统改造成这个样子:

上方显示LOGO,下方左侧显示一个菜单栏,右侧显示页面的主要内容。

所以新的HomeLayout应该包括LOGOMenu部分,然后HomeLayoutchildren放置在Content区域。

Menu我们使用AntDesign提供的Menu组件来完成,菜单项为:

  • 用户管理 
    • 用户列表
    • 添加用户
  • 图书管理 
    • 图书列表
    • 添加图书

来看新的组件代码:

/** * 布局组件 */import React from 'react';// 路由import { Link } from 'react-router';// Menu 导航菜单 Icon 图标import { Menu, Icon } from 'antd';import '../styles/home-layout.less';// 左侧菜单栏const SubMenu = Menu.SubMenu; class HomeLayout extends React.Component {  render () {    const {children} = this.props;    return (      
ReactManager
用户管理}>
用户列表
添加用户
图书管理}>
图书列表
添加图书
{children}
); }} export default HomeLayout;

HomeLayout引用了/src/styles/home-layout.less这个样式文件,样式代码为:

@import '~antd/dist/antd.css'; // 引入antd样式表.main {  height: 100vh;  padding-top: 50px;} .header {  position: absolute;  top: 0;  height: 50px;  width: 100%;  font-size: 18px;  padding: 0 20px;  line-height: 50px;  background-color: #108ee9;  color: #fff;   a {    color: inherit;  }} .menu {  height: 100%;  width: 240px;  float: left;  background-color: #404040;} .content {  height: 100%;  padding: 12px;  overflow: auto;  margin-left: 240px;  align-self: stretch;}

现在的首页是这个样子:

逼格立马就上来了有没?

改造HomePage

由于现在有菜单了,就不需要右侧那个HomePage里的链接了,把他去掉,然后放个Welcome吧(HomeLayout也去掉了,在下面会提到):

src / pages / Home.js

/** * 主页 */import React from 'react';// 引入样式表import '../styles/home-page.less';class Home extends React.Component {  // 构造器  constructor(props) {    super(props);    // 定义初始化状态    this.state = {};  }  render() {    return (      
Welcome
); }}export default Home;

新增样式文件/src/styles/home-page.less,代码:

.welcome{  width: 100%;  height: 100%;  display: flex;  align-items: center;  justify-content: center;  font-size: 32px;}

优化HomeLayout使用方式

现在的HomeLayout里有一个菜单了,菜单有展开状态需要维护,如果还是像以前那样在每个page组件里单独使用HomeLayout,会导致菜单的展开状态被重置(跳转页面之后都会渲染一个新的HomeLayout),所以需要将HomeLayout放到父级路由中来使用

src / index.js

/** * 配置路由 */import React from 'react';import ReactDOM from 'react-dom';// 引入react-routerimport { Router, Route, hashHistory } from 'react-router';// 引入布局组件import HomeLayout from './layouts/HomeLayout';import HomePage from './pages/Home'; // 首页import LoginPage from './pages/Login'; // 登录页import UserAddPage from './pages/UserAdd'; // 添加用户页import UserListPage from './pages/UserList'; // 用户列表页import UserEditPage from './pages/UserEdit'; // 用户编辑页面import BookAddPage from './pages/BookAdd'; // 添加图书页import BookListPage from './pages/BookList'; // 图书列表页import BookEditPage from './pages/BookEdit'; // 用户编辑页面// 渲染ReactDOM.render((  
), document.getElementById('root'));

效果图:

然后需要在各个页面中移除HomeLayout:

src / pages / BookAdd.js

/** * 图书添加页面 * 这个组件除了返回BookEditor没有做任何事,其实可以直接export default BookEditor */import React from 'react';// 编辑组件import BookEditor from '../components/BookEditor';class BookAdd extends React.Component {  render() {    return (      
); }}export default BookAdd;

src / pages / BookEdit.js

...render () {  const {book} = this.state;  return book ? 
:
加载中...;}...

src / pages / BookList.js

...render () {  ...  return (    
...
);}...

剩下的UserAdd.jsUserEdit.jsUserList.js与上面Book对应的组件做相同更改。

还有登录页组件在下面说。

升级登录页面

下面来对登录页面进行升级,修改/src/pages/Login.js文件:

/** * 登录页 */import React from 'react';// 引入antd组件import { Icon, Form, Input, Button, message } from 'antd';// 引入 封装后的fetch工具类import { post } from '../utils/request';// 引入样式表import styles from '../styles/login-page.less';// 引入 prop-typesimport PropTypes from 'prop-types';const FormItem = Form.Item; class Login extends React.Component {  // 构造器  constructor () {    super();    this.handleSubmit = this.handleSubmit.bind(this);  }    handleSubmit (e) {    // 通知 Web 浏览器不要执行与事件关联的默认动作    e.preventDefault();    // 表单验证    this.props.form.validateFields((err, values) => {      if(!err){        // 发起请求        post('http://localhost:8000/login', values)          // 成功的回调          .then((res) => {            if(res){              message.info('登录成功');              // 页面跳转              this.context.router.push('/');            }else{              message.info('登录失败,账号或密码错误');            }          });      }    });  }   render () {    const { form } = this.props;    // 验证规则    const { getFieldDecorator } = form;    return (      
ReactManager
{getFieldDecorator('account',{ rules: [ { required: true, message: '请输入管理员帐号', type: 'string' } ] })(
} /> )}
{getFieldDecorator('password',{ rules: [ { required: true, message: '请输入密码', type: 'string' } ] })(
} /> )}
); }} Login.contextTypes = { router: PropTypes.object.isRequired}; Login = Form.create()(Login); export default Login;

新建样式文件/src/styles/login-page.less,样式代码:

.wrapper {  height: 100vh;  display: flex;  align-items: center;  justify-content: center;}.body {  width: 360px;  box-shadow: 1px 1px 10px 0 rgba(0, 0, 0, .3);}.header {  color: #fff;  font-size: 24px;  padding: 30px 20px;  background-color: #108ee9;}.form {  margin-top: 12px;  padding: 24px;}.btn {  width: 100%;}

酷酷的登录页面:

改造后的登录页组件使用了antd提供的Form组件,Form组件提供了一个create方法,和我们之前写的一样,是一个高阶组件。使用Form.create({ ... })(Login)处理之后的Login组件会接收到一个props.form,使用props.form下的一系列方法,可以很方便地创造表单,上面有一段代码:

...
{getFieldDecorator('account',{ rules: [ { required: true, message: '请输入管理员帐号', type: 'string' } ] })(
} /> )}
...

这里使用了props.form.getFieldDecorator方法来包装一个Input输入框组件,传入的第一个参数表示这个字段的名称,第二个参数是一个配置对象,这里设置了表单控件的校验规则rules(更多配置项请查看)。使用getFieldDecorator方法包装后的组件会自动表单组件的value以及onChange事件;此外,这里还用到了Form.Item这个表单项目组件(上面的FormItem),这个组件可用于配置表单项目的标签、布局等。

在handleSubmit方法中,使用了props.form.validateFields方法对表单的各个字段进行校验,校验完成后会调用传入的回调方法,回调方法可以接收到错误信息err和表单值对象values,方便对校验结果进行处理:

...handleSubmit (e) {  // 通知 Web 浏览器不要执行与事件关联的默认动作  e.preventDefault();  // 表单验证  this.props.form.validateFields((err, values) => {    if(!err){      // 发起请求      post('http://localhost:8000/login', values)        // 成功的回调        .then((res) => {          if(res){            message.info('登录成功');            // 页面跳转            this.context.router.push('/');          }else{            message.info('登录失败,账号或密码错误');          }        });    }  });}...

升级UserEditor

升级UserEditor和登录页面组件类似,但是在componentWillMount里需要使用this.props.setFieldsValue将editTarget的值设置到表单:

src/components/UserEditor.js

/** * 用户编辑器组件 */import React from 'react';// 引入 antd 组件import { Form, Input, InputNumber, Select, Button, message } from 'antd';// 引入 prop-typesimport PropTypes from 'prop-types';// 引入 封装fetch工具类import request from '../utils/request';const FormItem = Form.Item;const formLayout = {  labelCol: {    span: 4  },  wrapperCol: {    span: 16  }};class UserEditor extends React.Component {  // 生命周期--组件加载完毕  componentDidMount(){    /**     * 在componentWillMount里使用form.setFieldsValue无法设置表单的值     * 所以在componentDidMount里进行赋值     */    const { editTarget, form } = this.props;    if(editTarget){      // 将editTarget的值设置到表单      form.setFieldsValue(editTarget);    }  }  // 按钮提交事件  handleSubmit(e){    // 阻止表单submit事件自动跳转页面的动作    e.preventDefault();    // 定义常量    const { form, editTarget } = this.props; // 组件传值    // 验证    form.validateFields((err, values) => {      if(!err){        // 默认值        let editType = '添加';        let apiUrl = 'http://localhost:8000/user';        let method = 'post';        // 判断类型        if(editTarget){          editType = '编辑';          apiUrl += '/' + editTarget.id;          method = 'put';        }        // 发送请求        request(method,apiUrl,values)          // 成功的回调          .then((res) => {            // 当添加成功时,返回的json对象中应包含一个有效的id字段            // 所以可以使用res.id来判断添加是否成功            if(res.id){              message.success(editType + '添加用户成功!');              // 跳转到用户列表页面              this.context.router.push('/user/list');              return;            }else{              message.error(editType + '添加用户失败!');            }          })          // 失败的回调          .catch((err) => console.error(err));      }else{        message.warn(err);      }    });  }    render() {    // 定义常量    const { form } = this.props;    const { getFieldDecorator } = form;    return (      
this.handleSubmit(e)}>
{getFieldDecorator('name',{ rules: [ { required: true, message: '请输入用户名' }, { pattern: /^.{1,4}$/, message: '用户名最多4个字符' } ] })(
)}
{getFieldDecorator('age',{ rules: [ { required: true, message: '请输入年龄', type: 'number' }, { min: 1, max: 100, message: '请输入1~100的年龄', type: 'number' } ] })(
)}
{getFieldDecorator('gender',{ rules: [ { required: true, message: '请选择性别' } ] })(
)}
); }}// 必须给UserEditor定义一个包含router属性的contextTypes// 使得组件中可以通过this.context.router来使用React Router提供的方法UserEditor.contextTypes = { router: PropTypes.object.isRequired};/** * 使用Form.create({ ... })(UserEditor)处理之后的UserEditor组件会接收到一个props.form * 使用props.form下的一系列方法,可以很方便地创造表单 */UserEditor = Form.create()(UserEditor);export default UserEditor;

升级BookEditor

BookEditor中使用了AutoComplete组件,但是由于antd提供的AutoComplete组件有一些问题(见),这里暂时使用我们之前实现的AutoComplete。

src/components/BookEditor.js

/** * 图书编辑器组件 */import React from 'react';// 引入 antd 组件import { Input, InputNumber, Form, Button, message } from 'antd';// 引入 prop-typesimport PropTypes from 'prop-types';// 引入自动完成组件import AutoComplete from '../components/AutoComplete'; // 也可以写为 './AutoComplete'// 引入 封装fetch工具类import request,{get} from '../utils/request';// const Option = AutoComplete.Option;const FormItem = Form.Item;// 表单布局const formLayout = {  // label 标签布局,同  组件  labelCol: {    span: 4  },  wrapperCol: {    span: 16  }};class BookEditor extends React.Component {  // 构造器  constructor(props) {    super(props);      this.state = {      recommendUsers: []    };    // 绑定this    this.handleSubmit = this.handleSubmit.bind(this);    this.handleOwnerIdChange = this.handleOwnerIdChange.bind(this);  }  // 生命周期--组件加载完毕  componentDidMount(){    /**     * 在componentWillMount里使用form.setFieldsValue无法设置表单的值     * 所以在componentDidMount里进行赋值     */    const {editTarget, form} = this.props;    if(editTarget){      form.setFieldsValue(editTarget);    }  }  // 按钮提交事件  handleSubmit(e){    // 阻止submit默认行为    e.preventDefault();    // 定义常量    const { form, editTarget } = this.props; // 组件传值    // 验证    form.validateFields((err, values) => {      if(err){        message.warn(err);        return;      }      // 默认值      let editType = '添加';      let apiUrl = 'http://localhost:8000/book';      let method = 'post';      // 判断类型      if(editTarget){        editType = '编辑';        apiUrl += '/' + editTarget.id;        method = 'put';      }      // 发送请求      request(method,apiUrl,values)        // 成功的回调        .then((res) => {          // 当添加成功时,返回的json对象中应包含一个有效的id字段          // 所以可以使用res.id来判断添加是否成功          if(res.id){            message.success(editType + '添加图书成功!');            // 跳转到用户列表页面            this.context.router.push('/book/list');          }else{            message.error(editType + '添加图书失败!');          }        })        // 失败的回调        .catch((err) => console.error(err));    });      }  // 获取推荐用户信息  getRecommendUsers (partialUserId) {    // 请求数据    get('http://localhost:8000/user?id_like=' + partialUserId)    .then((res) => {      if(res.length === 1 && res[0].id === partialUserId){        // 如果结果只有1条且id与输入的id一致,说明输入的id已经完整了,没必要再设置建议列表        return;      }      // 设置建议列表      this.setState({        recommendUsers: res.map((user) => {          return {            text: `${user.id}(${user.name})`,            value: user.id          }        })      });    })  }  // 计时器  timer = 0;  handleOwnerIdChange(value){    this.setState({      recommendUsers: []    });    // 使用"节流"的方式进行请求,防止用户输入的过程中过多地发送请求    if(this.timer){      // 清除计时器      clearTimeout(this.timer);    }    if(value){      // 200毫秒内只会发送1次请求      this.timer = setTimeout(() => {        // 真正的请求方法        this.getRecommendUsers(value);        this.timer = 0;      }, 200);    }  }    render() {    // 定义常量    const {recommendUsers} = this.state;    const {form} = this.props;    const {getFieldDecorator} = form;    return (      
{getFieldDecorator('name',{ rules: [ { required: true, message: '请输入书名' } ] })(
)}
{getFieldDecorator('price',{ rules: [ { required: true, message: '请输入价格', type: 'number' }, { min: 1, max: 99999, type: 'number', message: '请输入1~99999的数字' } ] })(
)}
{getFieldDecorator('owner_id',{ rules: [ { required: true, message: '请输入所有者ID' }, { pattern: /^\d*$/, message: '请输入正确的ID' } ] })(
)}
); }}// 必须给BookEditor定义一个包含router属性的contextTypes// 使得组件中可以通过this.context.router来使用React Router提供的方法BookEditor.contextTypes = { router: PropTypes.object.isRequired};BookEditor = Form.create()(BookEditor);export default BookEditor;

升级AutoComplete

因为要继续使用自己的AutoComplete组件,这里需要把组件中的原生input控件替换为antd的Input组件,并且在Input组件加了两个事件处理onFocusonBlurstate.show,用于在输入框失去焦点时隐藏下拉框:

src/components/AutoComplete.js

/** * 自动完成组件 */import React from 'react';// 引入 antd 组件import { Input } from 'antd';// 引入 prop-typesimport PropTypes from 'prop-types';// 引入样式import styles from '../styles/auto-complete.less';// 获得当前元素value值function getItemValue (item) {  return item.value || item;}class AutoComplete extends React.Component {  // 构造器  constructor(props) {    super(props);    // 定义初始化状态    this.state = {      show: false, // 新增的下拉框显示控制开关      displayValue: '',      activeItemIndex: -1    };    // 对上下键、回车键进行监听处理    this.handleKeyDown = this.handleKeyDown.bind(this);    // 对鼠标移出进行监听处理    this.handleLeave = this.handleLeave.bind(this);  }  // 处理输入框改变事件  handleChange(value){    // 选择列表项的时候重置内部状态    this.setState({      activeItemIndex: -1,      displayValue: ''    });    /**     * 通过回调将新的值传递给组件使用者     * 原来的onValueChange改为了onChange以适配antd的getFieldDecorator     */    this.props.onChange(value);  }  // 处理上下键、回车键点击事件  handleKeyDown(e){    const {activeItemIndex} = this.state;    const {options} = this.props;    /**     * 判断键码     */    switch (e.keyCode) {      // 13为回车键的键码(keyCode)      case 13: {        // 判断是否有列表项处于选中状态        if(activeItemIndex >= 0){          // 防止按下回车键后自动提交表单          e.preventDefault();          e.stopPropagation();          // 输入框改变事件          this.handleChange(getItemValue(options[activeItemIndex]));        }        break;      }      // 38为上方向键,40为下方向键      case 38:      case 40: {        e.preventDefault();        // 使用moveItem方法对更新或取消选中项        this.moveItem(e.keyCode === 38 ? 'up' : 'down');        break;      }      default: {        //      }    }  }  // 使用moveItem方法对更新或取消选中项  moveItem(direction){    const {activeItemIndex} = this.state;    const {options} = this.props;    const lastIndex = options.length - 1;    let newIndex = -1;    // 计算新的activeItemIndex    if(direction === 'up'){ // 点击上方向键      if(activeItemIndex === -1){        // 如果没有选中项则选择最后一项        newIndex = lastIndex;      }else{        newIndex = activeItemIndex - 1;      }    }else{ // 点击下方向键      if(activeItemIndex < lastIndex){        newIndex = activeItemIndex + 1;      }    }    // 获取新的displayValue    let newDisplayValue = '';    if(newIndex >= 0){      newDisplayValue = getItemValue(options[newIndex]);    }    // 更新状态    this.setState({      displayValue: newDisplayValue,      activeItemIndex: newIndex    });  }  // 处理鼠标移入事件  handleEnter(index){    const currentItem = this.props.options[index];    this.setState({      activeItemIndex: index,      displayValue: getItemValue(currentItem)    });  }  // 处理鼠标移出事件  handleLeave(){    this.setState({      activeItemIndex: -1,      displayValue: ''    });  }  // 渲染  render() {    const {show, displayValue, activeItemIndex} = this.state;    // 组件传值    const {value, options} = this.props;    return (      
this.handleChange(e.target.value)} onKeyDown={this.handleKeyDown} onFocus={() => this.setState({show: true})} onBlur={() => this.setState({show: false})} /> {show && options.length > 0 && (
    { options.map((item, index) => { return (
  • this.handleEnter(index)} onClick={() => this.handleChange(getItemValue(item))} > {item.text || item}
  • ); }) }
)}
); }}/** * 由于使用了antd的form.getFieldDecorator来包装组件 * 这里取消了原来props的isRequired约束以防止报错 */AutoComplete.propTypes = { value: PropTypes.any, // 任意类型 options: PropTypes.array, // 数组 onChange: PropTypes.func // 函数};// 向外暴露export default AutoComplete;

同时也更新了组件的样式/src/styles/auto-complete.less,给.options加了一个z-index:

.options {  z-index: 2;  background-color:#fff;    ...}

升级列表页组件

最后还剩下两个列表页组件,我们使用antd的Table组件来实现这两个列表:

src/pages/BookList.js

/** * 图书列表页面 */import React from 'react';// 引入 antd 组件import { message, Table, Button, Popconfirm } from 'antd';// 引入 prop-typesimport PropTypes from 'prop-types';// 引入 封装fetch工具类import { get, del } from '../utils/request'; class BookList extends React.Component {  // 构造器  constructor(props) {    super(props);    // 定义初始化状态    this.state = {      bookList: []    };  }  /**   * 生命周期   * componentWillMount   * 组件初始化时只调用,以后组件更新不调用,整个生命周期只调用一次   */  componentWillMount(){    // 请求数据    get('http://localhost:8000/book')      .then((res) => {        /**         * 成功的回调         * 数据赋值         */        this.setState({          bookList: res        });      });  }  /**   * 编辑   */  handleEdit(book){    // 跳转编辑页面    this.context.router.push('/book/edit/' + book.id);  }  /**   * 删除   */  handleDel(book){    // 执行删除数据操作    del('http://localhost:8000/book/' + book.id, {    })      .then(res => {        /**         * 设置状态         * array.filter         * 把Array的某些元素过滤掉,然后返回剩下的元素         */        this.setState({          bookList: this.state.bookList.filter(item => item.id !== book.id)        });        message.success('删除用户成功');      })      .catch(err => {        console.error(err);        message.error('删除用户失败');      });  }  render() {    // 定义变量    const { bookList } = this.state;    // antd的Table组件使用一个columns数组来配置表格的列    const columns = [      {        title: '图书ID',        dataIndex: 'id'      },      {        title: '书名',        dataIndex: 'name'      },      {        title: '价格',        dataIndex: 'price',        render: (text, record) => ¥{record.price / 100}      },      {        title: '所有者ID',        dataIndex: 'owner_id'      },      {        title: '操作',        render: (text, record) => (          
this.handleDel(record)}>
) } ]; return (
row.id} /> ); }}/** * 任何使用this.context.xxx的地方,必须在组件的contextTypes里定义对应的PropTypes */BookList.contextTypes = { router: PropTypes.object.isRequired};export default BookList;

src/pages/UserList.js

/** * 用户列表页面 */import React from 'react';// 引入 antd 组件import { message, Table, Button, Popconfirm } from 'antd';// 引入 prop-typesimport PropTypes from 'prop-types';// 引入 封装后的fetch工具类import { get, del } from '../utils/request';class UserList extends React.Component {  // 构造器  constructor(props) {    super(props);    // 定义初始化状态    this.state = {      userList: []    };  }  /**   * 生命周期   * componentWillMount   * 组件初始化时只调用,以后组件更新不调用,整个生命周期只调用一次   */  componentWillMount(){    // 请求数据    get('http://localhost:8000/user')      .then((res) => {        /**         * 成功的回调         * 数据赋值         */        this.setState({          userList: res        });      });  }  /**   * 编辑   */  handleEdit(user){    // 跳转编辑页面    this.context.router.push('/user/edit/' + user.id);  }  /**   * 删除   */  handleDel(user){    // 执行删除数据操作    del('http://localhost:8000/user/' + user.id, {    })      .then((res) => {        /**         * 设置状态         * array.filter         * 把Array的某些元素过滤掉,然后返回剩下的元素         */        this.setState({          userList: this.state.userList.filter(item => item.id !== user.id)        });        message.success('删除用户成功');      })      .catch(err => {        console.error(err);        message.error('删除用户失败');      });  }  render() {    // 定义变量    const { userList } = this.state;    // antd的Table组件使用一个columns数组来配置表格的列    const columns = [      {        title: '用户ID',        dataIndex: 'id'      },      {        title: '用户名',        dataIndex: 'name'      },      {        title: '性别',        dataIndex: 'gender'      },      {        title: '年龄',        dataIndex: 'age'      },      {        title: '操作',        render: (text, record) => {          return (            
this.handleDel(record)}>
); } } ]; return (
row.id} /> ); }}/** * 任何使用this.context.xxx的地方,必须在组件的contextTypes里定义对应的PropTypes */UserList.contextTypes = { router: PropTypes.object.isRequired};export default UserList;

antdTable组件使用一个columns数组来配置表格的列,这个columns数组的元素可以包含title(列名)dataIndex(该列数据的索引)render(自定义的列单元格渲染方法)等字段(更多配置请参考)。

然后将表格数据列表传入Table的dataSource,传入一个rowKey来指定每一列的key,就可以渲染出列表了

效果图:

转载于:https://www.cnblogs.com/crazycode2/p/8553664.html

你可能感兴趣的文章
有趣的数学书籍
查看>>
teamviewer 卸载干净
查看>>
多线程设计模式
查看>>
解读自定义UICollectionViewLayout--感动了我自己
查看>>
SqlServer作业指定目标服务器
查看>>
User implements HttpSessionBindingListener
查看>>
eclipse的maven、Scala环境搭建
查看>>
架构师之路(一)- 什么是软件架构
查看>>
USACO 土地购买
查看>>
【原创】远景能源面试--一面
查看>>
B1010.一元多项式求导(25)
查看>>
10、程序员和编译器之间的关系
查看>>
前端学习之正则表达式
查看>>
配置 RAILS FOR JRUBY1.7.4
查看>>
AndroidStudio中导入SlidingMenu报错解决方案
查看>>
修改GRUB2背景图片
查看>>
Ajax异步
查看>>
好记性不如烂笔杆-android学习笔记<十六> switcher和gallery
查看>>
JAVA GC
查看>>
3springboot:springboot配置文件(外部配置加载顺序、自动配置原理,@Conditional)
查看>>