前端人的后台语言Node.js

Node: node是为javascript提供服务端执行代码的环境(平台)
JavaScript目前是谷歌浏览器的 V8 引擎解析
NodeJs中JavaScript的组成部分:
ECMAscript 全局成员 核心API模块
NodeJs中没有BOM和DOM 但是ECMAscript是共有的
let 和 const

	let:没有变量提升
			必须先定义后使用
			存在 {} 块级作用域
	const:不存在变量提升  需要先定义后使用
				定义的是常量 不能被重复赋值
				当定义常量的时候,必须要定义并且初始化,否则会报错
				存在 {} 块级作用域

变量的解构赋值
定义:就是吧某个对象中的属性,当作变量给解放出来,这样今后就可以直接当作变量直接使用

可以使用 : 为解构出来的变量重新命名
// 变量的解构赋值
  const { name : name123, age, gender } = person
  console.log(name123)

示例

let user = {
  name: 'zs',
  age: 20,
  gender: '男'
}

// 解构赋值的语法
const { name: username, age: userage, gender } = user
username = 'ls'
console.log(username)
console.log(userage)
console.log(gender)

let name = user.name
let age = user.age
console.log(name)
console.log(age)

箭头函数
箭头函数本质是一个匿名函数
箭头函数内部的this指向永远和函数外部的this指向保持一致
如果我们省略了 右侧函数体的 { },那么,默认会把 右侧函数体中代码的执行结果,当作箭头函数的调用结果 return 出去;

function show() {
    console.log('这是普通 function 定义的 show 方法')
}

利用箭头函数可以改造为 ::

()=>{}    (形参列表) => { 函数体代码 }

核心API:
模块的引入使用 require(“需要引入的模块名称”)
读取文件:
readFile()

// 读取文件,首先先调用fs模块 使用require()
const fs = require('fs')

// 读取文件readFile("文件路径","文件编码格式",回调函数)  
// 回调函数有两个参数(err,data)  err为失败的参数,data为成功的参数
fs.readFile('./files/11.txt', 'utf-8', function(err, data) {
    if (err) {
        return console.log("读取失败" + err.message)
    }
    console.log('读取成功,内容是' + data);
})
// 使用es6中的用法读取文件
fs.readFile('./files/1.txt', 'utf-8', (err, data) => {
    if (err) return console.log("读取失败" + err.message)
    console.log('读取成功,内容是' + data);
})

写入文件:

// 写入文件
// 写入文件writeFile("文件路径","写入的文件数据","文件编码格式默认为utf-8",回调函数)  \
// 回调函数有一个参数(err)  err为失败的参数
fs.writeFile("./files/2.txt", "789", err => {
        if (err) return console.log("写入失败" + err.message)
        console.log('写入成功');
    })
// 如果在使用 fs.writeFile 的时候,要写入的文件不存在,则直接写入;如果要写入的文件路径已经存在,则会覆盖之前的文件;

追加文件:

// 追加文件
// 写入文件appendFile("文件路径","追加的文件数据","文件编码格式默认为utf-8",回调函数)  \
// 回调函数有一个参数(err)  err为失败的参数
fs.appendFile("./files/1.txt", "\nnihao", err => {
        if (err) return console.log("添加失败" + err.message)
        console.log('添加成功');
    })
// 如果要追加的文件路径不存在,则会先尝试创建这个文件,然后再向创建的文件中,追加具体的内容;


关于文件件中路径的问题:

// 关于文件的路径问题,这里用的相对路径有的时候会出错,因为代码执行的路径是相对于代码执行的node的所在路径执行的,就是代码执行的相对目录,所以此时我们需要使用__dirname来解决路径出错的问题
fs.appendFile(__dirname + "/files/1.txt", "\nnihao,laobiao", err => {
    if (err) return console.log("添加失败" + err.message)
    console.log('添加成功');
})

 __dirname  是一个(伪)全局成员    这个是绝对路径   拿到的路径是代码所在的路径来说的 
 __filename  拿到的是文件名

读取文件信息(大小 路径 创建时间):

const fs = require('fs')

