基于typescript下的贪吃蛇练习项目(内含源码)

各位铁汁们,这个是我练习typescript时根据资料做的一个练习项目(用babel配置了浏览器的兼容,以及用postcss兼容各个浏览器的css3(IE))

这个贪吃蛇的规则:最高等级是十级,每吃三个食物升一级,(最高等级和吃几个食物升级可以自己自定义的)贪吃蛇移动速度加快,撞到墙撞到自己身体都会显示游戏GAME OVER! 以及贪吃蛇不可以自己掉头移动等

在这里插入图片描述

首先先将我们的项目进行一些简单配置(这里就不详细描述了),小伙伴们要是不理解可以去之前写过的webpack打包ts的配置可以看看,(依然送上我们的截图和代码)

1.package.json
在这里插入图片描述

{
  "name": "part5",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build": "webpack",
    "start": "webpack serve --open chrome.exe"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "@babel/core": "^7.14.3",
    "@babel/preset-env": "^7.14.2",
    "babel-loader": "^8.2.2",
    "clean-webpack-plugin": "^4.0.0-alpha.0",
    "core-js": "^3.12.1",
    "css-loader": "^5.2.5",
    "html-webpack-plugin": "^5.3.1",
    "less": "^4.1.1",
    "less-loader": "^9.0.0",
    "postcss": "^8.3.0",
    "postcss-loader": "^5.3.0",
    "postcss-preset-env": "^6.7.0",
    "style-loader": "^2.0.0",
    "ts-loader": "^8.3.0",
    "typescript": "^4.2.4",
    "webpack": "^5.37.1",
    "webpack-cli": "^4.7.0",
    "webpack-dev-server": "^3.11.2"
  }
}

2.tsconfig.json
在这里插入图片描述

{
	"compilerOptions":{
		"module": "ES2015",
		"target": "ES2015",
		"strict": false,
		"noEmitOnError": true
	}
}

3.webpack.config.js(直接上代码)
webpack配置css3兼容浏览器的
npm i -D postcss postcss-loader postcss-preset-env

// 引入一个包
const path = require('path')
// 引入html插件
let HtmlWebpackPlugin = require('html-webpack-plugin');
// 引入clean插件
const { CleanWebpackPlugin } = require('clean-webpack-plugin');

// webpack中的所有的配置信息都应该写在module.exports中
module.exports = {
	
	//指定入口文件 通常放在src 源码目录
	entry: "./src/index.ts",
	
	// 指定打包文件所在目录
	output:{
		//指定打包文件的目录
		path:path.resolve(__dirname, 'dist'),
		// 打包后的文件
		filename: "bundle.js",
		
		//告诉webpack不要使用箭头函数以及const 我要兼容IE
		environment:{
			arrowFunction:false,
			const:false

		}
	},
	
	// 指定webpack打包时要使用的模块
	module:{
		//指定要加载的规则
		rules:[
			{
				// test指定的是规则生效的文件//去匹配所有以ts结尾的文件
				test:/\.ts$/,
				// 要使用对应的loader //从后往前执行 先执行ts-loader
				use: [
					//配置babel
					{
						//指定加载器
						loader:"babel-loader",
						//设置babel
						options:{
							// 设置预定义的环境
							presets:[
								[
									//指定环境的插件
									"@babel/preset-env",
									//配置信息
									{
										//要兼容的目标浏览器
										targets:{
											//"chrome":"88" //兼容到chrome88版本最新的  语法都能识别
											"chrome":"58",
											"ie":"11" //要兼容ie 11  不支持const语法的 
										},
										//指定corejs的版本
										"corejs": "3", //比如我们用了promise ie11是没有promise语法的 此时corejs就起作用了
										//使用core js的方式 "usage"表示按需加载
										"useBuiltIns":"usage"
									}
								]
							]
						}
					},
					//'babel-loader' 配置选项少的时候直接用就行了 多的就像上面那样配置
					'ts-loader'
				],
				// 要排除的文件
				exclude:/node_modules/	
			},

			//设置less文件的处理
			{
				test:/\.less$/,
				use:[
					"style-loader",
					"css-loader",
					// 引入postcss
					{
						loader:"postcss-loader",
						options:{
							postcssOptions:{
								plugins:[
									[
										"postcss-preset-env",
										{
											browsers:'last 2 version' //兼容两个最新的浏览器
										}
									]
								]
							}
						}
					},
					"less-loader"
				]
			}
		]
	},
	//配置webpack插件
	plugins:[
		new CleanWebpackPlugin(),
		new HtmlWebpackPlugin({
			//title:"这是一个自定义的title"
			template:"./src/index.html" //引入你自己定义的模板
		}),
	],
	
	// 用来设置引用模块
	resolve:{
		extensions: ['.ts', '.js']
	}
}

