触发图片四种状态的自定义事件打印顺序跟老师的不同

触发图片四种状态的自定义事件打印顺序跟老师的不同

问题描述:

老师你好,触发四种状态的自定义事件,我打印的跟视频中的老师打印不一样,不知道哪里出了问题,请老师看下


相关截图:

http://img1.sycdn.imooc.com//climg/600945d609737a7c21601440.jpg


相关代码:

这是html结构
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>幻灯片淡入淡出切换效果练习</title>

<link rel="stylesheet" href="base.css">

<link rel="stylesheet" href="common.css">

<style>
.slider {margin-left: 300px;
width: 728px;
height: 504px;
overflow: hidden;
position: relative;
}

.slider-indicator-wrap {
position: absolute;
left: 50%;
bottom: 24px;
margin-left: -28px;
}

.slider-indicator {
width: 8px;
height: 8px;
border-radius: 50%;
background: #313a43;
margin-right: 12px;
cursor: pointer;
}

.slider-indicator-active {
background: #f7f8f9;
border: 2px solid #858b92;
position: relative;
top: -2px;
}

.slider-control {
display: none;
width: 28px;
height: 62px;
line-height: 62px;
background: #000;
opacity: 0.7;
color: #fff;
font-size: 22px;
font-family: simsun;
text-align: center;
position: absolute;
top: 50%;
transform: translate(0, -50%);
}

.slider-control-left {
left: 0;
}

.slider-control-right {
right: 0;
}


.slider-fade .slider-item {
display: none;
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}


.slider-slide .slider-item {
position: absolute;
left: 100%;
top: 0;
width: 100%;
height: 100%;
}

</style>


</head>
<body>

<div id="focus-slider" class="slider">

<!-- 存放幻灯片图片的容器 -->
<div class="slider-container">

<!-- 这里使用div标签包裹,因为如果直接使用img标签就只能放图片了,扩展性不好 -->
<div class="slider-item">

<a href="##" target="_blank">
<img src="img/focus-slider/1.png">
</a>

</div>

<div class="slider-item">

<a href="##" target="_blank">
<img src="img/focus-slider/2.png">
</a>

</div>

<div class="slider-item">

<a href="##" target="_blank">
<img src="img/focus-slider/3.png">
</a>

</div>

<div class="slider-item">

<a href="##" target="_blank">
<img src="img/focus-slider/4.png">
</a>

</div>

</div>


<!-- 存放下方小圆点的父容器 -->
<ol class="slider-indicator-wrap">

<!-- 下方的四个小圆点 -->
<li class="slider-indicator fl"></li>
<li class="slider-indicator fl"></li>
<li class="slider-indicator fl"></li>
<li class="slider-indicator fl"></li>

</ol>


<!-- 左侧切换按钮 -->
<a href="javascript:;" class="slider-control slider-control-left">&lt;</a>


<!-- 右侧切换按钮 -->
<a href="javascript:;" class="slider-control slider-control-right">&gt;</a>
</div>














<!-- 引入jQuery文件 -->
<script src="../jquery.js"></script>


<!-- 引入浏览器是否兼容transition属性的js代码 -->
<script src="js/transition.js"></script>


<!-- 引入显示或隐藏的js代码 -->
<script src="js/showHide.js"></script>


<!-- 引入滑动切换效果的js代码 -->
<script src="滑动切换效果.js"></script>


<!-- 引入淡入淡出切换效果的js代码 -->
<script src="淡入淡出切换效果.js"></script>


<script>
var $focusSlider = $('#focus-slider');


// 定义自定义事件slider-show,当图片准备显示之前会调用该函数,形参index是准备显示图片的索引值,ele是准备显示图片的DOM元素。为后续按需加载图片做准备
$focusSlider.on('slider-show slider-shown slider-hide slider-hidden', function(event, index, ele) {
console.log(index + ':' + event.type);
})



// $focusSlider.slider({
// css3: true,
// js: false,
// animation: 'fade', // 滑动效果是slide,淡入淡出是fade
// activeIndex: 0, // 初始化时哪张图片在最上面,从0开始,2就是第三张
// interval: 0 // 自动切换的时间间隔。如果为0表示不开启自动切换,有值就代表自动切换的间隔。毫秒为单位,500毫秒就是0.5秒
// })




