[尚硅谷React笔记]——第4章 React ajax

目录:

  1. 脚手架配置代理_方法一
    1. server1.js
    2. 开启服务器server1:
    3. App.js
    4. 解决跨域问题:
  2. 脚手架配置代理_方法二
    1. ​​​​​​​server2.js
    2. 开启服务器server2
    3. 第一步:创建代理配置文件
    4. 编写setupProxy.js配置具体代理规则:
    5. App.js
    6. 运行结果:
    7. 说明:
  3. github用户搜索案例
    1. github搜索案例_静态组件
    2. github搜索案例_axios发送请求
    3. github搜索案例_展示数据
    4. github搜索案例_完成案例
  4. 消息订阅与发布_pubsub
  5. fetch发送请求

1.脚手架配置代理_方法一

server1.js
bash 复制代码
const express = require('express')
const app = express()

app.use((request,response,next)=>{
	console.log('有人请求服务器1了');
	console.log('请求来自于',request.get('Host'));
	console.log('请求的地址',request.url);
	next()
})

app.get('/students',(request,response)=>{
	const students = [
		{id:'001',name:'tom',age:18},
		{id:'002',name:'jerry',age:19},
		{id:'003',name:'tony',age:120},
	]
	response.send(students)
})

app.listen(5000,(err)=>{
	if(!err) console.log('服务器1启动成功了,请求学生信息地址为:http://localhost:5000/students');
})
开启服务器server1:
App.js
bash 复制代码
import React, {Component} from 'react';
import axios from "axios";

class App extends Component {
    getStudentData = () => {
        axios.get('http://localhost:5000/students').then(
            response => {
                console.log('成功了', response.data);
            },
            error => {
                console.log('失败了', error);
            }
        )
    }

    render() {
        return (
            <div>
                <button onClick={this.getStudentData}>点我获取学生数据</button>
            </div>
        );
    }
}

export default App;
解决跨域问题:

package.json

bash 复制代码
{
  "name": "20231003",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "@testing-library/jest-dom": "^5.17.0",
    "@testing-library/react": "^13.4.0",
    "@testing-library/user-event": "^13.5.0",
    "axios": "^1.5.1",
    "nanoid": "^5.0.1",
    "prop-types": "^15.8.1",
    "react": "^18.2.0",
    "react-dom": "^18.2.0",
    "react-scripts": "5.0.1",
    "web-vitals": "^2.1.4"
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject"
  },
  "eslintConfig": {
    "extends": [
      "react-app",
      "react-app/jest"
    ]
  },
  "browserslist": {
    "production": [
      ">0.2%",
      "not dead",
      "not op_mini all"
    ],
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ],
    "proxy": "http://localhost:5000"
  }
}

使用方法一遇到了bug,去stackoverflow解决一下,能解决,但是有更好的方法,方法一不在继续了.node.js - Invalid options object. Dev Server has been initialized using an options object that does not match the API schema - Stack Overflow

2.脚手架配置代理_方法二

server2.js
bash 复制代码
const express = require('express')
const app = express()

app.use((request,response,next)=>{
	console.log('有人请求服务器2了');
	next()
})

app.get('/cars',(request,response)=>{
	const cars = [
		{id:'001',name:'奔驰',price:199},
		{id:'002',name:'马自达',price:109},
		{id:'003',name:'捷达',price:120},
	]
	response.send(cars)
})

app.listen(5001,(err)=>{
	if(!err) console.log('服务器2启动成功了,请求汽车信息地址为:http://localhost:5001/cars');
})
开启服务器server2

访问端口:

第一步:创建代理配置文件

在src下创建配置文件:src/setupProxy.js

编写setupProxy.js配置具体代理规则:

这个适合低版本

const proxy = require('http-proxy-middleware')