4.我们在src目录下创建index.html和index.ts写上我们的基础页面,以及创建css目录下的index.less(这里引入less也已经在webpack配置好了, 以及我们引入postcss来兼容浏览器中的css3,兼容ie等,设置了兼容浏览器最新的两个版本)

index.html

<!DOCTYPE html>
<html land="zh">
<head>
    <meta charset="UTF-8">
    <title>贪吃蛇</title>
</head>
<body>

    <!--创建游戏的主容器-->
    <div id="main">
        <!--设置游戏的舞台-->
        <div id="stage">
            <!--设置蛇-->
            <div id="snake">
                <!--snake内部的div 表示🐍的各个部位-->
                <div>

                </div>
            </div>

            <!--设置食物-->
            <div id="food">
                <div></div>
                <div></div>
                <div></div>
                <div></div>
            </div>
        </div>

        <!--设置游戏的记分牌-->
        <div id="score-panel">
            <div>
                SCORE:<span id="score">0</span>
            </div>
            <div>
                level:<span id="level">1</span>
            </div>

        </div>

    </div>
    
</body>

</html>

index.less

//设置变量
@bg-color:#b7d4a8;

//清除默认样式
*{
    margin: 0;
    padding: 0;
    // 改变盒子模型的计算方式
    box-sizing: border-box;
}

body{
    font:bold 20px "Courier";
}

//设置主窗口
#main{
    width: 360px;
    height: 420px;
    background-color: @bg-color;
    margin:100px auto;
    border:10px solid black;
    border-radius: 40px;

    // 开启弹性盒模型
    display: flex;
    //设置主轴的方向
    flex-flow:column;//纵向
    //设置辅轴的对齐方式
    align-items:center;
    //设置主轴的对齐方式
    justify-content: space-around;

    #stage{
        width: 304px;
        height: 304px;
        border:2px solid black;
        //开启相对定位
        position: relative;

        //设置蛇的样式
        #snake{
            &>div{
                width: 10px;
                height: 10px;
                background-color: #000;
                border:1px solid @bg-color; //间隔缝隙
                //开启绝对定位
                position: absolute;

            }
        }

        //设置食物样式
        &>#food{
            width: 10px;
            height: 10px;
            position: absolute;
            left: 40px;
            top:100px;
            
            display: flex;
            flex-flow: row wrap;
            justify-content: space-between;
            align-content: space-between;
            &>div{
                width: 4px;
                height: 4px;
                background-color: red;
                transform: rotate(45deg);
            }
        }

    }

    #score-panel{
        width: 300px;
        display: flex;
        justify-content: space-between;
    }
}

现在基本的页面样式都搭好了。
重点编写我们的ts内容,我们要将各个功能分成一个个类实现

主要的index.ts

//引入样式
import './css/index.less'

//引入各个类
import Food from './moduls/Food';
import ScorePanel from './moduls/ScorePanel';


const food = new Food()
food.change()
console.log(food.X, food.Y);

const scorePanel = new ScorePanel();
for(let i=0;i<210 ;i++){
    scorePanel.addScore()
}

定义食物类Food.ts

//定义食物类Food

class Food{
    // 定义一个属性表示食物所对应的元素
    element:HTMLElement;

    constructor(){
        // 获取页面中的food元素并将其复制给element
        this.element = document.getElementById('food')!;//!表示这东西不会为空
    }

    //定义一个获取食物X轴坐标的方法
    get X(){
        return this.element.offsetLeft;
    }

    //定义一个获取食物Y轴坐标的方法
    get Y(){
        return this.element.offsetTop;
    }