$focusSlider.slider({
css3: true,
js: false,
animation: 'slide', // 滑动效果是slide,淡入淡出是fade
activeIndex: 0, // 初始化时哪张图片在最上面,从0开始,2就是第三张
interval: 0 // 自动切换的时间间隔。如果为0表示不开启自动切换,有值就代表自动切换的间隔。毫秒为单位,500毫秒就是0.5秒
})



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


相关代码:

这是fade和slide两种切换效果的js代码
(function ($) {

'use strict';



function Slider($ele, options) {
this.$ele = $ele;

this.$options = options;

// 获取每一张图片
this.$items = this.$ele.find('.slider-item');

// 获取所有图片的总数量
this.$itemsLen = this.$items.length;

// 获取底部的每一个小圆点
this.$indicator = this.$ele.find('.slider-indicator');

// 将当前显示图片的索引值保存在$currentIndex这个属性上,保存之前先使用_getCorrectIndex函数判断一下边界值,以免传入错误的图片索引值
this.$currentIndex = this._getCorrectIndex(this.$options.activeIndex);

// 获取左右两侧的按钮
this.$controls = this.$ele.find('.slider-control');



// 调用初始化方法
this._init();

}


Slider.DEFAULTS = {
css3: false,
js: false,
animation: 'fade',
activeIndex: 0,
interval: 0,
};




// 初始化的函数,只供内部使用
Slider.prototype._init = function () {

var self = this;



// 及相应图片的底部小圆点的active激活样式。先将所有小圆点的active激活样式移除,再将应该显示active激活样式的小圆点添加上类名
this.$indicator.removeClass('slider-indicator-active').eq(this.$currentIndex).addClass('slider-indicator-active');


// 如果调用时使用的是slide滑入滑出的切换效果,就添加一个to属性,因此to属性就是_slide函数
if (this.$options.animation === 'slide') {

// 给#focus-slider这个父容器添加上类名slider-slide
this.$ele.addClass('slider-slide');


// 调用滑动切换效果的函数,传入调用时的参数,进行初始化
this.$items.move(this.$options);

// 获取图片的宽度
this.itemsWidth = this.$items.eq(0).width();


// 让默认显示图片的left值设为0,让其显示出来。其它所有的图片都使用了absolute绝对定位,都是重叠在一起的,并且是贴着父容器的右侧,在父容器之外等待
this.$items.eq(this.$currentIndex).css('left', 0);


// 如果调用时使用的是slide滑动切换效果,就添加一个to属性,因此to属性就是_slide函数
this.to = this._slide;


// 如果使用的css3运动方式需要添加transition类名,但是js运动方式不需要。如果是js运动方式添加了transition会出错的,所以判断一下显示的图片是否有transition这个类名,如果有的话this.isTransition就是transition,如果没有就为空。
this.isTransition = this.$items.eq(0).hasClass('transition') ? 'transition' : '';



// 这是slide划动切换效果时往外发送消息
this.$items.on('move moved', function (event) {
var index = self.$items.index(this);

// 如果触发的事件是event.move,说明使用的是slide划动切换的效果
if (event.type === 'move') {

// 如果当前显示的图片触发move这个自定义事件,说明当前显示的图片准备隐藏了,因此触发slider-hide这个自定义事件
if (index === self.$currentIndex) {
self.$ele.triggerHandler('slider-hide', index, this);


// 如果是准备显示的图片触发了move自定义事件,表示准备显示的图片准备显示出来了,因此触发自定义事件slider-show
} else {
self.$ele.triggerHandler('slider-show', index, this);

}

// 这里表示触发的自定义事件moved,表示当前显示的图片已经隐藏完了,准备显示的图片已经完全显示出来了
} else {

// 如果是当前显示的图片触发了自定义事件moved,表示已经完全隐藏了,因此触发自定义事件slider-hidden
if (index === self.$currentIndex) { // 这是准备完全显示的图片

self.$ele.triggerHandler('slider-shown', index, this);

// 如果是准备显示的图片触发了自定义事件moved,表示已经完全显示,因此触发自定义事件slider-shown

} else { // 这才是准备完全隐藏的图片

self.$ele.triggerHandler('slider-hidden', index, this);

}

}
})



// 除了slide滑入滑出的效果以外的其它任何效果都用fade淡入淡出的切换效果
} else { // fade,淡入淡出的切换效果


// 给#focus-slider这个父容器添加上类名slider-fade,让其下所有图片都隐藏。
this.$ele.addClass('slider-fade');


// 调用showHide方法,让所有图片都初始化,因为点击显示或隐藏时需要用到showHide函数
this.$items.showHide(this.$options);


// 这是fade切换效果时往外发送的消息。发送准备显示、显示完成、准备隐藏、隐藏完成的消息,以便使用按需加载。当图片每次经过了这四个阶段时,都会调用自定义事件slider- 加上事件状态(show shown hide hidden),self.$items.index(this)是当前显示图片的索引值,this对应的DOM元素,将这两个当作参数传入该自定义事件
this.$items.on('show shown hide hidden', function (event) {
self.$ele.triggerHandler('slider-' + event.type, self.$items.index(this), this);
})


// 在切换效果为fade时,初始化默认显示的图片
this.$items.eq(this.$currentIndex).show();


// 如果调用时使用的fade淡入淡出的切换效果,就添加一个to属性,因此to属性就是_fade函数
this.to = this._fade;


}



// 给大容器绑定hover事件,鼠标移入,左右两侧按钮显示,移出时隐藏。
this.$ele.hover(function () {
self.$controls.show();

}, function () {
self.$controls.hide();


// 再使用事件代理的方式给左右两侧按钮分别绑定点击事件,被点击时切换图片
}).on('click', '.slider-control-left', function () {

// 调用to属性,该属性是切换图片效果的方法:_fade或者_slide函数,将当前显示图片的索引值相减 1,当作参数传入。传入之前先使用 _getCorrectIndex函数判断索引值是否合法
self.to(self._getCorrectIndex(self.$currentIndex - 1), 1);


}).on('click', '.slider-control-right', function () {

// 调用to属性,该属性是切换图片效果的方法:_fade或者_slide函数,将当前显示图片的索引值相加 1,当作参数传入。传入之前先使用 _getCorrectIndex函数判断索引值是否合法
self.to(self._getCorrectIndex(self.$currentIndex + 1), -1);


// 使用事件代理给底部小圆点绑定点击事件,被点击时,相对应的图片显示出来
}).on('click', '.slider-indicator', function () {

// 被点击时调用to函数,将当前被点击小圆点的索引值经过函数_getCorrectIndex的合法判断后当作参数传入。
self.to(self._getCorrectIndex(self.$indicator.index(this)));
});



// 是否启动自动切换图片。如果调用时传入的对象中interval属性不等于0且是有该属性的,并且属性值经过有效性判断后是一个数字,那便启用自动切换图片功能
if (this.$options.interval && !isNaN(Number(this.$options.interval))) {

// 当interval属性是有效的时候,给外面的大容器绑定hover事件,鼠标移入时调用this.pause函数,使用$.proxy()方法改变this指向,清除定时器。 鼠标移出时调用this.auto函数,同样使用$.proxy()方法改变this指向,开启定时器
this.$ele.hover($.proxy(this.pause, this), $.proxy(this.auto, this))

// 调用自动切换图片的函数
this.auto();
}


};



// 该函数用于对图片索引的临界值做判断
Slider.prototype._getCorrectIndex = function (index) {

// 如果使用Number转换,再使用isNaN转换过后仍是true,就表明不是一个数字,直接返回索引值0。
if (isNaN(Number(index))) return 0;

// 如果索引值加1后大于等于图片总数量就说明已经到底了,返回第一张图片的索引值0
if (index >= this.$itemsLen) return index = 0;

// 如果索引值减1后小于0,说明已经到头了,返回最后一张图片的索引值
if (index < 0) return this.$itemsLen - 1;

// 如果前面三个都没有判断到,说明索引值正常,直接返回索引值
return index;

};



// 根据不同的切换效果调用相对应的函数,_fade函数由to属性调用.
Slider.prototype._fade = function (index) {

// 如果重复点击相同的小圆点,还是同一张图片显示和隐藏,后面的代码还是会被执行,这样是浪费性能的。因此如果当前显示的图片和被点击的小圆点是同一个,直接return 退出函数,不执行下面的代码。
if (this.$currentIndex === index) return;


// 当点击切换按钮时让当前显示的图片隐藏,
this.$items.eq(this.$currentIndex).showHide('hide');

// 点击的图片显示出来
this.$items.eq(index).showHide('show');


// 以及当前的小圆点active样式去除
this.$indicator.eq(this.$currentIndex).removeClass('slider-indicator-active');


// 让相对应图片的小圆点添加上active样式
this.$indicator.eq(index).addClass('slider-indicator-active');


// 再让当前被点击的索引值赋值给$currentIndex属性
this.$currentIndex = index;
};




// 根据不同的切换效果调用相对应的函数,_slide由to属性调用.
Slider.prototype._slide = function (index, direction) {

// 如果点击小圆点时,就是被点击的小圆点所对应显示的图片,那不执行下面的代码,直接return 退出函数
if (this.$currentIndex === index) return;


// 确定划入划出的方向。如果第二个参数没有,说明是点击底部小圆点来切换图片的。因为点击小圆点图片运动方向无法确定,不像左右按钮,点击右侧按钮图片一定是从右往左运动,点击左侧按钮正好相反,是从左往右运动
if (!direction) {

// 如果当前显示图片的索引小于要进入的图片的索引值,说明是要显示当前显示图片后面的图片,那就应该整体往左划动。
if (this.$currentIndex < index) {
direction = -1;

// 相反,如果当前显示图片的索引值大于要进入的图片的索引值,说明是要显示当前显示图片前面的图片,那就整体往右滑动。
} else if (this.$currentIndex > index) {
direction = 1;
}
}


var self = this;


// 设置准备进入的图片在父容器的左右侧等待,具体在哪一侧等待取决于运动方向,也就是点击的是左侧按钮还是右侧按钮还是底部的小圆点。也就是在父容器之外的左侧还是右侧等待,所以left的距离是整个父容器的宽度,也就是要么是748px,要么是-748px。 初始时,让第一张幻灯片的left值为0,进入视野区,即初始化时除了要显示的图片,其余所有图片都是在父容器的右侧等待,只有实际点击按钮后才会区分到底是在父容器的左侧还是右侧等待。
this.$items.eq(index).removeClass(this.isTransition).css('left', -1 * direction * this.itemsWidth);



setTimeout(function () {

// 开一个定时器,当准备进入的图片已经根据的运动方向确定好已经在等待的位置之后,才开始切换图片。当前幻灯片离开可视区域,根据运动方向决定准备进入的图片是在父容器的左侧还是右侧,也就是direction的值根据点击的是左侧按钮还是右侧按钮决定。如果是左侧按钮就为1,如果是右侧按钮则正好相反,为-1。因为点击右侧按钮时,图片应该是朝左边也就是left值为负值运动。点击左侧按钮时应该是left值朝右边运动,也就是正数值。
self.$items.eq(self.$currentIndex).move('x', direction * self.itemsWidth);


// 准备显示的幻灯片进入可视区域。因为所有图片全部是贴着父容器的右侧,所有要进入时,让left的值为0便可显示在容器中,使其展示出来。
self.$items.eq(index).addClass(self.isTransition).move('x', 0);


// 激活小圆点的active样式
self.$indicator.eq(self.$currentIndex).removeClass('slider-indicator-active');

self.$indicator.eq(index).addClass('slider-indicator-active');


// 切换完成后的图片索引赋值给当前显示图片的索引,同步一样
self.$currentIndex = index;

}, 20)


};



// 自动切换图片的函数
Slider.prototype.auto = function () {
var self = this;

// 设置间歇调用定时器,调用to方法,将当前显示的图片索引值+1,并且经过有效性验证,并当作参数传入。时间间隔是调用时传入的对象中interval属性的值
this.setAutoComplete = setInterval(function () {
self.to(self._getCorrectIndex(self.$currentIndex + 1), -1);
}, this.$options.interval);
};



// 取消自动切换图片的函数
Slider.prototype.pause = function () {

// 清除自动切换图片的定时器。通过构造函数Slider实例化出来的对象上的setAutoComplete属性就是自动切换图片的定时器
clearInterval(this.setAutoComplete);
};




$.fn.extend({
slider: function (obj) {
return this.each(function () {
var $this = $(this),

mode = $this.data('slider'),

newObj = $.extend({}, Slider.DEFAULTS, $this.data(), typeof obj === 'object' && obj)


if (!mode) {
$this.data('slider', mode = new Slider($this, newObj))
}

if (typeof mode[obj] === 'function') {
mode[obj]();
}

})
}
})

})(jQuery)