module.exports = function(app) {

app.use(

proxy('/api1', { //api1是需要转发的请求(所有带有/api1前缀的请求都会转发给5000)

target: 'http://localhost:5000', //配置转发目标地址(能返回数据的服务器地址)

changeOrigin: true, //控制服务器接收到的请求头中host字段的值

/*

changeOrigin设置为true时,服务器收到的请求头中的host为:localhost:5000

changeOrigin设置为false时,服务器收到的请求头中的host为:localhost:3000

changeOrigin默认值为false,但我们一般将changeOrigin值设为true

*/

pathRewrite: {'^/api1': ''} //去除请求前缀,保证交给后台服务器的是正常请求地址(必须配置)

}),

proxy('/api2', {

target: 'http://localhost:5001',

changeOrigin: true,

pathRewrite: {'^/api2': ''}

})

)

}

我使用的node是:v18.18.0

复制代码
//react 18版本写法
const {createProxyMiddleware} = require('http-proxy-middleware')

module.exports = function (app) {
    app.use(
        createProxyMiddleware('/api1', {
            target: 'http://localhost:5000',
            changeOrigin: true,
            pathRewrite: {'^/api1': ''}
        }),
        createProxyMiddleware('/api2', {
            target: 'http://localhost:5001',
            changeOrigin: true,
            pathRewrite: {'^/api2': ''}
        }),
    )
}
App.js
bash 复制代码
import React, {Component} from 'react';
import axios from "axios";

class App extends Component {
    getStudentData = () => {
        axios.get('http://localhost:3000/api1/students').then(
            response => {
                console.log('成功了', response.data);
            },
            error => {
                console.log('失败了', error);
            }
        )
    }

    getCarData = () => {
        axios.get('http://localhost:3000/api2/cars').then(
            response => {
                console.log('成功了', response.data);
            },
            error => {
                console.log('失败了', error);
            }
        )
    }

    render() {
        return (
            <div>
                <button onClick={this.getStudentData}>点我获取学生数据</button>
                <button onClick={this.getCarData}>点我获取汽车数据</button>
            </div>
        );
    }
}

export default App;
运行结果:
说明:
  1. 优点:可以配置多个代理,可以灵活的控制请求是否走代理。

  2. 缺点:配置繁琐,前端请求资源时必须加前缀。

3.github用户搜索案例

github搜索案例_静态组件

App.js

bash 复制代码
import React, {Component} from 'react';
import Search from './components/Search/Search'
import List from "./components/List/List";

class App extends Component {
    render() {
        return (
            <div>
                <div className="container">
                    <Search></Search>
                    <List></List>
                </div>
            </div>
        );
    }
}

export default App;

index.js

bash 复制代码
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<App/>);

List.jsx

bash 复制代码
import React, {Component} from 'react';
import './List.css'

class List extends Component {
    render() {
        return (
            <div>
                <div className="row">
                    <div className="card">
                        <a rel="noreferrer" href="https://github.com/reactjs" target="_blank">
                            <img alt="head_protrait" src="https://avatars.githubusercontent.com/u/6412038?v=3" style={{width:'100px'}}/>
                        </a>
                        <p className="card-text">reactjs</p>
                    </div>
                </div>
            </div>
        );
    }
}

export default List;

List.css

bash 复制代码
.album {
    min-height: 50rem; /* Can be removed; just added for demo purposes */
    padding-top: 3rem;
    padding-bottom: 3rem;
    background-color: #f7f7f7;
}

.card {
    float: left;
    width: 33.333%;
    padding: .75rem;
    margin-bottom: 2rem;
    border: 1px solid #efefef;
    text-align: center;
}

.card > img {
    margin-bottom: .75rem;
    border-radius: 100px;
}

.card-text {
    font-size: 85%;
}

Search.jsx

bash 复制代码
import React, {Component} from 'react';

class Search extends Component {
    render() {
        return (
            <div>
                <section className="jumbotron">
                    <h3 className="jumbotron-heading">Search Github Users</h3>
                    <div>
                        <input type="text" placeholder="enter the name you search"/>&nbsp;
                        <button>Search</button>
                    </div>
                </section>
            </div>
        );
    }
}

export default Search;

运行结果:

github搜索案例_axios发送请求

demo.html

bash 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<script type="text/javascript">
    let obj = {a: {b: {c: 1}}}
    let obj2 = {a: {b: 1}}

    console.log(obj.a.b.c)

    const {a: {b: {c}}} = obj
    console.log(c)

    const {a: {b: data}} = obj2
    console.log(data)