    //修改食物的位置
    change(){
        //生成一个随机的位置
        //食物的位置 最小是0 最大是290(300-10 食物本身是10)
        // 蛇移动一次就说一格,一格的大小就是10 所以就要求食物的坐标必须是整10

        let top = Math.round(Math.random() * 29) * 10; //里面那个是取0-29的随机数 后面×10 
        let left = Math.round(Math.random() * 29) * 10;

        this.element.style.left = left + 'px';
        this.element.style.top = top + 'px';
    }


}


//测试代码
// const food = new Food()
// food.change()
// console.log(food.X, food.Y);

export default Food;

效果如下图所示
在这里插入图片描述

也能获取到食物的位置
在这里插入图片描述

定义表示记分牌的类ScorePanel.ts(默认最高等级是10级,每一级升级分数需要10分(可以根据自己的传入的值而改变))

//定义表示记分牌的类
class ScorePanel{
    //score 和level 用来记录分数和等级
    score = 0;
    level = 1;

    // 分数和等级所在的元素,在构造函数中进行初始化
    scoreEle:HTMLElement;
    levelEle:HTMLElement;

    //设置一个变量限制等级
    maxLevel:number;
    //设置一个变量表示升级所需要的分数
    upScore:number

    constructor(maxLevel:number = 10, upScore:number = 10){
        this.scoreEle = document.getElementById('score')!;
        this.levelEle = document.getElementById('level')!;
        this.maxLevel = maxLevel
        this.upScore = upScore
    }

    //设置一个加分的方法
    addScore(){
        this.score++;
        this.scoreEle.innerHTML = this.score + ''; //把分数赋给页面
        //判断分数是多少 能够整除10的时候就升一级
        if(this.score % this.upScore === 0){
            this.levelUp();
        }
    }
    //设置一个升级的方法
    levelUp(){
        //小于我的最大等级在给你升级
        if(this.level < this.maxLevel){
            this.levelEle.innerHTML = ++this.level + '';
        }
        
    }

}

// //测试代码
// const scorePanel = new ScorePanel();
// for(let i=0;i<210 ;i++){
//     scorePanel.addScore()
// }

export default ScorePanel

在这里插入图片描述

初步定义一个蛇的类Snake.ts

class Snake{
    // 表示蛇头的元素
    head:HTMLElement;
    // 表示蛇的身体的元素(包括蛇头)
    bodies:HTMLCollection; //会实时更新的
    // 获取蛇的容器
    element:HTMLElement;


    constructor(){
        this.element = document.getElementById('snake')!
        this.head = document.querySelector('#snake > div')! as HTMLElement;//只取一个 所以表示蛇头
        this.bodies =  this.element.getElementsByTagName('div'); //获取snake里面所有的div
    }

    //获取蛇X轴的坐标(蛇头坐标)
    get X(){
        return this.head.offsetLeft;
    }
    // 获取蛇的Y轴坐标(蛇头坐标)
    get Y(){
        return this.head.offsetTop;
    }

    //设置蛇头的坐标
    set X(value:number){
        this.head.style.left = value + 'px';
    }

    set Y(value:number){
        this.head.style.top = value + 'px';
    }


    //设置蛇增加身体的方法
    addBody(){
        //向element添加身体(添加div)
        this.element.insertAdjacentHTML("beforeend","<div></div>")
    }


}

接下来就是定义我们最重要的一个类GameControl.ts

1.首先是要定义键盘事件

注意: keydownHandler函数里面的this指向问题,下面提供了两种方法 ,一是使用bind(this)
二是使用了箭头函数,大家可以了解一下

//引入其他的类
import Snake from './Snake'
import Food from './Food'
import ScorePanel from './ScorePanel'


// 游戏控制器, 控制其他的所有类
class GameControl{
    //定义三个属性
    //蛇
    snake:Snake;
    //食物
    food:Food;
    //记分牌
    scorePanel:ScorePanel;

    //创建属性来存储蛇的移动方向(也就是我们按键的方向)
    direction:string = '';


    constructor() {
        this.snake = new Snake();
        this.food = new Food();
        this.scorePanel = new ScorePanel();   

        this.init()
    }