相关代码:

这是slide切换效果的js代码
​(function ($) {
'use strict';

// 这里是验证使用的浏览器到底使用哪种transition属性,因为每个浏览器transition属性的前缀都不同,将其保存在变量trans里
var trans = window.mt.trans;



// 初始化时共同调用的函数
function init($ele) {

// 将通过构造函数Silent实例化出来的对象的DOM元素当作该对象的一个属性
this.$ele = $ele;

// 保存DOM元素当前的X轴方向的位置,使用parseInt方法去除字符串“px”,只保留数字
this.currentX = parseInt(this.$ele.css('left'));

// 保存DOM元素当前的Y轴方向的位置,使用parseInt方法去除字符串“px”,只保留数字
this.currentY = parseInt(this.$ele.css('top'));
}



// 具体移动时,共同调用的函数
function to(x, y, callback) {

// 在参数使用之前先判断,如果是数字就使用该数字,如果是除了数字之外的其它任意值,就让x或y等于DOM元素当前所在位置的X和Y轴的位置数值
x = (typeof x === 'number') ? x : this.currentX;
y = (typeof y === 'number') ? y : this.currentY;


// 接着判断如果当前所在位置和要移动到的位置相等,直接return退出函数,不执行下面的代码。因为会浪费性能
if (this.currentX === x && this.currentY === y) return;

// 发送消息。在要移动之前触发自定义事件move,并将要移动的DOM元素当作参数传入
this.$ele.triggerHandler('move', this.$ele);


// 如果传入的第三个参数是一个函数,那便执行这个函数
if (typeof callback === 'function') {
callback();
}


// 最后将当前所在位置的数值更新为移动后新位置的数值
this.currentX = x;
this.currentY = y;
}



// 有三种滑动的方式,Silent没有任何效果、Css3是使用css实现动画效果、Js是使用js实现动画效果。这三种方式都有三个通过原型添加上的函数:to函数是X轴和Y轴的同时移动、x函数是只移动X轴、y函数是只移动Y轴。

function Silent($ele) {

// 调用init初始化函数,并且使用call方法改变this的指向,最后传入DOM元素
init.call(this, $ele);

// 将该对象上的transition属性移除
this.$ele.removeClass('transition');

}


Silent.prototype.to = function (x, y) {

var self = this;

// 调用to具体移动的函数,并且使用call方法改变this的指向,传入X轴和Y轴的位置数值,并传入一个回调函数
to.call(this, x, y, function () {

// 将DOM元素移动到目的地位置上去
self.$ele.css({
left: x,
top: y,
})

// 发送消息。在已经移动完成之后触发自定义事件moved,并将要移动的DOM元素当作参数传入
self.$ele.triggerHandler('moved', self.$ele);

})

};


Silent.prototype.x = function (x) {

// 当只移动X轴方向时,调用to方法,将x轴位置当作参数传入
this.to(x);

};


Silent.prototype.y = function (y) {

// 当只移动Y轴方向时,调用to方法,将x轴位置传入undefined,y轴位置当作参数传入
this.to(undefined, y);

};




function Css3($ele) {

// 调用init初始化函数,并且使用call方法改变this的指向,最后传入DOM元素
init.call(this, $ele);

this.$ele.addClass('transition');

// 将DOM元素的位置属性设置为当前所在位置的属性值。因为如果一开始就没写位置属性的话,会有问题。第一次执行滑动是没有滑动的效果的
this.$ele.css({
left: this.currentX,
top: this.currentY,
});

}


Css3.prototype.to = function (x, y) {

var self = this;

// 调用to具体移动的函数,并且使用call方法改变this的指向,传入X轴和Y轴的位置数值,并传入一个回调函数
to.call(this, x, y, function () {

// trans.end其实是transitionend这个事件,它是js提供的事件。元素有transition过渡时,如果过渡结束,就会触发自定义事件moved,也将元素当作参数传入。但是在触发之前先将元素滑动完成之后的触发事件解除掉。因为如果在移动的过程中又执行了相反的移动,自定义事件moved会被触发两次,这样比较浪费性能。以及transition.end事件也只绑定一次,否则每次点击时都会绑定,同样浪费性能
self.$ele.off(trans.end).one(trans.end, function () {
self.$ele.triggerHandler('moved', self.$ele);
})


self.$ele.css({
left: x,
top: y,
});

})

};


Css3.prototype.x = function (x) {

this.to(x);
};


Css3.prototype.y = function (y) {

this.to(undefined, y);
};




function Js($ele) {

// 调用init初始化函数,并且使用call方法改变this的指向,最后传入DOM元素
init.call(this, $ele);

this.$ele.removeClass('transition');

}


Js.prototype.to = function (x, y) {

var self = this;

// 调用to具体移动的函数,并且使用call方法改变this的指向,传入X轴和Y轴的位置数值,并传入一个回调函数
to.call(this, x, y, function () {
self.$ele.stop().animate({
left: x,
top: y,


// 写入一个回调函数当作发送消息的函数。当元素已经到达目标位置后,触发自定义事件moved
}, function () {

self.$ele.triggerHandler('moved', self.$ele);

});
})

};


Js.prototype.x = function (x) {

this.to(x);
};


Js.prototype.y = function (y) {

this.to(undefined, y);
};



// 调用滑动切换效果时的默认参数对象
var defaults = {
css3: false,
js: false,
};


// 定义一个move函数,确定使用哪种动画效果
function move($ele, options) {

// 定义一个变量mode,用来保存使用的动画效果
var mode = null;

// 如果调用时传入的参数中css3的属性为真并且浏览器支持transition属性,使用css3动画效果
if(options.css3 && trans.isSupport) {

mode = new Css3($ele);

// 如果不满足上面使用css3动画效果的条件,使用JS动画效果
}else if(options.js) {

mode = new Js($ele);

// css3和js都不满足,使用没有动画效果
}else {

mode = new Silent($ele);
};


// 最后返回一个对象,三个属性:to、x、y,使用$.proxy()方法传入对应着动画效果里的to、x、y函数,最后传入动画效果的构造函数,改变this指向。
return {
to: $.proxy(mode.to, mode),
x: $.proxy(mode.x, mode),
y: $.proxy(mode.y, mode),
}

}





$.fn.extend({
move: function (options, x, y) {
return this.each(function () {
var $this = $(this),

mode = $this.data('move'),

newOptions = $.extend({}, defaults, typeof options === 'object' && options);

if(!mode) {
$this.data('move', mode = move($this, newOptions))
}

if(typeof mode[options] === 'function') {
mode[options](x, y);
}
})
}
})
})(jQuery)