</script>
</body>
</html>

server.js

bash 复制代码
const express = require("express")
const axios = require("axios")
const app = express()


/*
  请求地址: http://localhost:3000/search/users?q=aa

  后台路由
    key: /search/users
    value: function () {}
*/
app.get("/search/users", function (req, res) {
  const {q} = req.query
  axios({
    url: 'https://api.github.com/search/users',
    params: {q}
  }).then(response => {
    res.json(response.data)
  })
})

app.get("/search/users2", function (req, res) {
  res.json({
    items: [
      {
        login: "yyx990803",
        html_url: "https://github.com/yyx990803",
        avatar_url:
          "https://avatars3.githubusercontent.com/u/499550?s=460&u=de41ec9325e8a92e281b96a1514a0fd1cd81ad4a&v=4",
        id: 1,
      },
      {
        login: "ruanyf",
        html_url: "https://github.com/ruanyf",
        avatar_url: "https://avatars2.githubusercontent.com/u/905434?s=460&v=4",
        id: 2,
      },
      {
        login: "yyx9908032",
        html_url: "https://github.com/yyx990803",
        avatar_url:
          "https://avatars3.githubusercontent.com/u/499550?s=460&u=de41ec9325e8a92e281b96a1514a0fd1cd81ad4a&v=4",
        id: 3,
      },
      {
        login: "ruanyf2",
        html_url: "https://github.com/ruanyf",
        avatar_url: "https://avatars2.githubusercontent.com/u/905434?s=460&v=4",
        id: 4,
      },
      {
        login: "yyx9908033",
        html_url: "https://github.com/yyx990803",
        avatar_url:
          "https://avatars3.githubusercontent.com/u/499550?s=460&u=de41ec9325e8a92e281b96a1514a0fd1cd81ad4a&v=4",
        id: 5,
      },
      {
        login: "ruanyf3",
        html_url: "https://github.com/ruanyf",
        avatar_url: "https://avatars2.githubusercontent.com/u/905434?s=460&v=4",
        id: 6,
      },
      {
        login: "yyx9908034",
        html_url: "https://github.com/yyx990803",
        avatar_url:
          "https://avatars3.githubusercontent.com/u/499550?s=460&u=de41ec9325e8a92e281b96a1514a0fd1cd81ad4a&v=4",
        id: 7,
      },
      {
        login: "ruanyf4",
        html_url: "https://github.com/ruanyf",
        avatar_url: "https://avatars2.githubusercontent.com/u/905434?s=460&v=4",
        id: 8,
      },
      {
        login: "yyx9908035",
        html_url: "https://github.com/yyx990803",
        avatar_url:
          "https://avatars3.githubusercontent.com/u/499550?s=460&u=de41ec9325e8a92e281b96a1514a0fd1cd81ad4a&v=4",
        id: 9,
      },
    ],
  });
});



app.listen(5000, "localhost", (err) => {
  if (!err){
  	console.log("服务器启动成功")
  	console.log("请求github真实数据请访问:http://localhost:5000/search/users")
  	console.log("请求本地模拟数据请访问:http://localhost:5000/search/users2")
  } 
  else console.log(err);
})

启动服务器:

bash 复制代码
node .\server.js

App.js

bash 复制代码
import React, {Component} from 'react';
import Search from './components/Search/Search'
import List from "./components/List/List";

class App extends Component {
    render() {
        return (
            <div>
                <div className="container">
                    <Search></Search>
                    <List></List>
                </div>
            </div>
        );
    }
}

export default App;

index.js

bash 复制代码
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<App/>);

List.jsx

bash 复制代码
import React, {Component} from 'react';
import './List.css'

class List extends Component {
    render() {
        return (
            <div>
                <div className="row">
                    <div className="card">
                        <a rel="noreferrer" href="https://github.com/reactjs" target="_blank">
                            <img alt="head_protrait" src="https://avatars.githubusercontent.com/u/6412038?v=3" style={{width:'100px'}}/>
                        </a>
                        <p className="card-text">reactjs</p>
                    </div>
                </div>
            </div>
        );
    }
}

export default List;

List.css

