20230404----重返学习-async与await-无限树形菜单-npm-终端窗口cmd控制台

day-042-forty-two-20230404-async与await-无限树形菜单-npm-终端窗口cmd控制台

async与await

async与await是Promise结合Generator函数的语法糖。

特性

  1. async和await可以分开,一般不会分开。

  2. async必须和函数一块使用,会返回一个Promise实例对象。

    async function getData() {
      return 10;
    }
    console.log(getData());
    getData()
      .then((value) => {
        console.log(value);
      })
      .catch((err) => {
        console.log("err", err);
      });
    
    • 返回值为ES5普通类型—>状态fulfilled,值为返回值。

      async function getData() {
        return 10;
      }
      console.log(getData());
      
      • 返回值默认是状态fulfilled,值为undefined。

        async function getData() {
        }
        console.log(getData());
        
    • 返回值为Promise实例对象

    • 如果函数语句出错,会返回一个Promise实例对象。状态为rejected,值为错误原因对象。

      • 但是控制台依旧会报错,要使用try和catch来捕获

        async function getData() {
            console.log(a);
        }
        console.log(getData());
        
        async function getData() {
          try {
            console.log(a);
          } catch (err) {
            console.log(err);
          }
          return 10;
        }
        console.log(getData());
        
        async function getData() {
            console.log(a);
          return 10;
        }
        getData()
          .then((value) => {
            console.log(value);
          })
          .catch((err) => {
            console.log("err", err);
          });
        
  3. async的提出就是为了结合await使用

    • await后面可以跟普通值,默认会转化为一个Promise实例对象,状态为fulfilled,值为普通值。

      async function getData() {
        console.log("111");
        const theNumber = 5;
        let res1 = await theNumber; //此时theNumber会转化成一个状态为fulfilled,值为5的Promise实例对象,之后变成微任务交由await解析,await解析为结果5,赋值给res1。
        console.log(res1);
      }
      getData();
      
      • 但是一般使用await都要跟Promise实例对象(异步任务)
        • Promise实例对象里面一般是异步任务。

          let thePromise = new Promise((resolve, reject) => {
            setTimeout(() => {
              resolve(100);
            }, 1000);
            console.log("111");
          });
          async function getData() {
            console.log("222");
            let res1 = await thePromise; //此时等待Promise实例对象thePromise转化成一个状态为fulfilled,值为100。之后交由await解析,await解析为结果100,赋值给res1。
            console.log(res1);
          }
          getData();
          console.log("333");
          
    • await同一句代码后面及上方都被认为是同步任务,以普通同步代码的方式在执行。但await同一行代码前面及下方都是异步任务。

      let thePromise = new Promise((resolve, reject) => {
        setTimeout(() => {
          resolve(100);
        }, 1000);
        console.log("111");
      });
      async function getData() {
        console.log("222");
        let res1 = await thePromise;
        console.log(res1);
      }
      getData();
      console.log("333");
      
      //"111"
      //"222"//await同一句代码后面及上方都被认为是同步任务
      //"333"
      //100 //await同一行代码前面及下方都是异步任务。
      
      • await 必须等待同一句代码后面的任务成功完成,才能执行下方的代码

      • await 如果同一句代码后面的任务是失败的,下方的代码将不执行

        let p = new Promise((resolve, reject) => {
          console.log(a);
        });
        p.then((value) => {
          console.log(value);
        }).catch((err) => {
          console.log("err", err);
        });
        
        async function getData() {
            console.log(a);
          return 10;
        }
        getData().then((value) => {
          console.log(value);
        }).catch((err) => {
          console.log("err", err);
        });
        
        • 所以await一般配合try-catch来使用

          async function getData() {
            try {
              console.log(a);
            } catch (err) {
              console.log(err);
            }
            return 10;
          }
          
          • 也有人直接在async函数里整个包上try-catch,类似于.the()链传透。
            • 个人感觉要排查问题时,最好是有可能报错的await外包一层,以方便同事排查。
          const get = function get(url, callback) {
            let xhr = new XMLHttpRequest();
            xhr.open("GET", url, true);
            xhr.onreadystatechange = function () {
              if (xhr.readyState === 4 && xhr.status === 200) {
                let data = JSON.parse(xhr.response);
                callback(data);
              }
            };
            xhr.send();
          };
          function fn1() {
            return new Promise((resolve, reject) => {
              get("./data.json", function (data) {
                resolve(data);
              });
            });
          }
          function fn2() {
            return new Promise((resolve, reject) => {
              get("./data.json", function (data) {
                reject(data);
              });
            });
          }
          function fn3() {
            return new Promise((resolve, reject) => {
              get("./data.json", function (data) {
                resolve(data);
              });
            });
          }
          //同步写法解决异步请求
          async function getData() {
            try {
              let res1 = await fn1();
              let res2 = await fn2();
              let res3 = await fn3();
              console.log(res1, res2, res3);
            } catch (err) {
              console.log("有失败的结果,执行中断了", err);
            }
            // console.log("111")
          }
          getData();
          async function getData1() {
            try {
              let res1 = await fn1();
            } catch (err) {
              console.log("有失败的结果,执行中断1", err);
            }
            
            try {
              let res2 = await fn2();
            } catch (err) {
              console.log("有失败的结果,执行中断2", err);
            }
            try {
              let res3 = await fn3();
              console.log(res1, res2, res3);
            } catch (err) {
              console.log("有失败的结果,执行中断3", err);
            }
            // console.log("111")
          }
          getData1()
          
          //等价于
          fn1().then(value=>{
              console.log(value);
              return fn2()
          }).then(value=>{
              console.log(value);
              return fn3()
          }).then(value=>{
              console.log(value);
          }).catch(err=>{
              console.log(err);
          })
          
      • await的结果值是它后方状态为fulfilled的Promise实例对象的结果值。

      • async可以没有await,await必须先有async。

        • await前面执行的代码都是同步代码,而await后面的代码,都是异步执行的。
          • 单纯的async函数执行时,执行的步骤都是同步的,但返回的值必定是Promise实例对象。可以是返回出去的,也可以是返回普通值被浏览器包装成了Promise实例对象。
          • async+await 就是异步任务,其中async函数在await前执行的代码都是同步的,但await之后执行的,就都是异步的了。
        • 可以单纯地写一个async函数,里面都不使用await。但await关键字要放在async函数内。
  4. 同步写法解决异步任务

    const get = function get(url, callback) {
      let xhr = new XMLHttpRequest();
      xhr.open("GET", url, true);
      xhr.onreadystatechange = function () {
        if (xhr.readyState === 4 && xhr.status === 200) {
          let data = JSON.parse(xhr.response);
          callback(data);
        }
      };
      xhr.send();
    };
    function fn1() {
      return new Promise((resolve, reject) => {
        get("./data.json", function (data) {
          resolve(data);
        });
      });
    }
    function fn2() {
      return new Promise((resolve, reject) => {
        get("./data.json", function (data) {
          reject(data);
        });
      });
    }
    function fn3() {
      return new Promise((resolve, reject) => {
        get("./data.json", function (data) {
          resolve(data);
        });
      });
    }
    //同步写法解决异步请求
    // async function getData() {
    //   try {
    //     let res1 = await fn1();
    //     let res2 = await fn2();
    //     let res3 = await fn3();
    //     console.log(res1, res2, res3);
    //   } catch (err) {
    //     console.log("有失败的结果,执行中断了", err);
    //   }
    //   // console.log("111")
    // }
    // getData()
    async function getData() {
      let res1 = await fn1();
      let res2 = await fn2();
      let res3 = await fn3();
      console.log(res1, res2, res3);
      // console.log("111")
    }
    getData()
    