fs.stat(__dirname + '/files/1.txt', (err, stats) => {
  if (err) return console.log(err.message)
  console.log(stats.size) // 单位是 字节   Byte
  console.log(stats.birthtime)  // 2018‎年‎6‎月‎7‎日,‏‎18:19:11    2018-06-07T10:19:11.186Z

  console.log(stats.isFile()) // 通过 isFile() 方法判断是否为 文件
  console.log(stats.isDirectory()) // isDirectory() 判断是否为目录
})

复制文件:

const fs = require('fs')

fs.copyFile(__dirname + '/files/1.txt', __dirname + '/files/1-copy.txt', (err) => {
  if (err) return console.log('拷贝失败:' + err.message)
  console.log('拷贝成功!')
})

读取指定文件中所有文件的名称:

console.log(path.basename(str)) // 获取文件名称的

console.log(path.dirname(str)) // 获取文件所在的路径的

console.log(path.extname(str)) // 获取文件的扩展名
path.join( )  拼接路径
path.join(   __dirname , './a.txt'   )    拼接的是一个绝对路径

foreach和for的区别:
break 对for 生效 对forEach 不生效

JavaScript是单线程 浏览器/Node是多线程

	模块化:是一种规定或者规范
				高内聚 低耦合
				好处:代码复用,减少命名污染
				NodeJs遵循CommonsJs规范
				同步的代码 引入模块用require()
				每一个js都是一个模块
				exports  modlue.exports 

web 端js 么有模块化

如果要想从一个js访问另一个js 需要使用require()进行引用
全局变量使用 global 进行定义挂载,但是一般不建议使用

模块作用域:
module(模块标示)
require(引用模块) 我们都是依靠require来引入模块
exports(暴露模块成员)

module.exports 和 exports关系
在NodeJs中 我们写的每一个js都是独立的模块,都是有块级作用域的,如果想要在外部被访问到,我们需要将数据暴露出去,module.exports 和 exports 他们默认的是指向同一个空对象,其中 module.exports 和 exports 都可以将数据暴露出去,但是前者的权重更高,我们在实际来发中都使用前者进行暴露是数据

浏览器的模块化规范
AMD模块化规范的代表是 RequireJs
CMD模块化规范的代表是 seaJs
ES6的模块化是趋势
什么是模块化:
模块化 就是规范 规定
模块是js
什么是包(packages)
包就是多个模块、代码、其他资源的组合
包 都要以一个单独的目录而存在
package.json 必须在包的顶层目录下
pagkage.json 必须要符合json的格式,并且要包含name version main
name:包的名字
version:包的版本号
main:包的入口文件
二进制文件放在bin目录下
JavaScript代码放在lib目录下
文档放在doc目录下
单元测试放在test目录下
README 文档说明
NodeJs对包要求没有那么严格,只要顶层目录下有packages.json即可
核心模块:
随着NodeJs安装的都是核心模块
第三方模块:
在npm上下载的都是第三方模块
用户自定义的模块:
用户自己写的js就是一个独立的模块
npm
什么是npm :
提供第三方模块的托管网站
全局的包:安装在计算局全局环境的包,安装的包可以在当前电脑的任何目录下,直接通过命令来访问。
安装全局包:通过 npm install 包名 -g 其中-g 参数,就是将包安装到全局环境中
全局包的安装路径:C:\Users\用户目录\AppData\Roaming\npm
卸载全局包:通过 npm uninstall 包名 -g

安装和卸载本地包
什么是本地包:跟着项目安装的包 ,本地包都会被安装在node_modules 目录下
注意:
如果拿到一个空项目,必须在当前项目的根目录下 终端中运行 npm init -y 命令,来初始化一个package.json的配置文件,否则包无法安装到本地项目中。
如何安装本地包:npm install 包名 -S 安装的包都在项目下的node_modules中
其中 package.json文件中记录了曾经安装的文件的下载地址,方便下次直接下载包
如何卸载本地包: npm uninstall/remove 包名 -S/-D 卸载
注意:dependencies节点 是项目上线部署时需要依赖的项
devDependencies节点 是项目开发阶段需要依赖的项,但是当项目需要上线的时候,devDependencies节点中的包就不需要了
当使用 npm i 快速装包的时候 npm会检查package.json文件,所有的依赖项都会被安装到项目中
使用 --production 表示只安装dependencies节点 中的包 只有当项目要上线了才使用该命令
由于npm是国外服务器 下载相对较慢,此时我们可以下载cnpm进行下载