bash 复制代码
.album {
    min-height: 50rem; /* Can be removed; just added for demo purposes */
    padding-top: 3rem;
    padding-bottom: 3rem;
    background-color: #f7f7f7;
}

.card {
    float: left;
    width: 33.333%;
    padding: .75rem;
    margin-bottom: 2rem;
    border: 1px solid #efefef;
    text-align: center;
}

.card > img {
    margin-bottom: .75rem;
    border-radius: 100px;
}

.card-text {
    font-size: 85%;
}

Search.jsx

bash 复制代码
import React, {Component} from 'react';
import axios from "axios";

class Search extends Component {
    search = () => {
        //连续结构+重命名
        const {keyWordElement: {value: keyWord}} = this

        axios.get(`http://localhost:3000/api1/search/users2?q=${keyWord}`).then(
            response => {
                console.log('成功了', response.data);
            },
            error => {
                console.log('失败了', error);
            }
        )
    }

    render() {
        return (
            <div>
                <section className="jumbotron">
                    <h3 className="jumbotron-heading">搜索github用户</h3>
                    <div>
                        <input ref={c => this.keyWordElement = c} type="text" placeholder="输入关键词点击搜索"/>&nbsp;
                        <button onClick={this.search}>搜索</button>
                    </div>
                </section>
            </div>
        );
    }
}

export default Search;

setupProxy.js

bash 复制代码
//react 18版本写法
const {createProxyMiddleware} = require('http-proxy-middleware')

module.exports = function (app) {
    app.use(
        createProxyMiddleware('/api1', {
            target: 'http://localhost:5000',
            changeOrigin: true,
            pathRewrite: {'^/api1': ''}
        }),
    )
}

运行结果:

github搜索案例_展示数据

App.js

bash 复制代码
import React, {Component} from 'react';
import Search from './components/Search/Search'
import List from "./components/List/List";

class App extends Component {
    state = {users: []}

    saveUsers = (users) => {
        this.setState({users})
    }

    render() {
        const {users} = this.state
        return (
            <div>
                <div className="container">
                    <Search saveUsers={this.saveUsers}></Search>
                    <List users={users}></List>
                </div>
            </div>
        );
    }
}

export default App;

List.jsx

bash 复制代码
import React, {Component} from 'react';
import './List.css'

class List extends Component {
    render() {
        return (
            <div>
                <div className="row">
                    {
                        this.props.users.map((userObj) => {
                            return (
                                <div className="card" key={userObj.id}>
                                    <a rel="noreferrer" href={userObj.html_url} target="_blank">
                                        <img alt="head_protrait"
                                             src={userObj.avatar_url}
                                             style={{width: '100px'}}/>
                                    </a>
                                    <p className="card-text">{userObj.login}</p>
                                </div>
                            )
                        })
                    }
                </div>
            </div>
        );
    }
}

export default List;

Search.jsx

bash 复制代码
import React, {Component} from 'react';
import axios from "axios";

class Search extends Component {
    search = () => {
        //连续结构+重命名
        const {keyWordElement: {value: keyWord}} = this

        axios.get(`http://localhost:3000/api1/search/users2?q=${keyWord}`).then(
            response => {
                console.log('成功了', response.data.items);
                this.props.saveUsers(response.data.items)
            },
            error => {
                console.log('失败了', error);
            }
        )
    }

    render() {
        return (
            <div>
                <section className="jumbotron">
                    <h3 className="jumbotron-heading">搜索github用户</h3>
                    <div>
                        <input ref={c => this.keyWordElement = c} type="text" placeholder="输入关键词点击搜索"/>&nbsp;
                        <button onClick={this.search}>搜索</button>
                    </div>
                </section>
            </div>
        );
    }
}

export default Search;

运行结果:

github搜索案例_完成案例

App.js

bash 复制代码
import React, {Component} from 'react';
import Search from './components/Search/Search'
import List from "./components/List/List";

class App extends Component {
    state = {users: [], isFirst: true, isLoading: false, err: ''}

    saveUsers = (users) => {
        this.setState({users})
    }

    updateAppState = (stateObj) => {
        this.setState(stateObj)
    }