无限树形菜单

  • ./data.json
[
  {
    "name": "前端开发基础",
    "open": true,
    "children": [
      {
        "name": "HTML5核心知识",
        "children": [
          {
            "name": "新增语义化标签"
          },
          {
            "name": "表单元素新特性"
          },
          {
            "name": "音视屏处理"
          },
          {
            "name": "canvas和webGL"
          },
          {
            "name": "新增JS中的API"
          }
        ]
      },
      {
        "name": "CSS3核心知识",
        "children": [
          {
            "name": "新增选择器"
          },
          {
            "name": "字体图标"
          },
          {
            "name": "常用的样式属性"
          },
          {
            "name": "背景的处理"
          },
          {
            "name": "transform变形"
          },
          {
            "name": "CSS3动画",
            "children": [
              {
                "name": "transition过度动画"
              },
              {
                "name": "animation帧动画"
              },
              {
                "name": "3D动画的处理"
              }
            ]
          },
          {
            "name": "新盒子模型属性",
            "children": [
              {
                "name": "flex弹性盒子模型"
              },
              {
                "name": "box-sizing新盒子模型属性"
              },
              {
                "name": "cloumns多列布局"
              }
            ]
          }
        ]
      },
      {
        "name": "实战案例和布局技巧",
        "children": [
          {
            "name": "实战案例练习",
            "children": [
              {
                "name": "居中处理"
              },
              {
                "name": "同行排列"
              },
              {
                "name": "圣杯布局"
              },
              {
                "name": "双飞翼布局"
              },
              {
                "name": "滑动门"
              },
              {
                "name": "面包屑导航"
              }
            ]
          },
          {
            "name": "响应式布局开发",
            "children": [
              {
                "name": "viewport和dpi适配"
              },
              {
                "name": "@media媒体查询"
              },
              {
                "name": "rem等比缩放"
              },
              {
                "name": "百分比布局"
              }
            ]
          }
        ]
      }
    ]
  },
  {
    "name": "前端开发核心",
    "children": [
      {
        "name": "JS(ES6)核心",
        "children": [
          {
            "name": "基础知识"
          },
          {
            "name": "闭包作用域及堆栈内存"
          },
          {
            "name": "面向对象和THIS处理"
          },
          {
            "name": "同步异步(事件循环、微任务、宏任务)"
          },
          {
            "name": "DOM事件和事件委托"
          },
          {
            "name": "设计模式"
          }
        ]
      },
      {
        "name": "AJAX前后端交互",
        "children": [
          {
            "name": "AJAX基础知识"
          },
          {
            "name": "跨域策略请求"
          },
          {
            "name": "TCP协议相关基础知识"
          },
          {
            "name": "性能和安全的初步优化"
          },
          {
            "name": "常用的AJAX库和插件"
          }
        ]
      },
      {
        "name": "底层原理和高阶JS函数",
        "children": [
          {
            "name": "函数柯里化"
          },
          {
            "name": "compos函数"
          },
          {
            "name": "惰性思想"
          },
          {
            "name": "组件插件封装"
          },
          {
            "name": "底层源码解读"
          }
        ]
      }
    ]
  },
  {
    "name": "前端工程化",
    "children": [
      {
        "name": "VUE全家桶",
        "children": [
          {
            "name": "基础知识"
          },
          {
            "name": "MVVM实现原理"
          },
          {
            "name": "路由处理"
          },
          {
            "name": "vuex公共状态管理"
          },
          {
            "name": "element-ui组件应用和二次封装"
          }
        ]
      },
      {
        "name": "REACT全家桶",
        "children": [
          {
            "name": "基础知识"
          },
          {
            "name": "MVC实现原理"
          },
          {
            "name": "DOM DIFF"
          },
          {
            "name": "Virtual DOM"
          },
          {
            "name": "路由处理"
          },
          {
            "name": "公共状态管理",
            "children": [
              {
                "name": "REACT-REDUX、DAVS/SAGA等"
              },
              {
                "name": "compos函数"
              },
              {
                "name": "惰性思想"
              },
              {
                "name": "组件插件封装"
              },
              {
                "name": "底层源码解读"
              }
            ]
          },
          {
            "name": "高阶租价"
          },
          {
            "name": "antd组件应用和二次封装"
          }
        ]
      },
      {
        "name": "底层原理和高阶JS函数",
        "children": [
          {
            "name": "函数柯里化"
          },
          {
            "name": "compos函数"
          },
          {
            "name": "惰性思想"
          },
          {
            "name": "组件插件封装"
          },
          {
            "name": "底层源码解读"
          }
        ]
      },
      {
        "name": "工程化开发部署",
        "children": [
          {
            "name": "webpack"
          },
          {
            "name": "git"
          },
          {
            "name": "linux"
          }
        ]
      }
    ]
  },
  {
    "name": "前端开发热门点",
    "children": [
      {
        "name": "TypeScript"
      },
      {
        "name": "flutter"
      },
      {
        "name": "react native"
      },
      {
        "name": "小程序"
      },
      {
        "name": "性能和安全的优化"
      }
    ]
  }
]

  • 初代代码
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <title>zTree树形结构菜单</title>
    <style>
      .container {
      box-sizing: border-box;
      margin: 20px auto;
      padding: 10px;
      width: 600px;
      border: 1px dashed #aaa;
      -webkit-user-select: none;
      user-select: none;
    }

    .level {
      display: none;
      font-size: 14px;
      margin-left: 10px;
      /* transition: height .3s linear 0s;
        overflow: hidden; */
    }

    .level.level0 {
      display: block;
      margin-left: 0;
    }

    .level li {
      position: relative;
      padding-left: 15px;
      line-height: 30px;
    }

    .level li .icon {
      position: absolute;
      left: 0;
      top: 9px;
      box-sizing: border-box;
      width: 12px;
      height: 12px;
      line-height: 8px;
      text-align: center;
      border: 1px solid #aaa;
      background: #eee;
      cursor: pointer;
    }

    .level li .icon:after {
      display: block;
      content: "+";
      font-size: 12px;
      font-style: normal;
    }

    .level li .icon.open:after {
      content: "-";
    }

    .level li .title {
      color: #000;
    }

    </style>
  </head>

  <body>
    <div class="container">
      <ul class="level level0" id="zTree">
        <li>
          <a href="javascript:;" class="title">第一级第一项</a>
          <em class="icon open"></em>
          <ul class="level" style="display: block">
            <li>
              <a href="javascript:;" class="title">第二级第一项</a>
            </li>
          </ul>
        </li>
        <li>
          <a href="javascript:;" class="title">第一级第二项</a>
        </li>
        <li>
          <a href="javascript:;" class="title">第一级第三项</a>
        </li>
      </ul>
    </div>

    <!-- IMPORT JS -->
    <!-- <script src="js/index.js"></script> -->
  </body>
