前后端交互二(jsonp原理及封装,搭建node服务器创建jsonp接口,jsonp实际运用)

84 篇文章 3 订阅
7 篇文章 1 订阅

目录

1.课程目标及知识要点

2.ajax问题

2.1问题重现

2.2浏览器同源策略

2.3跨域

2.4不受跨域问题(同源策略)影响的资源的引入

3.jsonp

3.1jsonp原理

3.2通过script来实现跨域;

3.3动态创建script实现请求

4.jsonp封装

5.请求百度接口

6.蘑菇街案例实现

6.1使用jsonp实现动态数据的获取及渲染

6.2实现滚动底部数据的重新获取及更新(下拉加载)

7.jsonp问题

8.总结


1.课程目标及知识要点

## 课堂目标

  • 掌握jsonp的原理
  • 会搭建node服务器创建jsonp接口
  • 学会封装jsonp
  • 学会jsonp的实际运用

## 知识要点

  • 跨域解决
  • jsonp原理及封装
  • jsonp服务器搭建
  • jsonp实际运用

2.ajax问题

2.1问题重现

在非同源下,请求不到接口

示例:在非同源情况下,如使用ajax,在3000端口上,请求4000端口上服务器接口,就会请求不到。

html:

<body>
    <button>发送请求</button>
    <script>
        document.querySelector("button").onclick = function(){
            let xhr = new XMLHttpRequest();
            xhr.open("get","http://localhost:4000/getInfo",true);
            xhr.onload = function(){
                console.log(xhr.responseText);
            };
            xhr.send();
        };
    </script>
</body>

 jsonp3000.js

const Koa = require("koa");
const Router = require("koa-router");
const static = require("koa-static");
const koaBody = require("koa-body");

let app = new Koa();
let router = new Router();
app.use(koaBody());
app.use(static("static"));

router.get("/",(ctx,next)=>{
    ctx.body = "请求成功";
});
router.get("/getInfo",(ctx,next)=>{
    ctx.body = "getInfo请求成功";
});

app.use(router.routes());
app.listen("3000");
 

jsonp4000.js

const Koa = require("koa");
const Router = require("koa-router");
const static = require("koa-static");
const koaBody = require("koa-body");

let app = new Koa();
let router = new Router();
app.use(koaBody());
app.use(static("static"));

router.get("/",(ctx,next)=>{
    ctx.body = "请求成功";
});
router.get("/getInfo",(ctx,next)=>{
    ctx.body = "getInfo请求成功";
});

app.use(router.routes());
app.listen("4000");
 

 在3000端口下访问http://localhost:3000/jsonp.html进行请求:报以下错误

此处即jsonp.html页面实在3000端口上运行,但是去访问了4000端口的getInfo请求,所以就导致了跨域问题。

2.2浏览器同源策略

  • 同源策略是浏览器的一个安全功能,不同源的客户端脚本在没有明确授权的情况下,不能读写对方资源
  • 源 :协议(http/https/ftp/tcp等)、域名和端口号

2.3跨域

跨域即非同源,非跨域即同源

区分:协议,域名,端口,都相同就是同源,其中有一个不同就是非同源。

2.4不受跨域问题(同源策略)影响的资源的引入

以下资源引入不收同源策略影响:

  • <script src="..."></script>
  • <img>
  • <link>
  • <iframe>

3.jsonp

JSONP*(JSON with Padding)解决跨域问题;可以让网页从别的域名(网站)那获取资料,即跨域读取数据。

3.1jsonp原理

jsonp就是通过script标签不受跨域问题影响去实现的。

返还时,返还可执行的js变量或数据;

3.2通过script来实现跨域;

 <script src="http://localhost:4000/getInfo?name=张三"></script>

jsonp4000.js:

const Koa = require("koa");
const Router = require("koa-router");
const static = require("koa-static");
const koaBody = require("koa-body");

let app = new Koa();
let router = new Router();
app.use(koaBody());
app.use(static("static"));

router.get("/",(ctx,next)=>{
    ctx.body = "请求成功";
});
router.get("/getInfo",(ctx,next)=>{
    // 推送变量(使用script直接引入问题:直接推送变量会造成变量污染)
    let a = 20;
    ctx.body = "var a = 20";
    // ctx.body = "getInfo请求成功";
});