使用Node构建web服务器
首先实现静态资源服务器
需要使用http模块
建立服务
代码如下:

// 构建web服务器,需要引入http模块
const http = require("http");

// 需要读取网页中的内容 需要引入fs模块
const fs = require("fs");

// 需要用到path拼接路径 需要引入path
const path = require("path");

// 创建服务器
const server = http.createServer();

// 设置客户端请求
server.on("request", function(rec, res) {
//为了防止中文乱码,我们设置请求头
res.writeHeader(200, { 
	'Content-Type': 'text/plain; charset=utf-8'
})
    // 定义我们获得的地址
    const url = rec.url;
    if (url === "/" || url === "/index.html") {
        // 通过readFile()读取文件
        fs.readFile(path.join(__dirname, "./index.html"), (err, data) => {
            if (err) return res.end("404 Not Found");
            res.end(data); // res.end()方法 可以接收两种类型的数据   String  二进制类型的数据
        });
    } else if (url === "/movice.html") {
        // 通过readFile()读取文件
        fs.readFile(path.join(__dirname, "./movice.html"), (err, data) => {
            if (err) return res.end("404 Not Found");
            res.end(data);
        });
    } else if (url === "/about.html") {
        // 通过readFile()读取文件
        fs.readFile(path.join(__dirname, "./about.html"), (err, data) => {
            if (err) return res.end("404 Not Found");
            res.end(data);
        });
    }
});
server.listen(3000, function() {
    console.log("server running at http://127.0.0.1:3000");
});

为了方便开发,避免程序员重复的启动服务器,我们使用第三方包 nodemon

express 框架
他是进一步封装了http模块,提供了更好的API,他没有覆盖原生的api,基于原生的进一步开发,更好的创建express服务器

使用exprss步骤:
下载安装express 导入第三方express模块 使用 const app=exprss()创建服务器实例 通过app.get()或者app.post()来监听客户端下的get 或者post请求 语法如下:
监听 GET 请求:app.get(‘请求地址’, (req, res) => { 处理函数 })
监听 POST 请求: app.post(‘请求地址’, (req, res) => { 处理函数 })
启动 express 服务器:通过 app.listen(端口, IP地址, 启动成功后的回调函数) 启动服务器;

express中存在的快捷方法:res.send() 支持所有格式的文件
res.sendFile()
有两种用法:1.res.sendFile(path.join(__dirname,’./view/index.html’)) 绝对路径
2.res.sendFile(’./view/movie.html’, { root: __dirname })

使用experss.static()可以快速托管静态资源 express.static(“托管的静态资源目录”)
语法1:

 app.use(express.static('public'));

app.use()方法 是专门用来注册的中间件
express.static() 是express内置的中间件

语法2:app.use('/虚拟目录', express.static('public'))

案例:
托管静态资源案例

// 引入express模块
const express = require("express");
const app = express();

// 使用中间件use
app.use(express.static('./views'));   // 就在此js文件的根目录下进入 views  对里面存放的静态资源托管
app.use('/node_modules', express.static('./node_modules')); // 和上面的一样

app.listen(6001, () => {
    console.log("http://127.0.0.1:6001");
});

使用express及模板引擎ejs动态渲染页面
使用ejs步骤:
下面涉及到的都是固定写法
下载安装ejs包 npm install ejs
使用app.set() 设置默认的模板引擎 app.set('view engine', 'ejs')
使用app.set()设置 默认模板页面的存放路径app.set(‘views’, ‘./views’)
使用res.render()来渲染模板页面 res.render('index.ejs', { 要渲染的数据对象 })
案例如下:

const express = require("express");
const app = express();
app.set('view engine', 'ejs');
app.set('views', './files'); // 前面的views 是虚拟路径
app.get('/', function(req, res) {
    res.render('模板引擎ejs.ejs', { name: "ll", age: 23, hobby: ["打游戏", "睡觉", "吃饭", "唱K"] })
})
app.listen(8800, function() {
    console.log("http://127.0.0.1:8800");
})