    //游戏的初始化方法,调用后游戏即开始
    init(){
        // 绑定键盘按键按下的事件
        //document.addEventListener('keydown', this.keydownHandler.bind(this)) //第一种解决方案-----调用bind(this) 创建一个新函数然后把(this)绑定成this.keydownHandler.bind(this)这个函数的this,所以(this)表示当前的对象 就不会有问题了
        document.addEventListener('keydown', this.keydownHandler) 

    }
    /**
     * 
     * ArrowUp  上   Up(IE)
     * ArrowDown 下  Down(IE)
     * ArrowLeft 左  Left(IE)
     * ArrowRight 右 Right(IE)
     */

    //创建一个键盘按下的响应函数
    // keydownHandler(event:KeyboardEvent){
       
    //     // 修改direction属性
    //     this.direction = event.key;

    // }

    //创建一个键盘按下的响应函数 //第二种解决方法改为箭头函数 解决this指向问题
    keydownHandler = (event:KeyboardEvent) =>{
        // 需要检查event.key的值是否合法(用户是否按了正确的按键)
        
        // 修改direction属性
        this.direction = event.key;
    }


}

export default GameControl

我们在index.ts进行测试

//引入样式
import './css/index.less'

//引入各个类
import GameControl from './moduls/GameControl'
const gameControl = new GameControl()

setInterval(()=>{
    console.log(gameControl.direction);
},1000)

结果如图所示

在这里插入图片描述

2.让蛇移动起来重点看run的方法

//引入其他的类
import Snake from './Snake'
import Food from './Food'
import ScorePanel from './ScorePanel'


// 游戏控制器, 控制其他的所有类
class GameControl{
    //定义三个属性
    //蛇
    snake:Snake;
    //食物
    food:Food;
    //记分牌
    scorePanel:ScorePanel;

    //创建属性来存储蛇的移动方向(也就是我们按键的方向)
    direction:string = '';

    //创建一个属性用来记录游戏是否结束
    islive = true;


    constructor() {
        this.snake = new Snake();
        this.food = new Food();
        this.scorePanel = new ScorePanel();   

        this.init()
    }

    //游戏的初始化方法,调用后游戏即开始
    init(){
        // 绑定键盘按键按下的事件
        //document.addEventListener('keydown', this.keydownHandler.bind(this)) //第一种解决方案-----调用bind(this) 创建一个新函数然后把(this)绑定成this.keydownHandler.bind(this)这个函数的this,所以(this)表示当前的对象 就不会有问题了
        document.addEventListener('keydown', this.keydownHandler) 
        //调用run方法 蛇移动
        this.run()

    }
    /**
     * 
     * ArrowUp  上   Up(IE)
     * ArrowDown 下  Down(IE)
     * ArrowLeft 左  Left(IE)
     * ArrowRight 右 Right(IE)
     */

    //创建一个键盘按下的响应函数
    // keydownHandler(event:KeyboardEvent){
       
    //     // 修改direction属性
    //     this.direction = event.key;

    // }

    //创建一个键盘按下的响应函数 //第二种解决方法改为箭头函数 解决this指向问题
    keydownHandler = (event:KeyboardEvent) =>{
        // 需要检查event.key的值是否合法(用户是否按了正确的按键)

        // 修改direction属性
        this.direction = event.key;
    }

    //创建一个控制蛇移动的方法
    run(){
        /**
         *  根据方向(this.direction) 来使蛇的位置改变
         *  向上    top值减小
         *  向下    top值增加    
         *  向左    left减小
         *  向右    left增加
         */
        // 获取蛇现在的坐标
        let X = this.snake.X;
        let Y = this.snake.Y;

        // 根据按键方向来修改X值和Y值
        switch(this.direction){
            //向上移动top减少
            case "ArrowUp":
            case "Up":
                Y -= 10;
                break;
            //向下移动top增加
            case "ArrowDown":
            case "Down":
                Y += 10;
                break;
            //向左移动left减少
            case "ArrowLeft":
            case "Left":
                X -= 10;
                break;
            //向右移动left增加
            case "ArrowRight":
            case "Right":
                X += 10;
                break;
        }

        //修改蛇的X和Y值
        this.snake.X = X;
        this.snake.Y = Y;

        //开启一个定时调用
        //第一种方法:setTimeout(this.run.bind(this),300)
        this.islive && setTimeout(()=>{  //加上this.islive &&判断 蛇还活着的时候才会继续动(this.islive==true)  蛇死了就不会动了(this.islive==false) 
            this.run()
        },300 - (this.scorePanel.level-1)*30) //根据等级来决定贪吃蛇的速度!! 比如2级270 3级240 4级210
    }



}

