sencha touch NavigationView

NavigationView 是官方根据Container控件扩展而来的,由一个导航栏和一个card组成,具备导航和返回时自动销毁当前界面的功能,非常适合新手使用。

其中导航栏的代码如下:

 

  1 Ext.define('Ext.navigation.Bar', {
  2     extend: 'Ext.TitleBar',
  3     requires: ['Ext.Button', 'Ext.Spacer'],
  4     isToolbar: true,
  5     config: {
  6         baseCls: Ext.baseCSSPrefix + 'toolbar',
  7         cls: Ext.baseCSSPrefix + 'navigation-bar',
  8         ui: 'dark',
  9         title: null,
 10         defaultType: 'button',
 11         layout: {
 12             type: 'hbox'
 13         },
 14         defaultBackButtonText: 'Back',
 15         animation: {
 16             duration: 300
 17         },
 18         useTitleForBackButtonText: null,
 19         view: null,
 20         android2Transforms: false,
 21         backButton: {
 22             align: 'left',
 23             ui: 'back',
 24             hidden: true
 25         }
 26     },
 27     platformConfig: [{
 28         theme: ['Blackberry'],
 29         animation: false
 30     }],
 31     constructor: function(config) {
 32         config = config || {};
 33         if (!config.items) {
 34             config.items = []
 35         }
 36         this.backButtonStack = [];
 37         this.activeAnimations = [];
 38         this.callParent([config])
 39     },
 40     applyBackButton: function(config) {
 41         return Ext.factory(config, Ext.Button, this.getBackButton())
 42     },
 43     updateBackButton: function(newBackButton, oldBackButton) {
 44         if (oldBackButton) {
 45             this.remove(oldBackButton)
 46         }
 47         if (newBackButton) {
 48             this.add(newBackButton);
 49             newBackButton.on({
 50                 scope: this,
 51                 tap: this.onBackButtonTap
 52             })
 53         }
 54     },
 55     onBackButtonTap: function() {
 56         this.fireEvent('back', this)
 57     },
 58     updateView: function(newView) {
 59         var me = this,
 60         backButton = me.getBackButton(),
 61         innerItems,
 62         i,
 63         backButtonText,
 64         item,
 65         title,
 66         titleText;
 67         me.getItems();
 68         if (newView) {
 69             innerItems = newView.getInnerItems();
 70             for (i = 0; i < innerItems.length; i++) {
 71                 item = innerItems[i];
 72                 title = (item.getTitle) ? item.getTitle() : item.config.title;
 73                 me.backButtonStack.push(title || '&nbsp;')
 74             }
 75             titleText = me.getTitleText();
 76             if (titleText === undefined) {
 77                 titleText = ''
 78             }
 79             me.setTitle(titleText);
 80             backButtonText = me.getBackButtonText();
 81             if (backButtonText) {
 82                 backButton.setText(backButtonText);
 83                 backButton.show()
 84             }
 85         }
 86     },
 87     onViewAdd: function(view, item) {
 88         var me = this,
 89         backButtonStack = me.backButtonStack,
 90         hasPrevious, title;
 91         me.endAnimation();
 92         title = (item.getTitle) ? item.getTitle() : item.config.title;
 93         backButtonStack.push(title || '&nbsp;');
 94         hasPrevious = backButtonStack.length > 1;
 95         me.doChangeView(view, hasPrevious, false)
 96     },
 97     onViewRemove: function(view) {
 98         var me = this,
 99         backButtonStack = me.backButtonStack,
100         hasPrevious;
101         me.endAnimation();
102         backButtonStack.pop();
103         hasPrevious = backButtonStack.length > 1;
104         me.doChangeView(view, hasPrevious, true)
105     },
106     doChangeView: function(view, hasPrevious, reverse) {
107         var me = this,
108         leftBox = me.leftBox,
109         leftBoxElement = leftBox.element,
110         titleComponent = me.titleComponent,
111         titleElement = titleComponent.element,
112         backButton = me.getBackButton(),
113         titleText = me.getTitleText(),
114         backButtonText = me.getBackButtonText(),
115         animation = me.getAnimation() && view.getLayout().getAnimation(),
116         animated = animation && animation.isAnimation && view.isPainted(),
117         properties,
118         leftGhost,
119         titleGhost,
120         leftProps,
121         titleProps;
122         if (animated) {
123             leftGhost = me.createProxy(leftBox.element);
124             leftBoxElement.setStyle('opacity', '0');
125             backButton.setText(backButtonText);
126             backButton[hasPrevious ? 'show': 'hide']();
127             titleGhost = me.createProxy(titleComponent.element.getParent());
128             titleElement.setStyle('opacity', '0');
129             me.setTitle(titleText);
130             properties = me.measureView(leftGhost, titleGhost, reverse);
131             leftProps = properties.left;
132             titleProps = properties.title;
133             me.isAnimating = true;
134             me.animate(leftBoxElement, leftProps.element);
135             me.animate(titleElement, titleProps.element,
136             function() {
137                 titleElement.setLeft(properties.titleLeft);
138                 me.isAnimating = false;
139                 me.refreshTitlePosition()
140             });
141             if (Ext.browser.is.AndroidStock2 && !this.getAndroid2Transforms()) {
142                 leftGhost.ghost.destroy();
143                 titleGhost.ghost.destroy()
144             } else {
145                 me.animate(leftGhost.ghost, leftProps.ghost);
146                 me.animate(titleGhost.ghost, titleProps.ghost,
147                 function() {
148                     leftGhost.ghost.destroy();
149                     titleGhost.ghost.destroy()
150                 })
151             }
152         } else {
153             if (hasPrevious) {
154                 backButton.setText(backButtonText);
155                 backButton.show()
156             } else {
157                 backButton.hide()
158             }
159             me.setTitle(titleText)
160         }
161     },
162     measureView: function(oldLeft, oldTitle, reverse) {
163         var me = this,
164         barElement = me.element,
165         newLeftElement = me.leftBox.element,
166         titleElement = me.titleComponent.element,
167         minOffset = Math.min(barElement.getWidth() / 3, 200),
168         newLeftWidth = newLeftElement.getWidth(),
169         barX = barElement.getX(),
170         barWidth = barElement.getWidth(),
171         titleX = titleElement.getX(),
172         titleLeft = titleElement.getLeft(),
173         titleWidth = titleElement.getWidth(),
174         oldLeftX = oldLeft.x,
175         oldLeftWidth = oldLeft.width,
176         oldLeftLeft = oldLeft.left,
177         useLeft = Ext.browser.is.AndroidStock2 && !this.getAndroid2Transforms(),
178         newOffset,
179         oldOffset,
180         leftAnims,
181         titleAnims,
182         omega,
183         theta;
184         theta = barX - oldLeftX - oldLeftWidth;
185         if (reverse) {
186             newOffset = theta;
187             oldOffset = Math.min(titleX - oldLeftWidth, minOffset)
188         } else {
189             oldOffset = theta;
190             newOffset = Math.min(titleX - barX, minOffset)
191         }
192         if (useLeft) {
193             leftAnims = {
194                 element: {
195                     from: {
196                         left: newOffset,
197                         opacity: 1
198                     },
199                     to: {
200                         left: 0,
201                         opacity: 1
202                     }
203                 }
204             }
205         } else {
206             leftAnims = {
207                 element: {
208                     from: {
209                         transform: {
210                             translateX: newOffset
211                         },
212                         opacity: 0
213                     },
214                     to: {
215                         transform: {
216                             translateX: 0
217                         },
218                         opacity: 1
219                     }
220                 },
221                 ghost: {
222                     to: {
223                         transform: {
224                             translateX: oldOffset
225                         },
226                         opacity: 0
227                     }
228                 }
229             }
230         }
231         theta = barX - titleX + newLeftWidth;
232         if ((oldLeftLeft + titleWidth) > titleX) {
233             omega = barX - titleX - titleWidth
234         }
235         if (reverse) {
236             titleElement.setLeft(0);
237             oldOffset = barX + barWidth - titleX - titleWidth;
238             if (omega !== undefined) {
239                 newOffset = omega
240             } else {
241                 newOffset = theta
242             }
243         } else {
244             newOffset = barX + barWidth - titleX - titleWidth;
245             if (omega !== undefined) {
246                 oldOffset = omega
247             } else {
248                 oldOffset = theta
249             }
250             newOffset = Math.max(titleLeft, newOffset)
251         }
252         if (useLeft) {
253             titleAnims = {
254                 element: {
255                     from: {
256                         left: newOffset,
257                         opacity: 1
258                     },
259                     to: {
260                         left: titleLeft,
261                         opacity: 1
262                     }
263                 }
264             }
265         } else {
266             titleAnims = {
267                 element: {
268                     from: {
269                         transform: {
270                             translateX: newOffset
271                         },
272                         opacity: 0
273                     },
274                     to: {
275                         transform: {
276                             translateX: titleLeft
277                         },
278                         opacity: 1
279                     }
280                 },
281                 ghost: {
282                     to: {
283                         transform: {
284                             translateX: oldOffset
285                         },
286                         opacity: 0
287                     }
288                 }
289             }
290         }
291         return {
292             left: leftAnims,
293             title: titleAnims,
294             titleLeft: titleLeft
295         }
296     },
297     animate: function(element, config, callback) {
298         var me = this,
299         animation;
300         element.setLeft(0);
301         config = Ext.apply(config, {
302             element: element,
303             easing: 'ease-in-out',
304             duration: me.getAnimation().duration || 250,
305             preserveEndState: true
306         });
307         animation = new Ext.fx.Animation(config);
308         animation.on('animationend',
309         function() {
310             if (callback) {
311                 callback.call(me)
312             }
313         },
314         me);
315         Ext.Animator.run(animation);
316         me.activeAnimations.push(animation)
317     },
318     endAnimation: function() {
319         var activeAnimations = this.activeAnimations,
320         animation, i, ln;
321         if (activeAnimations) {
322             ln = activeAnimations.length;
323             for (i = 0; i < ln; i++) {
324                 animation = activeAnimations[i];
325                 if (animation.isAnimating) {
326                     animation.stopAnimation()
327                 } else {
328                     animation.destroy()
329                 }
330             }
331             this.activeAnimations = []
332         }
333     },
334     refreshTitlePosition: function() {
335         if (!this.isAnimating) {
336             this.callParent()
337         }
338     },
339     getBackButtonText: function() {
340         var text = this.backButtonStack[this.backButtonStack.length - 2],
341         useTitleForBackButtonText = this.getUseTitleForBackButtonText();
342         if (!useTitleForBackButtonText) {
343             if (text) {
344                 text = this.getDefaultBackButtonText()
345             }
346         }
347         return text
348     },
349     getTitleText: function() {
350         return this.backButtonStack[this.backButtonStack.length - 1]
351     },
352     beforePop: function(count) {
353         count--;
354         for (var i = 0; i < count; i++) {
355             this.backButtonStack.pop()
356         }
357     },
358     doSetHidden: function(hidden) {
359         if (!hidden) {
360             this.element.setStyle({
361                 position: 'relative',
362                 top: 'auto',
363                 left: 'auto',
364                 width: 'auto'
365             })
366         } else {
367             this.element.setStyle({
368                 position: 'absolute',
369                 top: '-1000px',
370                 left: '-1000px',
371                 width: this.element.getWidth() + 'px'
372             })
373         }
374     },
375     createProxy: function(element) {
376         var ghost, x, y, left, width;
377         ghost = element.dom.cloneNode(true);
378         ghost.id = element.id + '-proxy';
379         element.getParent().dom.appendChild(ghost);
380         ghost = Ext.get(ghost);
381         x = element.getX();
382         y = element.getY();
383         left = element.getLeft();
384         width = element.getWidth();
385         ghost.setStyle('position', 'absolute');
386         ghost.setX(x);
387         ghost.setY(y);
388         ghost.setHeight(element.getHeight());
389         ghost.setWidth(width);
390         return {
391             x: x,
392             y: y,
393             left: left,
394             width: width,
395             ghost: ghost
396         }
397     }
398 });

 