</html>
<script>
(function () {
  let zTree = document.getElementById("zTree");
  //事件委托--功能
  zTree.onclick = function (e) {
    let tar = e.target;
    //点击元素的相邻兄弟下一个ul
    let ulbox = tar.nextElementSibling;
    if (tar.tagName === "EM") {
      //点击展开和收缩按钮
      //contains判断是否具有某个class
      //有 open --->删除open   ul隐藏
      if (tar.classList.contains("open")) {
        tar.classList.remove("open");
        ulbox.style.display = "none";
      } else {
        //没有 open --->添加open   ul显示
        tar.classList.add("open");
        ulbox.style.display = "block";
      }
    }
  };

  //"异步方式"获取数据
  function getData() {
    return new Promise((resolve, reject) => {
      let xhr = new XMLHttpRequest();
      xhr.open("get", "./data.json");
      xhr.onreadystatechange = function () {
        if (xhr.readyState === 4 && xhr.status === 200) {
          let data = JSON.parse(xhr.response);
          resolve(data);
        }
      };
      xhr.send();
    });
  }

  //获取数据
  async function fn() {
    let data = await getData();
    let str = render(data);
    zTree.innerHTML = str;
  }
  fn();

  //循环渲染
  function render(data) {
    let str = "";
    data.forEach((item) => {
      let { name, children, open } = item;
      str += `<li>
            <a href="javascript:;" class="title">${name}</a>
            ${
              children
                ? `<em class="icon ${open ? "open" : ""}"></em> <ul class="level" style="display: ${open ? "block" : "none"};"> ${render(children)} </ul>`
                : ""
            }
        </li>`;
    });
    return str;
  }

  //方法一:
  //   getData()
  //     .then((value) => {
  //       console.log(value);
  //     })
  //     .catch((err) => {
  //       console.log(err);
  //     });

  //方法二:
  //   async function render() {
  //     try {
  //       let data = await getData();
  //       console.log(data);
  //     } catch (error) {}
  //   }
  //   render();
})();
</script>
  • 改进
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <title>zTree树形结构菜单</title>
    <!-- IMPORT CSS -->
    <style>
      .container {
        box-sizing: border-box;
        margin: 20px auto;
        padding: 10px;
        width: 600px;
        border: 1px dashed #aaa;
        -webkit-user-select: none;
        user-select: none;
      }
      .container .level {
        display: none;
        font-size: 14px;
        margin-left: 10px;
        /* transition: height .3s linear 0s;
    overflow: hidden; */
      }
      .container .level.level0 {
        display: block;
        margin-left: 0;
      }
      .container .level li,
      .container .level .the-item {
        position: relative;
        padding-left: 15px;
        line-height: 30px;
      }
      .container .level li .icon,
      .container .level .the-item .icon {
        position: absolute;
        left: 0;
        top: 9px;
        box-sizing: border-box;
        width: 12px;
        height: 12px;
        line-height: 8px;
        text-align: center;
        border: 1px solid #aaa;
        background: #eee;
        cursor: pointer;
      }
      .container .level li .icon:after,
      .container .level .the-item .icon:after {
        content: "+";
        display: block;
        font-size: 12px;
        font-style: normal;
      }
      .container .level li .icon.open:after,
      .container .level .the-item .icon.open:after {
        content: "-";
      }
      .container .level li .title,
      .container .level .the-item .title {
        color: #000;
      }
    </style>
  </head>

  <body>
    <div class="container">
      <ul class="level level0" id="zTree">
        <li class="the-item">
          <a class="title" href="javascript:;">第一级第一项</a>
          <em class="icon open"></em>
          <ul class="level" style="display: block">
            <li class="the-item">
              <a href="javascript:;" class="title">第二级第一项</a>
            </li>
          </ul>
        </li>
        <li class="the-item">
          <a href="javascript:;" class="title">第一级第二项</a>
        </li>
        <li class="the-item">
          <a href="javascript:;" class="title">第一级第三项</a>
        </li>
      </ul>
    </div>

    <!-- IMPORT JS -->
  </body>