export default GameControl

注意:有小伙伴问是否可以把this.run方法放在keydownHandler方法下面,
按键按钮的时候就动起来,这个当然是可以的。但是不满足我们的需求,所以我们应该使用定时器来解决这个问题。以及在这个定时器加上this.islive这个判断
如果它为true的时候开启定时器,如果为false的时候不开启定时器,这就可以当作蛇是否活着还是死掉的判断

效果如图所示
在这里插入图片描述

3.判断蛇是否撞墙了(因为蛇撞墙,所以我们在snake类处理比较好点)

  1. 首先我们知道X轴的范围是0-290,Y轴的范围也是0-290,如图

    在这里插入图片描述

  2. 我们在snake类设置蛇头的坐标处理,判断X轴和Y轴的值是否小于0或者大于0,若是则抛出异常信息

    //设置蛇头的坐标
    set X(value:number){

        // 如果新值和旧值相同 则直接返回不再修改
        if(this.X === value){
            return;
        }

        // X的值合法范围是0-290之间 蛇撞墙了
        if(value < 0 || value > 290){
            //进入判断说明蛇撞墙了
            throw new Error('蛇撞墙了') //丢出异常
        }

        this.head.style.left = value + 'px';
    }

    set Y(value:number){

         // 如果新值和旧值相同 则直接返回不再修改
        if(this.Y === value){
            return;
        }

        // Y的值合法范围是0-290之间 蛇撞墙了
        if(value < 0 || value > 290){
            //进入判断说明蛇撞墙了
            throw new Error('蛇撞墙了')
        }

        this.head.style.top = value + 'px';
    }
  1. 然后在GameControl.ts类对异常进行处理(try catch)
    注意:要把蛇的状态改为死掉状态 否则它还会一直运动,则把this.islive改为false
 //修改蛇的X和Y值
        try{
            this.snake.X = X; //调用snake的X和Y方法
            this.snake.Y = Y;
        }catch(e){
            // 进入到catch,说明出现了异常 游戏结束,弹出了一个弹窗提示消息
            alert(e.message + 'GAME OVER!');
            //将islive改为false
            this.islive = false //若是没有改为false会一直弹窗
        }
4.效果图如下图

在这里插入图片描述

4.监测蛇吃到食物了

当蛇的新坐标与食物坐标相等时,就是吃到了食物,吃到了食物之后,分数要增加,食物的位置要重置,蛇要增加一节。 此时TS的面向对象好处就出来了,要用谁就调用谁如下所示,判断蛇吃到食物后进行的操作方法

//定义一个方法,用来检查蛇是否吃到食物
    checkEat(X:number,Y:number){ //这个是蛇的新坐标
       if(X === this.food.X && Y === this.food.Y){ //这个是蛇的新坐标与食物坐标相等时,就是吃到了食物
            console.log('吃到食物了')
            // 吃到食物了 食物的位置要进行重置
            this.food.change();
            // 分数要增加
            this.scorePanel.addScore();
            // 蛇要增加一节
            this.snake.addBody();
       }  
    }

在run方法中进行调用此方法

this.checkEat(X,Y) //运行蛇是否吃到食物

效果图如下:
在这里插入图片描述

如图所示 食物的刷新、分数的增加、蛇的身体都出现了。效果是可以了。但是蛇的身体没有跟着一起移动、以及蛇是否可以反方向走的问题。我们都需要继续处理

首先解决蛇的身体增加跟着移动,在snake.ts类增加蛇身体移动的方法

//头移动的时候 身体就要跟着移动了 所以要在头那里调用
    //添加一个蛇身体移动的方法
    moveBody(){
        /**
         *  将后边的身体设置为前边身体的位置
         *  举例子
         *      第四节 = 第三节的位置
         *      第三节 = 第二节的位置
         *      第二节 = 蛇头的位置
         * 
         */
        //遍历获取所有的身体 从后往前取
        for(let i=this.bodies.length-1; i>0; i--){
            //获取前边身体的位置
            let X = (this.bodies[i-1] as HTMLElement).offsetLeft; //这里需要对this.bodies[i-1]进行断言
            let Y = (this.bodies[i-1] as HTMLElement).offsetTop;

            // 将这个值设置到当前身体上
            (this.bodies[i] as HTMLElement).style.left = X + 'px';
            (this.bodies[i] as HTMLElement).style.top = Y + 'px';
        }

    }