app.use(router.routes());
app.listen("4000");
 

jsonp-script.html: 

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>jsop使用script标签实现跨域</title>
</head>
<body>
    <script src="http://localhost:4000/getInfo?name=张三"></script>
    <script>
        console.log(a);
        
    </script>
</body>
</html>

问题:

  • 使用script直接引入问题:直接推送变量会造成变量污染(html页面中可能js也定义了a变量);
  • 使用script标签(querystring)提交数据时,参数如果是变量时不好带参数变量(?name=zs)
  • 可能会导致异步问题

3.3动态创建script实现请求

通过执行回调的方式发送请求数据,以解决变量污染和异步问题。

步骤:

  • 通过let spt = createElement("script")创建script标签;
  • 通过spt.src = "http://localhost:4000/getInfo"发送请求;
  • document.querySelector(head").appendChild(spt)加到DOM中
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>动态创建script实现请求</title>
</head>
<body>
    <button>发送请求</button>
    <script>
        let btn = document.querySelector("button");
        btn.onclick = function(){
            //点击button时,动态创建script标签
            let spt = document.createElement("script");
            spt.src = "http://localhost:4000/getInfo";
            // 将创建的script标签append到head标签中
            document.querySelector("head").appendChild(spt);
        }
        console.log(a);
    </script>
</body>
</html>

jsonp4000.js同上;

router.get("/getInfo",(ctx,next)=>{
    // 推送变量(使用script直接引入问题:直接推送变量会造成变量污染)
    let a = 20;
    ctx.body = "var a = 20";
    // ctx.body = "getInfo请求成功";
});

结果:

 

问题:以上处理方式会涉及到异步问题(会返回数据a,但客户端获取不到变量a的值)

解决方式一:客户端通过onload加载完成后才能获得变量(但是通过这种方式并不好)。

<body>
    <button>发送请求</button>
    <script>
        let btn = document.querySelector("button");
        btn.onclick = function(){
            //点击button时,动态创建script标签
            let spt = document.createElement("script");
            spt.src = "http://localhost:4000/getInfo";
            // 将创建的script标签append到head标签中
            document.querySelector("head").appendChild(spt);
            
            spt.onload = function(){
                console.log(a);
            }
        }
        
    </script>
</body>

解决方式二:通过回调函数解决。客户端定义执行函数,并通过queryString设置回调函数cb;把需要的数据通过参数带到服务端,服务端通过ctx.query.cb进行接收,服务端不会执行这个函数,而是通过ctx.body=`${cb}(a)`重新发回到客户端。

注意:服务端发回到客户端的数据必须是字符串。如果数据是对象,需要通过JSON.stringfy(obj)转为字符串后才能传递。

<body>
    <button>发送请求</button>
    <script>
        // 客户端定义回调函数
        function cbfn(param){
            console.log(param);
        }

        let btn = document.querySelector("button");
        btn.onclick = function(){
            //点击button时,动态创建script标签
            let spt = document.createElement("script");
            spt.src = "http://localhost:4000/getInfo?cb=cbfn";
            // 将创建的script标签append到head标签中
            document.querySelector("head").appendChild(spt);
        }
        
    </script>
</body>
router.get("/getInfo",(ctx,next)=>{
    // 推送变量(使用script直接引入问题:直接推送变量会造成变量污染)
    let a = 20;

    // 服务端通过ctx.query.cb接收到回调函数
    let cb = ctx.query.cb;
    // 再通过ctx.body返回给客户端执行(注意a的取值方式)
    ctx.body = `${cb}(${a})`;
});

传递对象:

    // 注意返回数据必须是字符串,如果返回数据是对象,需要通过JSON.stringfy()转为字符串后才能进行传递
    let obj = {
        a:20,
        b:30
    }
    let cb = ctx.query.cb;
    ctx.body = `${cb}(${JSON.stringify(obj)})`;

4.jsonp封装

在ajax封装基础上,再封装jsonp(封装动态创建script标签,回调函数等);jsonp只支持get请求方式;

  • 回调函数cb=cbfn单独封装,如jsonp:"callback";
  • 通过dataType区分是普通ajax还是jsonp;
  • ajax中jsonp默认值为cb;
  • 处理jsonp请求(实际上和ajax没有关联)。如果有dataType且值为jsonp时才进行特殊处理(组装url:http://localhost:4000/getInfo?cb=cbFn)jsonpFn(opts.url,opts.data,opts.jsonp,opts.success);然后直接retern false;
  • 封装jsonpFn(url,data,cbName,cbFn)。data需要从对象转为queryString方式;cbName即为cb或callback;
  • cbFn回调函数不能直接获取(直接获取得到的是整个方法体),而是需要通过函数名和函数进行绑定 let fnName = "LMF_"+Math.random().toString().sustr(2),然后通过window[fnName]=cbFn挂载到window上;
  • 动态创建script标签,设置src属性和appendchild()

封装josnp.html:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>封装jsonp</title>
</head>
<body>
    <button>发送请求</button>
    <script type="module">
        import ajax from './js/ajax_jsonp.js';
        
        let btn = document.querySelector("button");
        btn.onclick = function(){
            // 发送jsonp请求
            ajax({
                url:"http://localhost:4000/getInfo",
                // jsonp只能使用get请求方式
                data:{
                    name:"zs",
                    age:20
                },
                // 通过dataType区分是普通ajax请求还是jsonp请求
                dataType:"jsonp",
                // 回调函数名
                jsonp:"callback",
                success(res) {
                    console.log(res);
                }
            })
            
        }

    </script>
</body>
</html>

jsonp4000.js:

const Koa = require("koa");
const Router = require("koa-router");
const static = require("koa-static");
const koaBody = require("koa-body");

let app = new Koa();
let router = new Router();
app.use(koaBody());
app.use(static("static"));

router.get("/",(ctx,next)=>{
    ctx.body = "请求成功";
});
router.get("/getInfo",(ctx,next)=>{
    // 注意返回数据必须是字符串,如果返回数据是对象,需要通过JSON.stringfy()转为字符串后才能进行传递
    let obj = {
        a:20,
        b:30
    }
    let cb = ctx.query.callback;
    ctx.body = `${cb}(${JSON.stringify(obj)})`;
});

app.use(router.routes());
app.listen("4000");
 

ajax_jsonp.js:注意ajax的封装,和ajax与jsonp结合的封装

class MyAjax {
    constructor(options) {
        // 将传入的配置和默认配置进行合并
        this.options = options;
        this.ajax(options);
    }
    ajax(options) {
        let xhr = new XMLHttpRequest();
        // 注意assign()方法是将后面的输入配置合并前面的默认配合,且需要使用参数接收,否则合并不了
        let opts = Object.assign({
            // 因为get/post请求不同带参方式不同,所以需要拼接url
            url: '',
            method: 'get',
            data: '',
            headers:{
                "content-type":"application/x-www-form-urlencoded",
            },
            // 回调函数默认值cb
            jsonp:"cb",
            success(res) {}
        }, options);

        // 处理jsonp请求
        if(opts.dataType === "jsonp"){
            jsonpFn(opts.url,opts.data,opts.jsonp,opts.success);
            return false;
        }

        // 判断get请求时拼接url
        if (opts.method === "get") {
            opts.url = olUrl(opts);
        }
        // 发送请求
        xhr.open(opts.method, opts.url, true);

        // 循环设置头部(可能会有不同的头部信息,不只是content-type),注意设置头部必须在open()方法之后
        for(let header in opts.headers){
            xhr.setRequestHeader(header, opts.headers[header]);
        }
        // 根据不同头部信息,发送请求不同
        let sendData;
        switch(opts.headers['content-type']){
            // 如果请求头content-type为application/x-www-form-urlencoded直接拼接参数
            case 'application/x-www-form-urlencoded':
                sendData = olUrl(opts);
                break;
            case 'application/json':
                sendData = JSON.stringify(opts.data);
                break;
        }

        // 获取返回数据(需要设置到options中的success,在调用时才能使用success()获得返回数据)
        xhr.onload = function(){
            // 用户名密码都正确时会返回页面
            if(xhr.responseText.startsWith("<!DOCTYPE html>")){
                opts.success({
                    msg:"校验成功",
                    code:4
                });
            }else{
                opts.success(JSON.parse(xhr.responseText));//返回数据全部转为对象
            }
        }

        // 判断如果是post请求需要将请求数据一并发送
        if (opts.method === "get") {
            xhr.send();
        } else {
            xhr.send(sendData);
        }

        // 拼接url(只有get请求需要拼接)
        function olUrl(options) {
            let keys = Object.keys(options.data);
            let values = Object.values(options.data);
            let queryString = keys.map((key, index) => {
                return key + '=' + values[index];
            }).join("&");
            return options.url + "?" + queryString;
        }

        // jsonp处理函数
        function jsonpFn(url,data,cbName,cbFn){
            // 回调函数cbFn不能直接拼接(options.success获得的是整个函数体)
            let fnName = "LMF_"+Math.random().toString().substr(2);//LMF_用于区分
            // 通过window[fnName]=cbFn将cbFn函数挂载到window上
            window[fnName]=cbFn;
            
            // 拼接url
            let path = olUrl(options) + "&" + cbName + "=" + fnName;
            
            // 创建动态script标签
            let spt = document.createElement("script");
            spt.src = path;
            document.querySelector("head").appendChild(spt);
        }
    }
}
// 通过定义函数new MyAjax(options),返回MyAjax对象
function ajax(options) {
    return new MyAjax(options);
}
export default ajax;

 结果:

5.请求百度接口

https://sp0.baidu.com/5a1Fazu8AA54nxGko9WTAnF6hhy/su?wd=hello&cb=succFn

需求: 将请求到的结果显示到div中

ajax_jsonp.js同上

baidu_jsonp.html:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>请求百度接口</title>
</head>
<body>
    <input type="text">
    <div class="exchange"></div>
    <script type="module">
        import ajax from './js/ajax_jsonp.js'
        document.querySelector("input").onblur = function(){
            ajax({
                url:"https://sp0.baidu.com/5a1Fazu8AA54nxGko9WTAnF6hhy/su",
                data:{
                    wd:this.value
                },
                jsonp:"cb",
                dataType:"jsonp",
                success(res){
                    console.log(res.s);
                    let exchange = document.querySelector(".exchange");
                    let inner = "<ul>"
                    res.s.forEach(s => {
                        inner += `<li>${s}</li>`;
                    });
                    exchange.innerHTML = inner + "</ul>";
                }
            });
        }
    </script>
</body>
</html>

结果:

6.蘑菇街案例实现

6.1使用jsonp实现动态数据的获取及渲染

https://list.mogu.com/search?callback=jQuery2110599693622515429_1558943916971&_version=8193&ratio=3%3A4&cKey=15&page=1&sort=pop&ad=0&fcid=52014&action=food

 

6.2实现滚动底部数据的重新获取及更新(下拉加载)

  • 可视区域高度:document.documentElement.clientHeight
  • 内容高度:document.documentElement.offsetHeight
  • 滚动高度:内容高度 - 可视区域高度
  • 当前滚动高度:document.documentElement.scrollTop
  • 当当前滚动高度 >=(滚动高度-10) 就再次进行获取。再次获取动态数据时需要page需要更改

案例完整实现:

ajax_jsonp.js同上

index.css:

a{
    text-decoration:none
}
.wrap{
    width: 960px;
    margin: 0 auto;    
}

.headerContainer{
    height: 100px;
    /* background: gray; */
    display: flex;
    justify-content: center;
}
.headerContainer img{
    width: 130px;
    float: left;
}
.catalog{
    height: 100px;
    width: 50px;
    display: block;
    float: left;
    line-height: 100px;
    text-align: center;
    margin-left: 30px;
}
.searchInput{
    float: left;
    width: 462px;
    height: 48px;
    line-height: 48px;
    border: none;
    font-size: 14px;
    background: #f7f7f7;
    color: #666;
    padding: 0 15px;
   
}
.searchContainer{
    height: 100px;
    width: 552px;
    display: flex;
    float: left;
    align-items: center;
    margin-left: 10px;
}
.btn{
    float: right;
    width: 58px;
    height: 48px;
    border: none;
    cursor: pointer;
    border-radius: 0 5px 5px 0;
    background-image: url(https://s10.mogucdn.com/mlcdn/c45406/181205_2llkjh1g0fe27h51ahh5k5f27gkk9_116x96.png);
    background-repeat: no-repeat;
    background-size: 100%;
}
.sub_title{
    line-height: 32px;
    font-size: 24px;
    font-weight: 400;
    color: #333;
    margin-top: 30px;
    margin-bottom: 8px;
}
.nav_box{
    border: 1px solid #e5e5e5;
    height: 24px;
    padding: 7px 0;
    line-height: 24px;
    overflow: hidden;
    margin-top: 10px;
}
.txt{
    float: left;
    width: 46px;
    height: 22px;
    line-height: 22px;
    display: block;
    border: 1px solid #dadada;
    font-size: 12px; 
    
}
.txt span{
    float: left;
    color: #999;
}
.divid{
    float: left;
    margin: 0 5px;
    font-size: 12px;
}
.confirm_btn{
    background: 0 0;
    border: 1px solid #dadada;
    color: #333;
    width: 45px;
    height: 22px;
    line-height: 22px;
    display: inline-block;
    text-align: center;
    margin-left: 10px;
    font-size: 12px;
}
.nav_box{
    padding-left: 15px;
    margin-bottom: 30px;
}


/* 每个商品的样式 */
.item{
    width: 220px;
    height: 374px;
    border: 1px solid #f2f2f2;
    position: relative;
    margin: 0 13px 25px 0;
    display: inline-block
}
.item img{
    width: 220px;
    height: 293px;
}
.bottom-describe{
    padding: 0 8px;
    color: #666;
    font-size: 14px;
}
.priceContainer{
    height: 25px;
    line-height: 25px;
    color: #333;
    width: 100%;
    position: absolute;
    bottom: 0px;
}
.priceContainer img{
    height: 15px;
    width: 16px;
    
}
.describe{
    position: absolute;
    width: 200px;
    height: 40px;
    overflow: hidden;
    bottom: 20px;
}
.mystar{
    float: right;
    margin-right: 20px;
    color:#999;
    font-size: 12px;
}
.mystar img{
    vertical-align: middle;
}
.oldPrice{
    text-decoration: line-through;
}
.iconShow{
    display: flex;
    flex-direction: column;
    justify-content: center;
    margin-left: 20px;
    align-items: center;
}
.iconMessage{
    width: 24px;
    height: 24px;
    display: block;
    background-image: url(https://s10.mogucdn.com/mlcdn/c45406/181016_626h76eek6jgl5621492ck0438af3_48x44.png);
    background-repeat: no-repeat;
    background-size: 100%;
}
.iconCollect{
    width: 24px;
    height: 24px;
    display: block;
    background-image: url(https://s10.mogucdn.com/mlcdn/c45406/181016_1e18fbc53dab5f8f6c45h6e57954d_44x44.png);
    background-repeat: no-repeat;
    background-size: 100%;
}


 mogu.js:

const Koa = require("koa");
const App = require("koa-router");
const static = require("koa-static");
const body = require("koa-body");

let app = new Koa();
let router = new App();

app.use(static(__dirname+"/static"));

router.get("/",(ctx,next)=>{
    ctx.body = "页面成功";
});

app.use(router.routes());
app.listen("8989");

 index.html:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>蘑菇街</title>
    <link rel="stylesheet" href="css/index.css" />
</head>

<body>
    <div class="wrap">
        <div class="headerContainer">
            <img src="https://s10.mogucdn.com/mlcdn/c45406/190102_088f4i166l4gkl08k297h5kk8690i_260x200.png">
            <div class="catalog">
                目录
            </div>
            <div class="searchContainer">
                <input type="text" class="searchInput" />
                <button class="btn"></button>
            </div>
            <div class="iconShow">
                <span class="iconMessage"></span>
                消息
            </div>
            <div class="iconShow">
                    <span class="iconCollect"></span>
                    收藏
                </div>
        </div>
        <h3 class="sub_title">
            全部食品
        </h3>
        <div class="nav_box">
            <div class="txt">
                <span>¥</span>
            </div>
            <span class="divid">-</span>
            <div class="txt">
                <span>¥</span>
            </div>
            <a class="confirm_btn" href="javascript:;">确定</a>
        </div>
        <div class="itemContainer">
            <!-- 商品呈现 -->
            <!-- <div class="item">
                <img
                    src="https://s11.mogucdn.com/mlcdn/c45406/180830_0ggfhcfd757g3jg8k8fjaclc3h123_640x960.jpg_440x587.v1cAC.40.webp" />
                <div class="bottom-describe">
                    <p class="describe">
                        海底捞火锅1盒懒人自煮自热方便速食牛油麻辣即食小火锅麻辣火锅
                    </p>
                    <div class="priceContainer">
                        <b>¥26.8</b>
                        <span class="oldPrice">¥53.6</span>
                        <span class="mystar">
                            <img src="https://s18.mogucdn.com/p2/160908/upload_27g4f1ch6akie83hacb676j622b9l_32x30.png"
                                alt="" />
                            2585
                        </span>
                    </div>
                </div>
            </div> -->
        </div>
    </div>
</body>
    <script type="module">
        import ajax from "./js/ajax_jsonp.js";

        // https://list.mogu.com/search?callback=jQuery2110599693622515429_1558943916971&_version=8193&ratio=3%3A4&cKey=15&page=1&sort=pop&ad=0&fcid=52014&action=food
        // page默认为1,根据加载成功次数+1
        let page = 1;

        getData();

        function getData(){
            ajax({
                url:"https://list.mogu.com/search",
                data:{
                    _version:8193,
                    ratio:"3%3A4",
                    cKey:15,
                    page:page,
                    sort:"pop",
                    ad:0,
                    fcid:52014,
                    action:"food"
                },
                dataType:"jsonp",
                jsonp:"callback",
                success:function(res){
                    console.log(res);
                    // 当获取成功时,渲染页面
                    if(res.status.code === 1001){
                        let foods = res.result.wall.docs;
                        page+=1;
                        foods.forEach(item => {
                            renderFood(item);
                        });
                    }
                }
            });
        }

        // 坚挺鼠标滚动事件,滚动到一定位置,重新加载数据
        document.onscroll = function(){
            // 获取可视区域高度
            let clientHeight = document.documentElement.clientHeight;
            // 获取内容高度
            let contentHeight = document.documentElement.offsetHeight;
            // 滚动高度
            let scrollHeight = contentHeight - clientHeight;
            // 获取当前滚动高度
            let scrollTop = document.documentElement.scrollTop;

            // 如果当前滚动高度所在位置 > 滚动高度
            if(scrollTop >= (scrollHeight-10)){
                getData();
            }
        }

        // 渲染页面
        function renderFood(item){
            let itemDiv = document.createElement("div");
            itemDiv.classList.add("item");
            itemDiv.innerHTML = `
                <img
                    src="${item.img}" />
                <div class="bottom-describe">
                    <p class="describe">
                        ${item.title}
                    </p>
                    <div class="priceContainer">
                        <b>¥${item.price}</b>
                        <span class="oldPrice">¥${item.orgPrice}</span>
                        <span class="mystar">
                            <img src="https://s18.mogucdn.com/p2/160908/upload_27g4f1ch6akie83hacb676j622b9l_32x30.png"
                                alt="" />
                            ${item.cfav}
                        </span>
                    </div>
                </div>
            `;

            let itemContainer = document.querySelector(".itemContainer");
            itemContainer.appendChild(itemDiv);
        }
    </script>
</html>

如果是真实项目需要做预加载处理。

7.jsonp问题

  1. 只能使用get请求
  2. 安全性问题(get请求只能使用queryString进行传参,所以会有安全性问题)

8.总结

  1. jsonp原理
  2. jsonp封装
  3. 会搭建node服务器创建jsonp接口
  4. jsonp实际运用
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值