</html>
<script>
  const theObject = (function () {
    let zTree = document.querySelector(".container>#zTree");
    // console.log(zTree);
    zTree.onclick = function (e) {
      if (e.target.tagName === "EM") {
        // console.log(e.target.classList.contains("open"));
        let siblingUl = e.target.nextElementSibling;
        // console.log([e.target], siblingUl);
        if (e.target.classList.contains("open")) {
          e.target.classList.remove("open");
          siblingUl.style.display = "none";
          // continue
        } else {
          e.target.classList.add("open");
          siblingUl.style.display = "block";
        }
      }
    };

    const getData = function getData() {
      const thePromise = new Promise((resolve, reject) => {
        let xhr = new XMLHttpRequest();
        xhr.open("GET", "./data.json", true);
        xhr.onreadystatechange = function () {
          if (xhr.readyState === 4 && xhr.status === 200) {
            let data = JSON.parse(xhr.response);
            // console.log(data);
            resolve(data);
          }
        };
        xhr.send();
      });
      return thePromise;
    };
    // getData();
    // getData()
    //   .then((value) => {
    //     console.log("方法一thePromise.then().catch()", value);
    //   })
    //   .catch((err) => {
    //     console.log(err);
    //   });

    // const render = async function render() {
    //   try {
    //     let data = await getData();
    //     console.log("方法二async-await", data);
    //   } catch (err) {
    //     console.log(err, "执行中断");
    //   }
    // };
    // render();

    const toLiListString = function toLiListString(liDataList) {
      let theString = ``;
      // console.log(liDataList);
      liDataList.forEach((item, index) => {
        console.log(item);
        let { name, children, open } = item;
        theString += `
        <li class="the-item">
          <a href="javascript:;" class="title">${name}</a>

          ${
            children?.length
              ? `
                <em class="icon ${open ? `open` : ``}"></em>
                <ul class="level" style="display:  ${open ? `block` : `none`}">
                  <!-- <li class="the-item">
                    <a href="javascript:;" class="title">第二级第一项</a>
                  </li> -->
                  ${toLiListString(children)}
                </ul>
                `
              : ``
          }
          
        </li>
      `;
      });
      return theString;
    };

    const render = async function render() {
      let data = await getData();
      const htmlString = toLiListString(data);
      zTree.innerHTML = htmlString;
    };
    render();
  })();