使用art-template 模板引擎进行页面渲染
使用步骤:下载art-template 包
自定义一个模板引擎 app.engine('自定义模板引擎的名称', 渲染函数)
将自定义的模板引擎,配置为express的默认模板引擎app.set('view engine', '具体模板引擎的名称')
配置模板页面存放路径 `app.set(‘views’, ‘路径’)
案例如下:

const express = require('express')
const app = express()

// 1. 使用 app.engine() 方法自定义模板引擎
app.engine('html', require('express-art-template'))
// 2. 使用 app.set('view engine', '指定模板引擎名称') 来配置项目中用到的模板引擎
app.set('view engine', 'html')
// 3. 配置模板页面的存放路径
app.set('views', './art_page')

app.get('/', (req, res) => {
  res.render('index.html', { name: 'zs', age: 22, hobby: ['玩游戏', '唱歌'] })
})

app.listen(3000, () => {
  console.log('server running at http://127.0.0.1:3000')
})

路由:

什么是路由:路由就是一种对应关系
什么是后端路由:前端请求的url地址,后端都有对应的一个处理函数,那么 这种URL地址到 处理函数之间的对应关系,就叫做后端路由;
在Express中,路由的主要职责 就是 把请求分发到对应的处理函数中;

在express中如何定义和使用路由:

  // 1. 封装单独的 router.js 路由模块文件
  const express = require('express')
  // 创建路由对象
  const router = express.Router()

  router.get('/', (req, res)=>{})
  router.get('/movie', (req, res)=>{})
  router.get('/about', (req, res)=>{})

  // 导出路由对象
  module.exports = router

express 创建的app服务器,如何使用路由模块

  // 导入自己的路由模块
  const router = require('./router.js')
  // 使用 app.use() 来注册路由
  app.use(router)

1、需要调用路由的服务器

// 1. 导入express
const bodyparser = require("body-parser");
const express = require('express');
// 2. 调用 express() 创建服务器
const app = express();
app.use(bodyparser.urlencoded({ extended: false }));

// 由于浏览器的访问端口号6001和后台服务器5001的不同,存在跨域
// 使用cors解决跨域问题
const cors = require("cors");
app.use(cors());

// 导入路由模块
const router = require("./router.js");
// 注册路由
app.use(router);

// 4. 调用 app.listen() 方法启动 express 服务器
app.listen(7001, () => {
    console.log('server running at http://127.0.0.1:7001');
});

2 、路由对应案例

const express = require("express");

const router = express.Router();

const ctr = require("./conreoller.js");

// 3. 调用 app.get() 方法,来监听客户端的请求,并指定要监听的 URL 地址 和 处理函数
router.get('/', ctr.getSuccess);

// 查询所有英雄
router.get("/dataall", ctr.dataall);

// 添加英雄
router.post("/addhero", ctr.addHero);

// 根据id查询英雄信息
router.get("/getHreo/:id", ctr.getHero);

// 根据id修改对应英雄的信息
router.post("/setHero/:id", ctr.setHero);

// 根据id软删除英雄
router.get("/delehero/:id", ctr.deleHero);

// 根据id软恢复英雄
router.get("/backHero/:id", ctr.backHero);

// 暴露路由
module.exports = router;

3 、路由对应的调用的模块

const conn = require("./db.js");

module.exports = {
    getSuccess: (req, res) => {
        // 使用 express 调用 http 模块中的 end 方法向客户端发送数据
        // res.end('你好!')
        res.send('请求API接口成功');
    },
    dataall: (req, res) => {
        let sql = "select * from heros ";
        conn.query(sql, (err, result) => {
            if (err) return res.send(err.message);
            res.send({
                status: 200,
                data: result
            })
        })
    },
    addHero: (req, res) => {
        // console.log(req.body);
        const hero = req.body;
        const dt = new Date();
        const y = dt.getFullYear();
        const m = (dt.getUTCMonth() + 1).toString().padStart(2, "0");
        const d = dt.getDay().toString().padStart(2, "0");
        const hh = dt.getHours().toString().padStart(2, "0");
        const mm = dt.getMinutes().toString().padStart(2, "0");
        const ss = dt.getSeconds().toString().padStart(2, "0");
        hero.ctime = y + "-" + m + "-" + d + " " + hh + ":" + mm + ":" + ss;
        const sql = "insert into heros set ?";
        conn.query(sql, hero, (err, result) => {
            if (err) return res.send({ status: 500, msg: err.message })
            res.send({ status: 200, data: null, msg: "成功" })
        })
    },
    getHero: (req, res) => {
        const id = req.params.id;
        const sql = "select * from heros where id=?"
        conn.query(sql, id, (err, result) => {
            if (err) return res.send({ status: 500, msg: err.message })
            res.send({
                status: 200,
                data: result
            })
        })
    },
    setHero: (req, res) => {
        const id = req.params.id;
        const hero = req.body
        const sql = "update heros set ? where id=?";
        conn.query(sql, [hero, id], (err, result) => {
            if (err) return res.send({
                status: 500,
                msg: err.message
            });
            res.send({
                status: 200,
                data: result
            })
        })
    },
    deleHero: (req, res) => {
        // 获取id
        const id = req.params.id;
        // console.log(id);
        const sql = "update heros set isdel=1 where id=?"
        conn.query(sql, id, (err, result) => {
            if (err) return res.send({
                status: 500,
                msg: err.message
            });
            res.send({
                status: 200,
                data: result
            })
        })
    },
    backHero: (req, res) => {
        // 获取id
        const id = req.params.id;
        const sql = "update heros set isdel=0 where id=?"
        conn.query(sql, id, (err, result) => {
            if (err) return res.send({
                status: 500,
                msg: err.message
            });
            res.send({
                status: 200,
                data: result
            })
        })
    }

}

4 、为了代码便于维护,将数据库的连接单独封装为模块

const mysql = require("mysql");
let conn = mysql.createConnection({
    host: "127.0.0.1",
    user: "root",
    password: "root",
    database: "node_001"
});
module.exports = conn;

express中的中间件
中间件的作用就是共享数据,在实际开发中我们用到的最多的就是第三方中间件
中间件分5类:
应用级别的中间件:挂载到app上的中间件 app.get(‘URL地址’, (req, res, next)=> {})
路由级别的中间件:挂载到router对象上的中间件router.get(‘url地址’, (req, res, next)=>{})
错误级别的中间件:回调函数中有四个参数app.use((err, req, res, next)=>{}) 唯一内置的中间件;express.static()
第三方中间件:非express框架提供的,需要程序员手动安装才能使用的中间件;body-parser 解析post 表单数据

模块的加载机制:
先从缓存中加载
核心模块的加载机制: 先查找缓存;如果缓存中没有,再去加载核心模块;
用户模块的加载机制:
首先,严格按照指定的名称去查找
其次,尝试加载后缀名是 .js 的文件
如果没有.js的文件,则尝试加载 .json 结尾的文件
如果没有 .json 的文件,则尝试加载 .node 结尾的文件
查找规则:index -> index.js -> index.json -> index.node

express中获取参数的几种形式
地址栏中通过 ? 传输的数据 http://127.0.0.1:3001/user?id=10&name=zs 中的查询参数:
直接使用req.query获取参数即可
在地址栏中通过 http://127.0.0.1:3001/:user/:id 传递参数

  • 假设后台的路由是 app.get(’/user/:id/:name’, (req, res) => {})
  • 假设客户端浏览器请求的URL地址为:http://127.0.0.1:3001/user/10/zs
    直接使用req.params获取参数

从post表单中获取数据,我们使用body-parser来解析表单数据

使用步骤如下:

借助于body-parser来解析表单数据

  • 安装:npm i body-parser -S
  • 导入:const bodyParser = require(‘body-parser’)
  • 注册中间件:app.use(bodyParser.urlencoded({ extended: false }))
  • 使用解析的数据: req.body 来访问解析出来的数据

web开发模式:
混合开发模式
前后端分离
- 后端负责操作数据库、给前端暴露接口

  • 前后端分离的好处:保证了各个岗位的职责单一;
  • 前端负责调用接口,渲染页面、前端就可以使用一些流行的前端框架 Vue, React, Angular

数据库的增删改查

/* eslint-disable semi */
// 1. 导入express
const bodyparser = require("body-parser");
const express = require('express');
// 2. 调用 express() 创建服务器
const app = express();
app.use(bodyparser.urlencoded({ extended: false }))
const mysql = require("mysql");
let conn = mysql.createConnection({
    host: "127.0.0.1",
    user: "root",
    password: "root",
    database: "node_001"
});
// 3. 调用 app.get() 方法,来监听客户端的请求,并指定要监听的 URL 地址 和 处理函数
app.get('/', (req, res) => {
    // 使用 express 调用 http 模块中的 end 方法向客户端发送数据
    // res.end('你好!')
    res.send('你好!');
})

// 查询所有英雄
app.get("/dataall", (req, res) => {
        let sql = "select * from heros ";
        conn.query(sql, (err, result) => {
            if (err) return res.send(err.message);
            res.send({
                status: 200,
                data: result
            })
        })
    })
    // 添加英雄
app.post("/addhero", (req, res) => {
    console.log(req.body);
    const hero = req.body;
    const dt = new Date();
    const y = dt.getFullYear();
    const m = (dt.getUTCMonth() + 1).toString().padStart(2, "0");
    const d = dt.getDay().toString().padStart(2, "0");
    const hh = dt.getHours().toString().padStart(2, "0");
    const mm = dt.getMinutes().toString().padStart(2, "0");
    const ss = dt.getSeconds().toString().padStart(2, "0");
    hero.ctime = y + "-" + m + "-" + d + " " + hh + ":" + mm + ":" + ss;
    const sql = "insert into heros set ?";
    conn.query(sql, hero, (err, result) => {
        if (err) return res.send({ status: 500, msg: err.message })
        res.send({ status: 200, data: null, msg: "成功" })
    })
})

// 根据id查询英雄信息
app.get("/getHreo/:id", (req, res) => {
        const id = req.params.id;
        const sql = "select * from heros where id=?"
        conn.query(sql, id, (err, result) => {
            if (err) return res.send({ status: 500, msg: err.message })
            res.send({
                status: 200,
                data: result
            })
        })
    })
    // 根据id修改对应英雄的信息
app.post("/setHero/:id", (req, res) => {
        const id = req.params.id;
        const hero = req.body
        const sql = "update heros set ? where id=?";
        conn.query(sql, [hero, id], (err, result) => {
            if (err) return res.send({
                status: 500,
                msg: err.message
            });
            res.send({
                status: 200,
                data: result
            })
        })
    })
    // 根据id软删除英雄
app.get("/delehero/:id", (req, res) => {
        // 获取id
        const id = req.params.id;
        const sql = "update heros set isdel=1 where id=?"
        conn.query(sql, id, (err, result) => {
            if (err) return res.send({
                status: 500,
                msg: err.message
            });
            res.send({
                status: 200,
                data: result
            })
        })
    })
    // 4. 调用 app.listen() 方法启动 express 服务器
app.listen(5001, () => {
    console.log('server running at http://127.0.0.1:5001');
})

jsonp和cros的区别

  1. JSONP的原理:动态创建script标签;
    • JSONP发送的不是Ajax请求
    • 不支持 Post 请求;
  2. CORS中文意思是跨域资源共享 ,需要服务器端进行 CORS 配置;
    • CORS 发送的是真正的Ajax请求
    • CORS 支持Ajax的跨域
    • 如果要启用 CORS 跨域资源共享,关键在于 服务器端,只要 服务器支持CORS跨域资源共享,则 浏览器肯定能够正常访问 这种 CORS 接口;而且,客户端在 发送 Ajax的时候,就像发送普通AJax一样,没有任何代码上的变化;
  3. 对于Node来说,如果想要开启 CORS 跨域通信,只需要安装cors的模块即可;

制作后台英雄管理
其中ajax请求对 /delehero/:id 的请求
url如果是请求的ip建议定义一个变量 ,便于后期修改和维护
ajax中的地址写法为

   // 软删除操作
    $('#edit_del').on('click', '#del', function() {
        let id = $(this).attr("data-id");
        $.ajax({
            type: 'GET',
            // /delehero/:id 和 这个对应
            url: url + "/delehero/" + id,
            data: { id: id },
            dataType: 'json',
            success: function(res) {
                if (res.status === 200) {
                    location.reload();
                }
            },
        })
    });

在使用art -template 时 web就是简易用法 {{}}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

码上登堂

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值