Flex 设置模式窗口 不可移出 当前窗口外

此博客介绍了一个名为 MarketMovers 的弹窗组件及其管理器的实现细节,包括按钮栏选择逻辑、数据加载限制及状态管理等。该组件允许用户查看当前赛马比赛和接下来三场比赛的数据,并提供打印与帮助功能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

<?xml version="1.0" encoding="utf-8"?>
<s:TitleWindow xmlns:fx="http://ns.adobe.com/mxml/2009" 
			   xmlns:s="library://ns.adobe.com/flex/spark" 
			   xmlns:mx="library://ns.adobe.com/flex/mx"
			   xmlns:parsley="http://www.spicefactory.org/parsley"
			   
			   title="Market Movers"
			   width="790" height="500" xmlns:analytics="au.com.tabcorp.neo.analytics.*" 
			   xmlns:components="au.com.tabcorp.neo.common.components.*"
			   windowMoving="onWindowMovingHandler(event)"
			   currentState="{marketMoversPM.dataLoaded?'normal':'loading'}">
	<fx:Script>
		<![CDATA[
			
			import embeddedAssets.EmbeddedResourceModel;
			
			import mx.events.FlexEvent;
			
			import spark.events.TitleWindowBoundsEvent;
			
			[Bindable]
			public var marketMoversPM:MarketMoversPopupPM;
			
			/**
			 *  windowMovig event handler
			 */
			protected function onWindowMovingHandler(event:TitleWindowBoundsEvent):void
			{
				// code to restrict at left and right
				if (event.afterBounds.left < marketMoversPM.PANEL_MOVE_RESTRICTION_LEFT) {
					event.afterBounds.left = marketMoversPM.PANEL_MOVE_RESTRICTION_LEFT - 50;
				} else if (event.afterBounds.right > systemManager.stage.stageWidth + marketMoversPM.PANEL_MOVE_RESTRICTION_RIGHT)
				{
					event.afterBounds.left = systemManager.stage.stageWidth - 75;
				}
				
				// code to restrict at Top and Bottom
				if (event.afterBounds.top < 0) 
				{
					event.afterBounds.top = 0;
				}
				else if (event.afterBounds.bottom > systemManager.stage.stageHeight + marketMoversPM.PANEL_MOVE_RESTRICTION_BOTTOM) 
				{
					event.afterBounds.top = this.height + 75;
				}
			}
			
		]]>
	</fx:Script>
	
	<fx:Declarations>
		<parsley:FastInject property="marketMoversPM" 
							type="{MarketMoversPopupPM}" injectionComplete="marketMoversPM.onInjectionComplete()" />
				
		<analytics:LocationProvider location="{marketMoversPM.marketMoversTabLocation}" >
			<analytics:EventListenerActivationWatcher />
		</analytics:LocationProvider>
	</fx:Declarations>
	<s:states>
		<s:State name="normal"/>
		<s:State name="loading"/>
	</s:states>
	
	<s:Scroller id="scorllbar" width="100%" height="100%"  horizontalScrollPolicy="off"  verticalScrollPolicy="auto">
		<s:VGroup gap="0" width="100%" height="100%" paddingLeft="{scorllbar.visible?5:0}"
				  visible="true"
				  visible.loading="false" 
				  includeInLayout.normal="true"
				  includeInLayout.loading="false">
		
			<!-- Date, Help and Print buttons-->
			<s:HGroup width="760" height="47"
					  verticalAlign="middle">
				
				<components:DateLabel id="dateLabel" 
									  date="{marketMoversPM.serverTimeModel.currentTimeDate}" 
									  dateTimePattern="EEE dd MMM yyyy" 
									  timeZoneOffset="{marketMoversPM.serverTimeModel.globalTimeZoneOffset}" 
									  styleName="header1"/>
				
				<mx:Spacer width="100%"/>
				
				<s:Button label="Print" buttonMode="true" useHandCursor="true"
						  click="marketMoversPM.printClickHandler(this)" 
						  styleName="printButton"/>
				<s:Button click="marketMoversPM.helpClickHandler()"  buttonMode="true" useHandCursor="true"
						  skinClass="au.com.tabcorp.neo.common.presentation.skins.InfoButtonSkin"
						  height="18" width="18"/>
			</s:HGroup>
			
			<s:Line width="760">
				<s:stroke>
					<s:SolidColorStroke color="#DDDDDD" weight="2"/>
				</s:stroke>
			</s:Line>
			<s:HGroup width="100%" height="70" verticalAlign="middle" horizontalAlign="center">
				<s:ButtonBar id="buttonBarMarketMovers" 
							 dataProvider="{marketMoversPM.ac}"
							 selectedItem="{marketMoversPM.selectedRaceOption}"
							 selectedIndex="@{marketMoversPM.selectedIndexButtonBar}"
							 change="marketMoversPM.onChangeMarketMoversButtonBar(event)"
							 enabled="{marketMoversPM.enableButtonBar}"/>
			</s:HGroup>
			
			<!-- DataGrid DataGroup -->		
				<s:DataGroup dataProvider="{marketMoversPM.model.marketMovers}" height="100%">
					
					<s:layout>
						<s:VerticalLayout />
					</s:layout>
					
					<s:itemRenderer>
						<fx:Component>
							<presentation:MarketMoversIRv2 marketMoversPM="{outerDocument.marketMoversPM}" />
						</fx:Component>
					</s:itemRenderer>
					
				</s:DataGroup>
		</s:VGroup>
		
	</s:Scroller>
	<s:HGroup width="100%" height="100%" 
			  horizontalAlign="center"
			  verticalAlign="middle"
			  visible="false"
			  visible.loading="true" 
			  includeInLayout="false"
			  includeInLayout.loading="true">
		<mx:SWFLoader source="{EmbeddedResourceModel.loadingIcon}" />
	</s:HGroup>