    render() {
        const {users} = this.state
        return (
            <div>
                <div className="container">
                    <Search updateAppState={this.updateAppState}></Search>
                    <List {...this.state}></List>
                </div>
            </div>
        );
    }
}

export default App;

Search.jsx

bash 复制代码
import React, {Component} from 'react';
import axios from "axios";

class Search extends Component {
    search = () => {
        //连续结构+重命名
        const {keyWordElement: {value: keyWord}} = this
        console.log(keyWord)

        this.props.updateAppState({isFirst: false, isLoading: true})

        axios.get(`http://localhost:3000/api1/search/users2?q=${keyWord}`).then(
            response => {
                console.log('成功了', response.data.items);
                this.props.updateAppState({isLoading: false, users: response.data.items})
            },
            error => {
                console.log('失败了', error);
                this.props.updateAppState({isLoading: false, err: error.message})
            }
        )
    }

    render() {
        return (
            <div>
                <section className="jumbotron">
                    <h3 className="jumbotron-heading">搜索github用户</h3>
                    <div>
                        <input ref={c => this.keyWordElement = c} type="text" placeholder="输入关键词点击搜索"/>&nbsp;
                        <button onClick={this.search}>搜索</button>
                    </div>
                </section>
            </div>
        );
    }
}

export default Search;

List.jsx

bash 复制代码
import React, {Component} from 'react';
import './List.css'

class List extends Component {
    render() {
        const {users, isFirst, isLoading, err} = this.props
        return (
            <div>
                <div className="row">
                    {
                        isFirst ? <h2>欢迎使用,输入关键字,然后点击搜索</h2> :
                            isLoading ? <h2>加载中...</h2> :
                                err ? <h2 style={{color: 'red'}}>{err}</h2> :
                                    users.map((userObj) => {
                                        return (
                                            <div className="card" key={userObj.id}>
                                                <a rel="noreferrer" href={userObj.html_url} target="_blank">
                                                    <img alt="head_protrait"
                                                         src={userObj.avatar_url}
                                                         style={{width: '100px'}}/>
                                                </a>
                                                <p className="card-text">{userObj.login}</p>
                                            </div>
                                        )
                                    })
                    }
                </div>
            </div>
        );
    }
}

export default List;

setProxy.js

bash 复制代码
//react 18版本写法
const {createProxyMiddleware} = require('http-proxy-middleware')

module.exports = function (app) {
    app.use(
        createProxyMiddleware('/api1', {
            target: 'http://localhost:5000',
            changeOrigin: true,
            pathRewrite: {'^/api1': ''}
        }),
    )
}

index.js

bash 复制代码
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<App/>);

运行结果:

4.消息订阅与发布_pubsub

App.js

bash 复制代码
import React, {Component} from 'react';
import Search from './components/Search/Search'
import List from "./components/List/List";

class App extends Component {
    render() {
        return (
            <div>
                <div className="container">
                    <Search></Search>
                    <List></List>
                </div>
            </div>
        );
    }
}

export default App;

List.jsx

bash 复制代码
import React, {Component} from 'react';
import './List.css'
import PubSub from 'pubsub-js'

class List extends Component {
    state = {users: [], isFirst: true, isLoading: false, err: ''}

    componentDidMount() {
        this.token = PubSub.subscribe('atguigu', (_, stateObj) => {
            console.log(stateObj);
            this.setState(stateObj);
        })
    }

    componentWillUnmount() {
        PubSub.unsubscribe(this.token)
    }

    render() {
        const {users, isFirst, isLoading, err} = this.state
        return (
            <div>
                <div className="row">
                    {
                        isFirst ? <h2>欢迎使用,输入关键字,然后点击搜索</h2> :
                            isLoading ? <h2>加载中...</h2> :
                                err ? <h2 style={{color: 'red'}}>{err}</h2> :
                                    users.map((userObj) => {
                                        return (
                                            <div className="card" key={userObj.id}>
                                                <a rel="noreferrer" href={userObj.html_url} target="_blank">
                                                    <img alt="head_protrait"
                                                         src={userObj.avatar_url}
                                                         style={{width: '100px'}}/>
                                                </a>
                                                <p className="card-text">{userObj.login}</p>
                                            </div>
                                        )
                                    })
                    }
                </div>
            </div>
        );
    }
}