因为头移动的时候 身体就要跟着移动了 所以要在头那里的方法调用此方法

set X(value:number){
	//移动身体
	this.moveBody()
}

set Y(value:number){
	//移动身体
	this.moveBody()
}

效果图如下
在这里插入图片描述

2.解决蛇可以掉头的bug

解决蛇水平可以掉头的bug应该在set X方法解决(要注意的我注释哦,这里逻辑可能需要你自己捋一捋)

set X(value){
	 //修改X时,是在修改水平坐标,蛇在左右移动,蛇向左移动时, 不能向右掉头,反之亦然
        
        //首先先判断是否有第二节身体与第二节身体的水平坐标等于value坐标时 则发生了掉头
        if(this.bodies[1] && (this.bodies[1] as HTMLElement).offsetLeft === value){ 
            
            console.log('水平方向发生了掉头')
            //如果发生了掉头,让蛇向反方向继续移动
            console.log(value)
            console.log('----------')
            console.log(this.X)
            if(value > this.X){
                
                //如果新值value大于旧值X,则说明蛇想要往向右走,此时发生掉头,不允许发生掉头应该使蛇继续向左走
                value = this.X -10;
            }else{
                //如果新值value小于旧值X,则说明蛇想要往向左走,此时发生掉头,不允许发生掉头应该使蛇继续向右走
                value = this.X +10;
            }

        }
}

解决蛇垂直可以掉头的bug应该在set Y方法解决(要注意的我注释哦,这里逻辑可能需要你自己捋一捋)
比如:如果新值value大于旧值Y,则说明蛇想要往向下走,此时发生掉头,不允许发生掉头应该使蛇继续向上走

重点:就是它想发生掉头的时候才会触发这个事件,比如此时向下移动 它的Y轴坐标是在一直增加的,但是因为掉头进入方法打印时它的Y轴坐标是在降低的,所以根据这个判断在向上走时,要继续给它的Y轴加10 则会继续往下走

//修改Y时,是在修改垂直坐标,蛇在上下移动,蛇向上移动时, 不能向下掉头,反之亦然
         //首先先判断是否有第二节身体与第二节身体的垂直坐标等于value坐标时 则发生了掉头
         if(this.bodies[1] && (this.bodies[1] as HTMLElement).offsetTop === value){ 
            console.log(value)
            console.log(this.Y)

            if(value > this.Y){
                
                //如果新值value大于旧值Y,则说明蛇想要往向下走,此时发生掉头,不允许发生掉头应该使蛇继续向上走
                value = this.Y -10;
            }else{
                //如果新值value小于旧值Y,则说明蛇想要往向上走,此时发生掉头,不允许发生掉头应该使蛇继续向下走
                value = this.Y +10;
            }

        }

在这里插入图片描述

3.解决蛇可以撞自己身体的bug(判断蛇头的坐标是否和身体的坐标相等)

 // 检查蛇头是否撞到身体的方法
	  //判断头和身体有没有相撞
	  checkHeadBody(){
	      //获取所有的身体,检查其是否和蛇头的坐标重叠
	      for(let i=1; i<this.bodies.length; i++){
	          let bd = this.bodies[i] as HTMLElement
	          if(this.X === bd.offsetLeft && this.Y === bd.offsetTop){
	              //进入判断说明蛇头撞到了身体, 游戏结束
	              throw new Error('撞到了自己!')
	          }
	      }
	  }

在set X 和set Y调用

this.head.style.top = value + 'px';
 // 检查有没有撞到自己(注意顺序调用问题) 放在头部下面 否则吃到食物就会相当于撞到了自己
 this.checkHeadBody();

结果如动图:
在这里插入图片描述

此时这个简单项目就完成了,有需要的朋友可以自行在gitee下载源码去学习哟,学习的过程中有问题欢迎随时交流鸭
源码地址:项目源码

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值