</s:TitleWindow>

 

package au.com.tabcorp.neo.racing.marketmovers.presentation
{
	
	import flash.display.DisplayObject;
	import flash.events.Event;
	import flash.net.URLRequest;
	import flash.net.navigateToURL;
	
	import mx.collections.ArrayCollection;
	import mx.collections.ListCollectionView;
	import mx.core.FlexGlobals;
	import mx.core.IFlexDisplayObject;
	import mx.core.UIComponent;
	import mx.managers.PopUpManager;
	
	import org.spicefactory.lib.reflect.types.Void;
	
	import spark.collections.Sort;
	import spark.collections.SortField;
	import spark.events.IndexChangeEvent;
	import spark.events.TitleWindowBoundsEvent;
	
	public class MarketMoversPopupPM extends Broadcaster
	{
		[Inject] [Bindable] 	
		public var serverTimeModel:ServerTimeModel;
		
		[Inject] [Bindable]
		public var model:MarketMoversModel;
		/**
		 * DG column widths so we can match footer to DG
		 */ 
		public const SMALL_COLUMN_WIDTH:int = 30;
		public const MEDIUM_COLUMN_WIDTH:int = 70;
		public const MEDIUM_COLUMN_WIDTH_2:int = 85;
		public const MEDIUM_COLUMN_WIDTH_3:int = 100;
		public const LARGE_COLUMN_WIDTH:int = 160;
		public const LARGE_COLUMN_WIDTH_2:int = 170;
		public const PANEL_WIDTH:int = 760;
		
		public const PANEL_MOVE_RESTRICTION_LEFT:int = -660;
		public const PANEL_MOVE_RESTRICTION_RIGHT:int = 660;
		public const PANEL_MOVE_RESTRICTION_BOTTOM:int = 450;
		
		[Bidnable]
		public var marketMoversListView:ListCollectionView;
		[Bindable]
		public var selectedRaceOption:Object;
		[Bindable]
		public var ac:ArrayCollection;
		[Bindable]
		public var arrRaceNos:ArrayCollection;
		[Bindable]
		public var marketMoversTabLocation : String = MARKET_MOVERS_LOCATION;
		[Bindable] public var dataLoaded:Boolean;
		
		private static const MARKET_MOVERS_LOCATION:String 	= "informational:market movers";
		private static const NEXT_3_RACES_TO_GO:String 		= "informational:next 3 races to go";
		private static const INDEX_VIEW_CURRENT_RACE : int = 0;
		private static const INDEX_VIEW_NEXT_THREE_RACE : int = 1;
		
		/**
		 *  variable to store the currentRace Details at PM level only - not into to model 
		 */		
		private var _currentRacingCode : String;
		private var _currentMeetingName : String;
		private var _currentMeetingDateRaw : Date;
		private var _currentRaceNumber : int;
		
		private var _enableButtonBar : Boolean;
		private var _todayDate:Date;
		private var _numberSortField:SortField;
		private var _numberSort:Sort;
		private var _runnerNameSortField : SortField;
		private var _runnerNameSort : Sort;
		private var _oddsSortField : SortField;
		private var _oddsSort : Sort;
		private var _percentChangeSortField : SortField;
		private var _percentChangeSort : Sort;
		private var _selectedIndexButtonBar : int;
		private var callFromExternal : Boolean = true;
		
		public function MarketMoversPopupPM()
		{
			ac = new ArrayCollection([
				{value: MarketMoversModel.MARKET_MOVERS_CURRENT_VIEW, label: "CURRENT RACE"},
				{value: MarketMoversModel.MARKET_MOVERS_NEXT_3_RACES, label: "NEXT 3 RACES"}
			]);
			
			// sort fields
			_numberSortField = new SortField();
			_numberSortField.name = 'runnerNumber';
			_numberSortField.numeric = true;
			_numberSort = new Sort();
			_numberSort.fields = [_numberSortField];
			
			_runnerNameSortField = new SortField();
			_runnerNameSortField.name = 'runnerName';
			_runnerNameSort = new Sort();
			_runnerNameSort.fields = [_runnerNameSortField];
			
			_oddsSortField = new SortField();
			_oddsSortField.name = RacingConstants.SORT_LABEL_OPENING_ODDS;
			_oddsSort = new Sort();
			_oddsSort.fields = [_oddsSortField];
			
			_percentChangeSortField = new SortField();
			_percentChangeSortField.name = RacingConstants.SORT_LABEL_PERCENT_CHANGE;
			_percentChangeSort = new Sort();
			_percentChangeSort.fields = [_percentChangeSortField];
		}
		
		[Init]
		public function onInjectionComplete() : void
		{
			todayDate = serverTimeModel.currentTimeDate;
			marketMoversListView = new ListCollectionView(model.marketMovers);
			dataLoaded = true;
			
		}
		
		[Init]
		public function init():void
		{
			var source:Array = new Array(1, 2, 3);
			arrRaceNos = new ArrayCollection(source);
			
			if(serverTimeModel.currentTimeDate)
				onInjectionComplete();
		}
		
		public function onWeatherIcon_clickHandler(meetingMNC3:String, meetingName:String):void
		{
			broadcast(new GetWeatherInfoMessage(meetingMNC3, meetingName));
		}
		
		public function onTrackClickHandler(source:MarketMoversVO):void
		{
			var trackRequest:TrackRequest = new TrackRequest();
			trackRequest.meetingCode = source.meetingCode;
			trackRequest.meetingName = source.meetingName;
			trackRequest.raceNumber = source.raceNumber;
			trackRequest.racingCode = source.racingCode;
			trackRequest.meetingDateRaw = source.raceStartTime;//race.meeting.meetingDate;
			broadcast(new GetTrackEvent(trackRequest));
		}
		
		///////////////////////////////////////////////////////////////////////
		//                           handlers                                //
		///////////////////////////////////////////////////////////////////////
		
		public function onChangeMarketMoversButtonBar(event:IndexChangeEvent):void
		{
			selectedRaceOption = event.currentTarget.selectedItem;
			
			//model.changeRaceListView(selectedRaceOption.value);
			getMarketMoversData(selectedIndexButtonBar);
			
			// TODO : show loader - while getting new data
			
			//analytics: track the market movers/next 3 races to go location
			if(selectedRaceOption.value == MarketMoversModel.MARKET_MOVERS_NEXT_3_RACES)
				marketMoversTabLocation = NEXT_3_RACES_TO_GO;
			else
				marketMoversTabLocation = MARKET_MOVERS_LOCATION;
			dispatchEvent(new LocationChangeEvent());
		}
		
		// ----------------------------------------------------------
		// 					PUBLIC METHODS
		// ----------------------------------------------------------
		
		private function getMarketMoversData(selectedIndex:int) : void
		{
			callFromExternal = false;
			if(selectedIndex == INDEX_VIEW_CURRENT_RACE)
			{
				dataLoaded = false;
				// call to a specific race MarketMovers ( current race )
				var request:MarketMoversRequest = new MarketMoversRequest();
				request.racingCode = this._currentRacingCode;
				request.raceNumber = this._currentRaceNumber;
				request.meetingName = this._currentMeetingName;
				request.meetingDateRaw = this._currentMeetingDateRaw;
				broadcast(new MarketMoversMessage(request));
			}
			else if(selectedIndex == INDEX_VIEW_NEXT_THREE_RACE)
			{
				dataLoaded = false;
				// for the default market movers call - like the RacingOverview
				var mmRequest : MarketMoversRequest = new MarketMoversRequest();
				var msg:CallMarketMoversMessage = new CallMarketMoversMessage(mmRequest);
				broadcast(msg);
			}
		}
		
		public function storeCurrentRaceDetails(req : MarketMoversRequest) : void
		{
			this._currentRacingCode = req.racingCode;
			this._currentMeetingName = req.meetingName;
			this._currentMeetingDateRaw = req.meetingDateRaw;
			this._currentRaceNumber = req.raceNumber;
		}
		
		public function removeCurrentRaceDetails() : void
		{
			this._currentRacingCode = null;
			this._currentMeetingName = null;
			this._currentMeetingDateRaw = new Date();
			this._currentRaceNumber = 0;
		}

		
		public function printClickHandler(component:UIComponent):void
		{
			var printReceipt:PrintPreviewPopUp = new PrintPreviewPopUp();
			PopUpManager.addPopUp(printReceipt as IFlexDisplayObject, FlexGlobals.topLevelApplication as DisplayObject, true);
			
			if(component.height>0 && component.width>0)
			{
				printReceipt.toBePrintData = component;
				printReceipt.x = FlexGlobals.topLevelApplication.width/2 - component.width/2;
				printReceipt.y = FlexGlobals.topLevelApplication.height/2 - component.height/2;
				PopUpManager.bringToFront(printReceipt as IFlexDisplayObject );
			}
		}
		
		public function navigateToRacePage(marketMoversVO:MarketMoversVO) : void
		{
			var request:RaceDetailsRequestV2 = new RaceDetailsRequestV2();
			request.meetingName = marketMoversVO.meetingName;
			request.meetingCode = marketMoversVO.meetingCode;
			request.meetingDateRaw = marketMoversVO.raceStartTime;
			request.racingCode = marketMoversVO.racingCode;
			request.raceNumber = String(marketMoversVO.raceNumber);
			
			broadcast(new RacingNavigatorMessage(RacingNavigatorModel.BETTING_PAGE, null, false, request));
			
			closeMarketMovers();
		}
		
		/**
		 *  to close the market movers popup
		 */		
		[MessageHandler]
		public function messageHandlerCloseMarketMoversPopup( message : CloseMarketMoversPopupMessage) : void
		{
			// next call will be from external only
			callFromExternal = true;
		}
		
		public function closeMarketMovers() : void
		{
			callFromExternal = true;
			var message : CloseMarketMoversPopupMessage = new CloseMarketMoversPopupMessage(
				CloseMarketMoversPopupMessage.Close_Market_Movers_Popup_Message);
			broadcast(message);
		}
		
		// ----------------------------------------------------------
		// 					SUBSCRIBE HANDLER
		// ----------------------------------------------------------
		
		private var _requestedRaceNo:int;
		
		private var _marketMoversRequest:MarketMoversRequest;
		
		public function get marketMoversRequest():MarketMoversRequest
		{
			return _marketMoversRequest
		}
		
		public function get requestedRaceNo():int
		{
			return _requestedRaceNo;
		}
		
		[Subscribe] 
		public function set marketMoversRequest(value:MarketMoversRequest):void
		{
			_marketMoversRequest = value;
			if (marketMoversRequest)
			{
				_requestedRaceNo = marketMoversRequest.raceNumber;
				broadcast(new MarketMoversMessage(marketMoversRequest));
			}
		}
		
		public function viewToBeShown(showCurrentRace:Boolean = false) : void
		{
			if(callFromExternal)
			{
				if(showCurrentRace)
				{
					// show current race button
					enableButtonBar = true;
					callFromExternal = true;
					selectedIndexButtonBar = INDEX_VIEW_CURRENT_RACE;
					selectedRaceOption = ac.getItemAt(INDEX_VIEW_CURRENT_RACE);
				}
				else
				{
					selectedIndexButtonBar = INDEX_VIEW_NEXT_THREE_RACE;
					selectedRaceOption = ac.getItemAt(INDEX_VIEW_NEXT_THREE_RACE);
					enableButtonBar = false;
				}
			}
		}
		
		// Getter / Setter
		[Bindable(event="todayDateChanged")]
		public function get todayDate():Date
		{
			return _todayDate;
		}
		
		public function set todayDate(value:Date):void
		{
			_todayDate = value;
			dispatchEvent(new Event("todayDateChanged"));
		}

		[Bindable(event="selectedIndexButtonBarChange")]
		public function get selectedIndexButtonBar():int
		{
			return _selectedIndexButtonBar;
		}

		public function set selectedIndexButtonBar(value:int):void
		{
			_selectedIndexButtonBar = value;
			dispatchEvent(new Event("selectedIndexButtonBarChange"));
		}

		[Bindable(event="enableButtonBarChange")]
		public function get enableButtonBar():Boolean
		{
			return _enableButtonBar;
		}

		public function set enableButtonBar(value:Boolean):void
		{
			if( _enableButtonBar !== value)
			{
				_enableButtonBar = value;
				dispatchEvent(new Event("enableButtonBarChange"));
			}
		}
	}
}

 