正在回答 回答被采纳积分+1

登陆购买课程后可参与讨论,去登陆

1回答
好帮手慕慕子 2021-01-21 18:58:45

同学你好, 代码效果实现是对的,同学先点击按钮切换到最后一张图片,然后再点击第一个按钮,切换到第一张图片,与视频中的打印的顺序就是一样的了,示例:

http://img1.sycdn.imooc.com//climg/60095c120956a78111290811.jpg

可以下载老师的源码测试下,也是点击下一张时和你打印的是一样的,点击上一张打印的和视频中的一样

祝学习愉快~

  • 提问者 粉墨登场 #1

    老师,为啥要先切换到最后一张图片,再切换到第一张图片时就对了呢? 

    2021-01-21 19:33:37
  • 好帮手慕慕子 回复 提问者 粉墨登场 #2

    同学你好,因为要实现从后往前切换的顺序,所以需要先切换到最后一张图片,然后再切换到第一张图片哦,祝学习愉快~

    2021-01-21 19:38:28
问题已解决,确定采纳
还有疑问,暂不采纳

恭喜解决一个难题,获得1积分~

来为老师/同学的回答评分吧

0 星
2.组件化网页开发
  • 参与学习           人
  • 提交作业       1121    份
  • 解答问题       14456    个

本阶段在运用JS实现动态网页开发的基础上,带你深入理解企业开发核心思想,完成一个企业级网页的开发,体验前端工程师的成就感。

了解课程
请稍等 ...
意见反馈 帮助中心 APP下载
官方微信

在线咨询

领取优惠

免费试听

领取大纲

扫描二维码,添加
你的专属老师