可以看出他是继承于一个TitleBar,中间为标题,左侧有一个默认的返回按钮这些代码看似复杂,其实逻辑很简单。

他的主要作用就是监听返回按钮,为其添加一个back自定义事件。并且通过this.backButtonStack这个数组来储存标题显示记录。

在返回时动态更新标题栏,并且有切换的动画效果。由于标题长短不一,所以整个导航栏的代码大部分都是来处理切换动画了。

实际上它的核心方法只有:

constructor:进行配置的初始化处理

applyBackButton:动态创建返回按钮

updateBackButton:动态更新返回按钮,并且添加监听(触发onBackButtonTap

onBackButtonTap:将返回按钮的点击转换为自定义事件back

updateView:更新导航栏按钮,标题。NavigationView中更新视图时会触发它

onViewAdd:添加新的历史记录。NavigationView中添加新的视图时会触发它

onViewRemove:移除当前的历史记录,NavigationView中移除视图时会触发它

doChangeView:视图改变后处理标题,返回按钮。大部分代码其实是处理切换动画效果

getBackButtonText:获取返回按钮的text值,如果useTitleForBackButtonText为ture就需要它来处理

getTitleText:获取最后一个标题

beforePop:点返回按钮时处理this.backButtonStack这个数组

我们如果想要重写它可以注意这些方法,其他的都是用来处理动画效果的。个人觉得没必要有这些方法,会影响性能

 

NavigationView代码如下:

 

  1 Ext.define('Ext.navigation.View', {
  2     extend: 'Ext.Container',
  3     alternateClassName: 'Ext.NavigationView',
  4     xtype: 'navigationview',
  5     requires: ['Ext.navigation.Bar'],
  6     config: {
  7         baseCls: Ext.baseCSSPrefix + 'navigationview',
  8         navigationBar: {
  9             docked: 'top'
 10         },
 11         defaultBackButtonText: 'Back',
 12         useTitleForBackButtonText: false,
 13         layout: {
 14             type: 'card',
 15             animation: {
 16                 duration: 300,
 17                 easing: 'ease-out',
 18                 type: 'slide',
 19                 direction: 'left'
 20             }
 21         }
 22     },
 23     platformConfig: [{
 24         theme: ['Blackberry'],
 25         navigationBar: {
 26             splitNavigation: true
 27         }
 28     }],
 29     initialize: function() {
 30         var me = this,
 31         navBar = me.getNavigationBar();
 32         if (navBar) {
 33             navBar.on({
 34                 back: me.onBackButtonTap,
 35                 scope: me
 36             });
 37             me.relayEvents(navBar, 'rightbuttontap');
 38             me.relayEvents(me, {
 39                 add: 'push',
 40                 remove: 'pop'
 41             })
 42         }
 43         var layout = me.getLayout();
 44         if (layout && !layout.isCard) {
 45             Ext.Logger.error('The base layout for a NavigationView must always be a Card Layout')
 46         }
 47     },
 48     applyLayout: function(config) {
 49         config = config || {};
 50         return config
 51     },
 52     onBackButtonTap: function() {
 53         this.pop();
 54         this.fireEvent('back', this)
 55     },
 56     push: function(view) {
 57         return this.add(view)
 58     },
 59     pop: function(count) {
 60         if (this.beforePop(count)) {
 61             return this.doPop()
 62         }
 63     },
 64     beforePop: function(count) {
 65         var me = this,
 66         innerItems = me.getInnerItems();
 67         if (Ext.isString(count) || Ext.isObject(count)) {
 68             var last = innerItems.length - 1,
 69             i;
 70             for (i = last; i >= 0; i--) {
 71                 if ((Ext.isString(count) && Ext.ComponentQuery.is(innerItems[i], count)) || (Ext.isObject(count) && count == innerItems[i])) {
 72                     count = last - i;
 73                     break
 74                 }
 75             }
 76             if (!Ext.isNumber(count)) {
 77                 return false
 78             }
 79         }
 80         var ln = innerItems.length,
 81         toRemove;
 82         if (!Ext.isNumber(count) || count < 1) {
 83             count = 1
 84         }
 85         count = Math.min(count, ln - 1);
 86         if (count) {
 87             me.getNavigationBar().beforePop(count);
 88             toRemove = innerItems.splice( - count, count - 1);
 89             for (i = 0; i < toRemove.length; i++) {
 90                 this.remove(toRemove[i])
 91             }
 92             return true
 93         }
 94         return false
 95     },
 96     doPop: function() {
 97         var me = this,
 98         innerItems = this.getInnerItems();
 99         me.remove(innerItems[innerItems.length - 1]);
100         if (innerItems.length < 3 && this.$backButton) {
101             this.$backButton.hide()
102         }
103         if (this.$titleContainer) {
104             if (!this.$titleContainer.setTitle) {
105                 Ext.Logger.error('You have selected to display a title in a component that does not                     support titles in NavigationView. Please remove the `title` configuration from your                     NavigationView item, or change it to a component that has a `setTitle` method.')
106             }
107             var item = innerItems[innerItems.length - 2];
108             this.$titleContainer.setTitle((item.getTitle) ? item.getTitle() : item.config.title)
109         }
110         return this.getActiveItem()
111     },
112     getPreviousItem: function() {
113         var innerItems = this.getInnerItems();
114         return innerItems[innerItems.length - 2]
115     },
116     updateUseTitleForBackButtonText: function(useTitleForBackButtonText) {
117         var navigationBar = this.getNavigationBar();
118         if (navigationBar) {
119             navigationBar.setUseTitleForBackButtonText(useTitleForBackButtonText)
120         }
121     },
122     updateDefaultBackButtonText: function(defaultBackButtonText) {
123         var navigationBar = this.getNavigationBar();
124         if (navigationBar) {
125             navigationBar.setDefaultBackButtonText(defaultBackButtonText)
126         }
127     },
128     applyNavigationBar: function(config) {
129         if (!config) {
130             config = {
131                 hidden: true,
132                 docked: 'top'
133             }
134         }
135         if (config.title) {
136             delete config.title;
137             Ext.Logger.warn("Ext.navigation.View: The 'navigationBar' configuration does not accept a 'title' property. You set the title of the navigationBar by giving this navigation view's children a 'title' property.")
138         }
139         config.view = this;
140         config.useTitleForBackButtonText = this.getUseTitleForBackButtonText();
141         if (config.splitNavigation) {
142             this.$titleContainer = this.add({
143                 docked: 'top',
144                 xtype: 'titlebar',
145                 ui: 'light',
146                 title: this.$currentTitle || ''
147             });
148             var containerConfig = (config.splitNavigation === true) ? {}: config.splitNavigation;
149             this.$backButtonContainer = this.add(Ext.apply({
150                 xtype: 'toolbar',
151                 docked: 'bottom'
152             },
153             containerConfig));
154             this.$backButton = this.$backButtonContainer.add({
155                 xtype: 'button',
156                 text: 'Back',
157                 hidden: true,
158                 ui: 'back'
159             });
160             this.$backButton.on({
161                 scope: this,
162                 tap: this.onBackButtonTap
163             });
164             config = {
165                 hidden: true,
166                 docked: 'top'
167             }
168         }
169         return Ext.factory(config, Ext.navigation.Bar, this.getNavigationBar())
170     },
171     updateNavigationBar: function(newNavigationBar, oldNavigationBar) {
172         if (oldNavigationBar) {
173             this.remove(oldNavigationBar, true)
174         }
175         if (newNavigationBar) {
176             this.add(newNavigationBar)
177         }
178     },
179     applyActiveItem: function(activeItem, currentActiveItem) {
180         var me = this,
181         innerItems = me.getInnerItems();
182         me.getItems();
183         if (!me.initialized) {
184             activeItem = innerItems.length - 1
185         }
186         return this.callParent([activeItem, currentActiveItem])
187     },
188     doResetActiveItem: function(innerIndex) {
189         var me = this,
190         innerItems = me.getInnerItems(),
191         animation = me.getLayout().getAnimation();
192         if (innerIndex > 0) {
193             if (animation && animation.isAnimation) {
194                 animation.setReverse(true)
195             }
196             me.setActiveItem(innerIndex - 1);
197             me.getNavigationBar().onViewRemove(me, innerItems[innerIndex], innerIndex)
198         }
199     },
200     doRemove: function() {
201         var animation = this.getLayout().getAnimation();
202         if (animation && animation.isAnimation) {
203             animation.setReverse(false)
204         }
205         this.callParent(arguments)
206     },
207     onItemAdd: function(item, index) {
208         if (item && item.getDocked() && item.config.title === true) {
209             this.$titleContainer = item
210         }
211         this.doItemLayoutAdd(item, index);
212         var navigaitonBar = this.getInitialConfig().navigationBar;
213         if (!this.isItemsInitializing && item.isInnerItem()) {
214             this.setActiveItem(item);
215             if (navigaitonBar) {
216                 this.getNavigationBar().onViewAdd(this, item, index)
217             }
218             if (this.$backButtonContainer) {
219                 this.$backButton.show()
220             }
221         }
222         if (item && item.isInnerItem()) {
223             this.updateTitleContainerTitle((item.getTitle) ? item.getTitle() : item.config.title)
224         }
225         if (this.initialized) {
226             this.fireEvent('add', this, item, index)
227         }
228     },
229     updateTitleContainerTitle: function(title) {
230         if (this.$titleContainer) {
231             if (!this.$titleContainer.setTitle) {
232                 Ext.Logger.error('You have selected to display a title in a component that does not                     support titles in NavigationView. Please remove the `title` configuration from your                     NavigationView item, or change it to a component that has a `setTitle` method.')
233             }
234             this.$titleContainer.setTitle(title)
235         } else {
236             this.$currentTitle = title
237         }
238     },
239     reset: function() {
240         return this.pop(this.getInnerItems().length)
241     }
242 });

 

这些代码就是核心了,作用如下:

initialize:初始化,为导航栏的返回事件添加监听(触发onBackButtonTap方法),为add和romove方法添加监听

onBackButtonTap:点击返回按钮时触发,触发pop方法,并且添加自定义事件。

push:其实就是调用add方法,添加新视图

pop:会调用beforePop方法和doPop方法

beforePop:有时候不止移除一项,这里的逻辑很复杂

doPop:移除card中最后一项

getPreviousItem:获取倒数第二项

updateUseTitleForBackButtonText:作用顾名思义

updateDefaultBackButtonText:同上

applyNavigationBar:创建导航栏而已,别怕代码多

updateNavigationBar:更新导航栏

applyActiveItem:为什么card始终显示最后一项,就是因为重写了它

doResetActiveItem:点击返回按钮时反转切换动画的

doRemove:调用remove方法后会触发也是反转切换动画

onItemAdd:项第一次被添加到card中触发,一系列的逻辑处理

updateTitleContainerTitle:顾名思义

reset:清空所有项,不过这里逻辑比较复杂。会调用pop方法

http://www.cnblogs.com/mlzs/p/3376399.html这里我有对代码进行一些注释,可以参考一下。

 

下面说说用法,个人推荐先创建一个视图继承它,如下:

 

 

 1 Ext.define('app.view.Main', {
 2     extend: 'Ext.NavigationView',
 3     xtype: 'main',
 4     config: {
 5         navigationBar: {
 6             backButton: {
 7                 iconCls: 'arrow_left',
 8                 ui: '',
 9                 cls: 'back'
10             }
11         },
12         cls: 'cardPanel'
13     }
14 });

 

app.js中初始化它:

1     launch: function () {
2         // Destroy the #appLoadingIndicator element
3         Ext.fly('appLoadingIndicator').destroy();
4         // Initialize the main view
5         Ext.Viewport.add(Ext.create('app.view.Main'));
6     }

单独的main控制层监听它:

1         //引用
2         refs: {
3             main: 'main'
4         }

添加一个路由监听

1         routes: {
2             'redirect/:view': 'showView'
3         }

main控制层中写一个方法:

1     //展示页面
2     showView: function (view, isPop) {
3         var main = this.getMain(),
4             view = Ext.create(xtype);
5         main.push(view, params);
6     }

任何控制层之中都可以通过如下方法触发这个方法

 this.redirectTo('redirect/xtype');

当然这个只是简单的用法,有兴趣的可以看看http://www.cnblogs.com/mlzs/p/3498846.html,里面有免费视频听,也可以参考官方的示例。

值得注意的是,NavigationView作为一个容器,虽然是继承于Container,里面的tpl,data,html这些都是不能使用的,他的布局也不能随便更改。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

转载于:https://www.cnblogs.com/mlzs/p/3550011.html

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值