<think>我们正在解决一个具体的技术问题:在使用flex布局时,el-tour组件定位的SVG图出现偏差。需要修复这个问题。 分析问题: 1. 问题背景:在flex布局中,el-tour(可能是Element UI的Tour组件)定位的SVG位置不正确。 2. 可能原因:flex布局会影响子元素的定位,而el-tour组件的定位可能依赖于绝对定位,这可能会受到flex容器的影响。 3. 解决方案:需要调整el-tour组件的定位方式,或者调整其父容器的flex属性,以确保SVG定位正确。 解决思路: 1. 检查el-tour组件的定位方式:通常,类似引导组件的元素会使用绝对定位(position: absolute)或固定定位(position: fixed)。如果它被包含在一个flex容器内,则绝对定位的子元素会脱离文档流,但它的定位参考点(即包含块)是由最近的具有定位属性(非static)的祖先元素决定的。 2. 确保el-tour组件的定位参考点正确:如果el-tour组件的定位是absolute,那么需要确保其父容器(或某个祖先容器)设置了position: relative(或absolute、fixed等),并且这个祖先容器不是flex容器?实际上,flex容器如果设置了position: relative,那么它就可以作为绝对定位子元素的包含块。 3. 问题可能在于flex容器改变了布局,导致包含块的位置或尺寸发生变化,从而影响SVG的位置。 具体步骤: 1. 为el-tour组件的直接父元素设置position: relative,这样el-tour组件的绝对定位就会相对于这个父元素定位。 2. 检查flex布局是否影响了父元素的尺寸或位置,尤其是当flex容器内的项目有动态变化时,可能导致父元素尺寸变化,从而影响定位。 3. 调整el-tour组件的定位样式,可能需要覆盖默认样式,例如调整top、left等属性。 如果上述方法不行,还可以尝试: - 将el-tour组件放在flex容器的部,然后通过调整z-index和定位使其覆盖在正确的位置上。 - 使用CSS transform来调整SVG的位置,但要注意transform也会创建新的包含块。 由于问题描述中提到了SVG,可能el-tour组件内部使用了SVG来绘制引导框或箭头等。SVG的位置偏差可能是由于定位计算错误导致的。 参考已有的类似问题解决方案: 在Element UI的Tour组件(el-tour)中,定位是通过Popper.js或类似库实现的,它计算目标元素的位置并放置引导框。在flex布局中,目标元素的位置可能会因为flex布局的特性而发生变化,导致Popper计算的位置不准确。 解决方案: 1. 确保目标元素在flex布局中具有稳定的位置和尺寸。可以尝试给目标元素设置一个明确的宽度和高度,或者设置flex属性使其尺寸稳定。 2. 如果问题仍然存在,可以尝试在el-tour组件上使用:popper-options属性来调整Popper的配置,例如设置gpuAcceleration为false,或者手动调整偏移量。 例如: ```html <el-tour :popper-options="{ modifiers: [{ name: 'offset', options: { offset: [0, 10] } }] }"> ``` 或者,可以监听el-tour的打开事件,然后动态调整位置,但这种方法比较麻烦。 另,检查是否有CSS样式冲突,特别是flex容器内的项目可能受到对齐方式(justify-content, align-items)的影响,导致目标元素的位置与预期不同。 总结修复步骤: 1. 给el-tour组件的父元素(即目标元素的父元素)设置position: relative,确保定位基准正确。 2. 检查目标元素在flex布局中的尺寸和位置是否稳定,必要时设置固定尺寸。 3. 通过popper-options微调定位偏移量。 4. 如果以上方法无效,考虑将el-tour组件移出flex容器,使用绝对定位或固定定位到整个页面,然后通过计算目标元素的位置来定位(这种方法可能需要手动计算位置)。 由于没有具体的代码示例,以上是通用的解决方案。 生成相关问题: 1. 如何为el-tour组件设置定位的参考点? 2. 在flex布局中,如何避免子元素的绝对定位出现偏差? 3. Element UI的Tour组件有哪些常用的定位调整方法?</think>### 解决 flex 布局导致 el-tour 定位 SVG 位置错误的问题 #### 问题分析 当使用 flex 布局时,`el-tour` 组件的定位参考点可能发生变化,导致 SVG 位置偏移。根本原因是 **flex 容器会改变子元素的定位上下文**,而 `el-tour` 的定位依赖于绝对定位(position: absolute)。具体表现为: 1. **参考点偏移**:绝对定位元素默认相对于最近的非 static 定位祖先定位,flex 容器可能成为新参考点 2. **布局计算差异**:flex 的弹性计算与绝对定位的坐标计算存在冲突 3. **SVG 坐标系影响**:SVG 的 viewBox 坐标系与 flex 布局的百分比单位不兼容 --- #### 解决方案 ##### 方法 1:隔离定位上下文(推荐) ```html <div class="tour-container"> <!-- 新增隔离层 --> <el-tour v-model="visible"> <!-- 内容 --> </el-tour> </div> ``` ```css .tour-container { position: relative; /* 创建独立定位上下文 */ width: 0; /* 不干扰 flex 布局 */ height: 0; } ``` ##### 方法 2:调整 SVG 定位方式 ```css /* 覆盖 el-tour 默认样式 */ .el-tour__popper { position: fixed !important; /* 改为固定定位 */ transform: translate( calc(50% + var(--offset-x, 0)), calc(50% + var(--offset-y, 0)) ); /* 动态偏移 */ } ``` ```javascript // 在打开 tour 时计算偏移量 function adjustSVGPosition(targetElement) { const rect = targetElement.getBoundingClientRect(); document.documentElement.style.setProperty('--offset-x', `${rect.left}px`); document.documentElement.style.setProperty('--offset-y', `${rect.top}px`); } ``` ##### 方法 3:禁用 flex 影响 ```css /* 针对 flex 容器 */ .flex-container { position: static !important; /* 阻止成为定位参考 */ transform: none !important; /* 禁用变换影响 */ } ``` --- #### 关键注意事项 1. **层级关系**:确保 `el-tour` 的父级不是 flex 项目 2. **动态计算**:窗口大小变化时需重新计算位置 ```javascript window.addEventListener('resize', () => { if (tourVisible) readjustPosition(); }); ``` 3. **SVG 自适应**:添加 `preserveAspectRatio="xMidYMid meet"` 到 SVG 标签 4. **兼容性检查**:确保浏览器支持 CSS 变量(IE 需 polyfill) > 通过隔离定位上下文可解决 90% 的偏移问题,若仍有偏差可结合 `transform` 微调[^1]。实际案例表明 flex 容器中的绝对定位需要明确参考系才能精确定位[^2]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值