export default List;

Search.jsx

bash 复制代码
import React, {Component} from 'react';
import axios from "axios";
import PubSub from "pubsub-js";

class Search extends Component {
    search = () => {
        //连续结构+重命名
        const {keyWordElement: {value: keyWord}} = this
        console.log(keyWord)

        // this.props.updateAppState({isFirst: false, isLoading: true})
        PubSub.publish('atguigu', {isFirst: false, isLoading: true})

        axios.get(`http://localhost:3000/api1/search/users2?q=${keyWord}`).then(
            response => {
                console.log('成功了', response.data.items);
                // this.props.updateAppState({isLoading: false, users: response.data.items})
                PubSub.publish('atguigu', {isLoading: false, users: response.data.items})
            },
            error => {
                console.log('失败了', error);
                // this.props.updateAppState({isLoading: false, err: error.message})
                PubSub.publish('atguigu', {isLoading: false, err: error.message})
            }
        )

    }

    render() {
        return (
            <div>
                <section className="jumbotron">
                    <h3 className="jumbotron-heading">搜索github用户</h3>
                    <div>
                        <input ref={c => this.keyWordElement = c} type="text" placeholder="输入关键词点击搜索"/>&nbsp;
                        <button onClick={this.search}>搜索</button>
                    </div>
                </section>
            </div>
        );
    }
}

export default Search;

setupProxy.js

bash 复制代码
//react 18版本写法
const {createProxyMiddleware} = require('http-proxy-middleware')

module.exports = function (app) {
    app.use(
        createProxyMiddleware('/api1', {
            target: 'http://localhost:5000',
            changeOrigin: true,
            pathRewrite: {'^/api1': ''}
        })
    )
}

运行结果:

5.fetch发送请求

Search.jsx

bash 复制代码
import React, {Component} from 'react';
import axios from "axios";
import PubSub from "pubsub-js";

class Search extends Component {
    search = async () => {
        //连续结构+重命名
        const {keyWordElement: {value: keyWord}} = this

        PubSub.publish('atguigu', {isFirst: false, isLoading: true})

        try {
            const response = await fetch(`http://localhost:3000/api1/search/users2?q=${keyWord}`)
            const data = await response.json()
            console.log(data)
            PubSub.publish('atguigu', {isLoading: false, users: data.items})
        } catch (error) {
            console.log('请求出错', error)
            PubSub.publish('atguigu', {isLoading: false, err: error.message})
        }
    }

    render() {
        return (
            <div>
                <section className="jumbotron">
                    <h3 className="jumbotron-heading">搜索github用户</h3>
                    <div>
                        <input ref={c => this.keyWordElement = c} type="text" placeholder="输入关键词点击搜索"/>&nbsp;
                        <button onClick={this.search}>搜索</button>
                    </div>
                </section>
            </div>
        );
    }
}

export default Search;

ajax,fetch,xhr,axios,jquery之间的关系

运行结果:

相关推荐
花花鱼5 分钟前
vue3 基于element-plus进行的一个可拖动改变导航与内容区域大小的简单方法
前端·javascript·elementui
k09339 分钟前
sourceTree回滚版本到某次提交
开发语言·前端·javascript
EricWang135830 分钟前
[OS] 项目三-2-proc.c: exit(int status)
服务器·c语言·前端
September_ning30 分钟前
React.lazy() 懒加载
前端·react.js·前端框架
web行路人40 分钟前
React中类组件和函数组件的理解和区别
前端·javascript·react.js·前端框架
番茄小酱00141 分钟前
Expo|ReactNative 中实现扫描二维码功能
javascript·react native·react.js
Yawesh_best1 小时前
思源笔记轻松连接本地Ollama大语言模型,开启AI写作新体验!
笔记·语言模型·ai写作
超雄代码狂1 小时前
ajax关于axios库的运用小案例
前端·javascript·ajax
长弓三石1 小时前
鸿蒙网络编程系列44-仓颉版HttpRequest上传文件示例
前端·网络·华为·harmonyos·鸿蒙
小马哥编程1 小时前
【前端基础】CSS基础
前端·css