</script>

npm

npm就是包管理器。

npm安装

想要使用npm包管理器,就要先安装node.js。

到node.js官网上,下载稳定版。

下载好之后一直点击下一步,最好用默认方式,别乱改路径。

npm的作用

  1. 下载项目资源包
    • 一般情况下,需要进入官方网站,下载对应的资源包,如swiper。
    • 而npm就可以使用资源包的名称直接下载,不需要去官方网站。
      • 不过下载下来的资源包,还需要进行管理。
  2. 管理项目资源包
    1. 基本概念
      1. 依赖清单 package.json
        • devDependencies 服务器上已经没有了,在服务器打包时用不到的依赖
          • 不过服务器环境将不可以使用它里面的依赖。
        • dependencies 项目部署到服务器上用到的依赖
          • 不过开发环境还也可以使用它里面的依赖。
      2. 资源包的仓库 node_modules
        • 前提条件
          1. 文件夹不能含有中文,可能会造成一些错误。因为中文字符在window或一些服务器或一些包里,可能支持度不太好。
          2. 祖先文件夹 不能含有node_modules文件夹
    2. 使用npm常用命令管理项目资源包

常用命令

  • 初始化,创建依赖清单
npm init -y//会在当前文件夹中创建package.json这个文件
常用依赖项管理命令
  1. 下载资源包

    npm install 资源包名称 --save//完全
    // npm install(简写:i) XXX (--save省略)
    // npm install XXX --save(简写:-S)
    npm i XXX
    npm i XXX -S
    
    • 依赖清单记录到—》dependencies(生产环境)项目部署到服务器
    • 下载的资料包会放到—》node_modules
  2. 下载资料包

    npm install(简写:i) XXX --save-dev(简写:-D)
    npm i XXX -D
    
    • 依赖清单记录到—》devDependencies(开发环境)本地自己开发的时候
    • 下载的资料包会放到—》node_modules
  3. 删除资料包 npm uninstall(简写:uni) XXX

资料包的网站:https://www.npmjs.com/(国外网站),只要这个网站上含有的资料包都可以下载

  1. 安装指定版本
    npm i XXX@3.6.2(版本号)

  2. 查看版本号

npm view XXX versions

alpha 内测版{不稳定,有BUG}
beta 公测版
rc 最终测试版「和正式版差不多了」
stable 正式稳定版(不写)

  1. 最后一个稳定版
    npm i XXX@latest

  2. 最后一个即将发布版本(最后一个测试内测版本)
    npm i XXX@next

全局安装

全局安装(前面的路径随意) :安装到你的电脑的某个位置(电脑规定的文件夹)
电脑上的任何文件都可以用
只能安装1个版本

本地安装(前面的路径自己规定的文件夹) :直接安装到自己规定文件夹
只能在当前文件夹里使用
可以安装多个版本(多个文件夹 1文件夹–版本1 2文件夹–版本2)

全局安装(前面的路径随意)
npm i xxx --global(简写: -g)

苹果电脑全局安装要加***** sudo
sudo npm i xxx -g 还有可能输入 密码

查看全局安装目录
npm root -g

删除全局安装
npm uni xxx --g

跑环境-安装依赖

git 下载的资料文件家中都不会含有 node_modules,又需要node_modules

*****跑环境:(依据 package.json)

npm i (开发环境和生产环境都跑下来)
npm i --production(只跑生产环境(dependencies))

npm的替代方案

1.下载加速(去国内站点下载)淘宝镜像

1.1使用淘宝镜像(下载cnpm)
npm install -g cnpm --registry=https://registry.npm.taobao.org

1.2使用cnpm(有可能有点小问题 丢包)
之后使用 cnpm 来代替 npm,----》cnpm和npm可以共存

2.下载加速(去国内站点下载)yarn*****
2.1 下载yarn npm i yarn -g

2.2 yarn init -y
2.3 yarn add XXX
yarn add xxx --dev 安装开发依赖
2.4 yarn remove XXX

yarn add/remove xxx/xxx@xxx/xxx@latest…

2.5跑环境
yarn install
yarn install --production

2.6全局安装
yarn global add xxx 很少用

还用yarn 要保证有 yarn.lock----》yarn install

终端窗口

cmd窗口常用命令

清屏:cls 苹果(clear)
ctrl+c(多次) —>y 中断命令

进阶参考

  1. node.js 官网 - 用来下载node.js的
  2. Node.js安装及环境配置之Windows篇
  3. npm官网 - 国外 - 查找资源包
  4. 详解从零创建自己的NPM包
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值