使用ClassHelper\record helper 给类“打补丁”

有些时候,我们需要在现有类的基础上,给类添加一些东西
并且在类中protected区分的方法,成员是无法在单元外访问的,如果要调用它们该怎么办呢?
于是就有了以下三种方法

1. 继承该类
2. 直接覆写该类
3. 使用Class Helper

对于第一种方法,大家都很熟悉,我就不多说了,第二种方法的实现:

TControl =class(Controls.TControl)
private
FText: WideString;
published
propertyText: WideStringreadFTextwriteFText;
end;


而第三种,就是我在本文中要说的,利用ClassHelper来实现对类的扩展,也可以称之为,给类“打补丁”

具体的操作方式如下,假设有一个很简单的类:
type
TTestClass =class
private
FInfo:string;
protected
procedureDoTest;
public
functionDoAdd(a,b: integer):string;
public
propertyInfo:string readFInfowriteFInfo;
end;

我现在想给它添加一个DoMinus方法,于是就有了这个Class Helper

type
TTestClassHelper =class helper forTTestClass
public
functionDoMinus(a,b: integer):string;
end;

可以看到,只是很简单的声明一个class helper,然后在里面添加内容。
这样做的好处是,原本的类代码不会被改动,只是扩展
在class helper内,可以访问到基类的protected, public, published区分符下的成员,属性和方法,但是不能访问private下的。

经过class helper的补丁后,我们就可以在基类的实例下调用添加的新方法了
var
t:TTestClass;
begin
t:=TTestClass.Create;
t.DoAdd(1,2);
t.DoMinus(2,1);
end;

这只是一个很小的例子,以前我曾经见过把class helper用得非常复杂的。它对于代码有着相当强的隐蔽性
例如:
type
TTestClass =class
protected
FA, FB, FC:string;
public
end;

type
TTestClassHelper =class helper forTTestClass
public
procedureDo1;
procedureDo2;
......
end;

这样的代码看着真能让人头疼,按着Ctrl点类名只能看到一个空的类,没有一个方法。
实现的代码全都放在了class helper里,并且class helper也不是写在pas里,是拆分后写在inc里了

总结
class helper对于简单的对类打补丁而言,是比较有用的,毕竟写一个class helper和继承一个类差不多,而且class helper不会对基类造成影响,也不用担心一个不小心就把基类的东西override错了
但是class helper并不适合用在较大规模的类复写中,特别是用class helper来实现半开源的产品,将非常不利于代码的维护(如果是商业手段则不在此列),此时就需要较为详尽的文档来标识class helper以及其所指的类。

当初Delphi 2007为了兼容D2006编译的单元,才想到了用Class Helper。不过Class Helper/Record Helper只适合作为”补丁“,一般不要放到常规的设计里面。


使用了Record Helper来扩展TGuid结构,很方便。

type
TGuidHelper = record helper for TGUID
private
class function GetEmpty: TGUID; static;
public
class function Create(const g: string): TGUID; overload; static;
class function NewGuid: TGUID; static;
function Equals(const guid: TGUID): Boolean;
function ToString: string;
class property Empty: TGUID read GetEmpty;
end;

http://blog.sina.com.cn/s/blog_6016bdc80100dhbh.html~type=v5_one&label=rela_prevarticle?1290186141

一、IDE 1、对IDE的高DPI支持,涵盖了最新的4k+显示器,整个字体和图标更干净、更清晰。 2、多显示器和多窗口改进:在多个窗口中同时设计和编辑同一个表单的代码 3、完全重建的欢迎页,具有原生的外观和感觉,以及适合IDE的UI,并可自定义布局和内容 4、C++代码格式化器。使用clang-format自动布局你的C++代码 5、改进了对VCL和IDE的远程桌面支持 6、FireMonkey设计时指南。通过可视化的线条和增强的边距和填充支持更快地进行原型设计 二、VCL 1、丰富的编辑组件更新删除了XP的依赖性,并为TRichEdit控件引入了新的功能。 2、VCL样式增加了设计时支持:通过在设计时立即看到你的样式表格和控件在运行时的样子,使时尚的UI原型更快。 3、增加了对TreeViews中CheckBoxes的支持,每个节点都支持3种状态(部分、变暗、排除)以帮助定制UI。 4、新的TDBLabeledEdit组件提供了TLabelEdit的数据感知版本,以加快原型设计。 5、大量的VCL改进,包括默认的表格大小和字体,例外对话框的复制按钮,备忘录和RichEdit的边距等等。 三、FMX 1、改进了对Windows和桌面的FMX高DPI支持,桌面UI明显优越 2、在WebBrowser组件中支持微软的WebView 2控件(Edge Chromium)。 3、支持最新的Android 30 API和最新的计费API,并迁移到使用AndroidX库 4、Android支持多个classes.dex文件,简化了对外部Android依赖的整合 四、Platforms 1、Delphi macOS 64位ARM编译器和工具链,包括为Intel/Arm AppStore提交构建通用二进制文件 2、Delphi语言支持二进制小数和数字分离器 3、改进了C++工具链,对C++型的RTTI进行了大修,包括在Delphi型上使用typeid。 4、为Delphi型改进了C++风格的RTTI 5、在Win32和Win64上改进了CMake的质量并大大改进了异常处理。 五、RTL 1、RTL质量重点。TZipFile、大数据结构的64位改进、蓝牙LE 2、System.DateUtils中TDateTime的记录帮助器 3、System.SysUtils中TCurrency的Record Helper使货币的工作更加简单和容易。 4、C++ RTL的改进包括在Delphi使用make_shared和make_unique,对Delphi字符串的string_view支持,以及Delphi/C++字符串的简单转换。 六、LSP 1、使用Visual Studio Code来编辑Delphi源代码,并具有完整的代码完成功能 2、LSP对Include文件的认识 3、自动重新启动LSP服务器 4、用Tab键自动完成代码 5、支持辅助工具 6、赋值数组时的数组建议 七、Data 1、新版本中的FireDAC为PostgreSQL、Oracle和Firebird数据库提供了具体的改进。 2、HTTP和REST客户端库已经扩展了超时机制,支持HTTP/2、TLS 1.3、Base64 URL编码。 3、新组件TRESTRequestDataSetAdapter简化了向服务器上传数据集的过程 4、新的低流量RAD Server Lite允许无限制地部署您的多层解决方案,以及完全可扩展的Server引擎。 5、对于DataSnap,REST URL映射逻辑现在是完全可配置的
gapinyc@DESKTOP-9QS7RL5:~/superset-prod/superset-5.0.0/superset-frontend$ npm run build > superset@5.0.0 build > cross-env NODE_OPTIONS=--max_old_space_size=8192 NODE_ENV=production BABEL_ENV="${BABEL_ENV:=production}" webpack --color --mode production [Superset Plugin] Use symlink source for @superset-ui/chart-controls @ ./packages/superset-ui-chart-controls [Superset Plugin] Use symlink source for @superset-ui/core @ ./packages/superset-ui-core [Superset Plugin] Use symlink source for @superset-ui/legacy-plugin-chart-calendar @ ./plugins/legacy-plugin-chart-calendar [Superset Plugin] Use symlink source for @superset-ui/legacy-plugin-chart-chord @ ./plugins/legacy-plugin-chart-chord [Superset Plugin] Use symlink source for @superset-ui/legacy-plugin-chart-country-map @ ./plugins/legacy-plugin-chart-country-map [Superset Plugin] Use symlink source for @superset-ui/legacy-plugin-chart-horizon @ ./plugins/legacy-plugin-chart-horizon [Superset Plugin] Use symlink source for @superset-ui/legacy-plugin-chart-map-box @ ./plugins/legacy-plugin-chart-map-box [Superset Plugin] Use symlink source for @superset-ui/legacy-plugin-chart-paired-t-test @ ./plugins/legacy-plugin-chart-paired-t-test [Superset Plugin] Use symlink source for @superset-ui/legacy-plugin-chart-parallel-coordinates @ ./plugins/legacy-plugin-chart-parallel-coordinates [Superset Plugin] Use symlink source for @superset-ui/legacy-plugin-chart-partition @ ./plugins/legacy-plugin-chart-partition [Superset Plugin] Use symlink source for @superset-ui/legacy-plugin-chart-rose @ ./plugins/legacy-plugin-chart-rose [Superset Plugin] Use symlink source for @superset-ui/legacy-plugin-chart-world-map @ ./plugins/legacy-plugin-chart-world-map [Superset Plugin] Use symlink source for @superset-ui/legacy-preset-chart-deckgl @ ./plugins/legacy-preset-chart-deckgl [Superset Plugin] Use symlink source for @superset-ui/legacy-preset-chart-nvd3 @ ./plugins/legacy-preset-chart-nvd3 [Superset Plugin] Use symlink source for @superset-ui/plugin-chart-cartodiagram @ ./plugins/plugin-chart-cartodiagram [Superset Plugin] Use symlink source for @superset-ui/plugin-chart-echarts @ ./plugins/plugin-chart-echarts [Superset Plugin] Use symlink source for @superset-ui/plugin-chart-handlebars @ ./plugins/plugin-chart-handlebars [Superset Plugin] Use symlink source for @superset-ui/plugin-chart-pivot-table @ ./plugins/plugin-chart-pivot-table [Superset Plugin] Use symlink source for @superset-ui/plugin-chart-table @ ./plugins/plugin-chart-table [Superset Plugin] Use symlink source for @superset-ui/plugin-chart-word-cloud @ ./plugins/plugin-chart-word-cloud [Superset Plugin] Use symlink source for @superset-ui/switchboard @ ./packages/superset-ui-switchboard 20% building 1/8 entries 7485/7517 dependencies 696/3486 modules`isModuleDeclaration` has been deprecated, please migrate to `isImportOrExportDeclaration` at isModuleDeclaration (/home/gapinyc/superset-prod/superset-5.0.0/superset-frontend/node_modules/@babel/types/lib/validators/generated/index.js:2793:35) at PluginPass.Program (/home/gapinyc/superset-prod/superset-5.0.0/superset-frontend/node_modules/babel-plugin-lodash/lib/index.js:102:44) 1146 assets 12873 modules WARNING in ./src/components/Tooltip/index.tsx 22:0-42 export 'TooltipPlacement' (reexported as 'TooltipPlacement') was not found in 'antd-v5/lib/tooltip' (possible exports: __esModule, default) WARNING in ./src/components/Tooltip/index.tsx 22:0-42 export 'TooltipProps' (reexported as 'TooltipProps') was not found in 'antd-v5/lib/tooltip' (possible exports: __esModule, default) ERROR in ./plugins/plugin-chart-handlebars/node_modules/just-handlebars-helpers/lib/helpers/formatters.js 27:24-55 Module not found: Error: Can't resolve 'currencyformatter.js' in '/home/gapinyc/superset-prod/superset-5.0.0/superset-frontend/plugins/plugin-chart-handlebars/node_modules/just-handlebars-helpers/lib/helpers' ERROR in ./node_modules/@luma.gl/webgl/dist/adapter/webgl-adapter.js 65:38-62 Module not found: Error: Can't resolve './webgl-device' in '/home/gapinyc/superset-prod/superset-5.0.0/superset-frontend/node_modules/@luma.gl/webgl/dist/adapter' Did you mean 'webgl-device.js'? BREAKING CHANGE: The request './webgl-device' failed to resolve only because it was resolved as fully specified (probably because the origin is strict EcmaScript Module, e. g. a module with javascript mimetype, a '*.mjs' file, or a '*.js' file where the package.json contains '"type": "module"'). The extension in the request is mandatory for it to be fully specified. Add the extension to the request. ERROR in ./node_modules/@deck.gl/core/src/controllers/controller.ts:21:24 TS7006: Parameter 't' implicitly has an 'any' type. 19 | 20 | const DEFAULT_INERTIA = 300; > 21 | const INERTIA_EASING = t => 1 - (1 - t) * (1 - t); | ^ 22 | 23 | const EVENT_TYPES = { 24 | WHEEL: ['wheel'], ERROR in ./node_modules/@deck.gl/core/src/controllers/controller.ts:114:3 TS2578: Unused '@ts-expect-error' directive. 112 | abstract get transition(): TransitionProps; 113 | > 114 | // @ts-expect-error (2564) - not assigned in the constructor | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 115 | protected props: ControllerProps; 116 | protected state: Record<string, any> = {}; 117 | ERROR in ./node_modules/@deck.gl/core/src/controllers/controller.ts:173:7 TS7032: Property 'events' implicitly has type 'any', because its set accessor lacks a parameter type annotation. 171 | } 172 | > 173 | set events(customEvents) { | ^^^^^^ 174 | this.toggleEvents(this._customEvents, false); 175 | this.toggleEvents(customEvents, true); 176 | this._customEvents = customEvents; ERROR in ./node_modules/@deck.gl/core/src/controllers/controller.ts:173:14 TS7006: Parameter 'customEvents' implicitly has an 'any' type. 171 | } 172 | > 173 | set events(customEvents) { | ^^^^^^^^^^^^ 174 | this.toggleEvents(this._customEvents, false); 175 | this.toggleEvents(customEvents, true); 176 | this._customEvents = customEvents; ERROR in ./node_modules/@deck.gl/core/src/controllers/controller.ts:338:16 TS7006: Parameter 'eventNames' implicitly has an 'any' type. 336 | } 337 | > 338 | toggleEvents(eventNames, enabled) { | ^^^^^^^^^^ 339 | if (this.eventManager) { 340 | eventNames.forEach(eventName => { 341 | if (this._events[eventName] !== enabled) { ERROR in ./node_modules/@deck.gl/core/src/controllers/controller.ts:338:28 TS7006: Parameter 'enabled' implicitly has an 'any' type. 336 | } 337 | > 338 | toggleEvents(eventNames, enabled) { | ^^^^^^^ 339 | if (this.eventManager) { 340 | eventNames.forEach(eventName => { 341 | if (this._events[eventName] !== enabled) { ERROR in ./node_modules/@deck.gl/core/src/controllers/controller.ts:340:26 TS7006: Parameter 'eventName' implicitly has an 'any' type. 338 | toggleEvents(eventNames, enabled) { 339 | if (this.eventManager) { > 340 | eventNames.forEach(eventName => { | ^^^^^^^^^ 341 | if (this._events[eventName] !== enabled) { 342 | this._events[eventName] = enabled; 343 | if (enabled) { ERROR in ./node_modules/@deck.gl/core/src/controllers/controller.ts:484:29 TS7006: Parameter 'event' implicitly has an 'any' type. 482 | } 483 | > 484 | protected _onPanRotateEnd(event): boolean { | ^^^^^ 485 | const {inertia} = this; 486 | if (this.dragRotate && inertia && event.velocity) { 487 | const pos = this.getCenter(event); ERROR in ./node_modules/@deck.gl/core/src/controllers/map-controller.ts:406:19 TS7006: Parameter 'scale' implicitly has an 'any' type. 404 | /* Private methods */ 405 | > 406 | _zoomFromCenter(scale) { | ^^^^^ 407 | const {width, height} = this.getViewportProps(); 408 | return this.zoom({ 409 | pos: [width / 2, height / 2], ERROR in ./node_modules/@deck.gl/core/src/controllers/map-controller.ts:414:18 TS7006: Parameter 'offset' implicitly has an 'any' type. 412 | } 413 | > 414 | _panFromCenter(offset) { | ^^^^^^ 415 | const {width, height} = this.getViewportProps(); 416 | return this.pan({ 417 | startPos: [width / 2, height / 2], ERROR in ./node_modules/@deck.gl/core/src/controllers/map-controller.ts:422:20 TS7006: Parameter 'newProps' implicitly has an 'any' type. 420 | } 421 | > 422 | _getUpdatedState(newProps): MapState { | ^^^^^^^^ 423 | // @ts-ignore 424 | return new this.constructor({ 425 | makeViewport: this.makeViewport, ERROR in ./node_modules/@deck.gl/core/src/controllers/transition-manager.ts:40:24 TS7006: Parameter 't' implicitly has an 'any' type. 38 | }; 39 | > 40 | const DEFAULT_EASING = t => t; | ^ 41 | const DEFAULT_INTERRUPTION = TRANSITION_EVENTS.BREAK; 42 | 43 | type TransitionSettings = BaseTransitionSettings & { ERROR in ./node_modules/@deck.gl/core/src/controllers/transition-manager.ts:209:12 TS7006: Parameter 'transition' implicitly has an 'any' type. 207 | 208 | _onTransitionEnd(callback?: (transition: Transition) => void) { > 209 | return transition => { | ^^^^^^^^^^ 210 | this.propsInTransition = null; 211 | 212 | this.onStateChange({ ERROR in ./node_modules/@deck.gl/core/src/controllers/transition-manager.ts:223:25 TS7006: Parameter 'transition' implicitly has an 'any' type. 221 | } 222 | > 223 | _onTransitionUpdate = transition => { | ^^^^^^^^^^ 224 | // NOTE: Be cautious re-ordering statements in this function. 225 | const { 226 | time, ERROR in ./node_modules/@deck.gl/core/src/lib/view-manager.ts:161:7 TS7053: Element implicitly has an 'any' type because expression of type 'string' can't be used to index type '{}'. No index signature with a parameter of type 'string' was found on type '{}'. 159 | const viewMap = {}; 160 | this.views.forEach(view => { > 161 | viewMap[view.id] = view; | ^^^^^^^^^^^^^^^^ 162 | }); 163 | return viewMap; 164 | } ERROR in ./node_modules/@deck.gl/core/src/lib/view-manager.ts:180:32 TS7053: Element implicitly has an 'any' type because expression of type 'string' can't be used to index type 'ViewStateObject<ViewsT>'. No index signature with a parameter of type 'string' was found on type 'ViewStateObject<ViewsT>'. 178 | typeof viewOrViewId === 'string' ? this.getView(viewOrViewId) : viewOrViewId; 179 | // Backward compatibility: view state for single view > 180 | const viewState = (view && this.viewState[view.getViewStateId()]) || this.viewState; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 181 | return view ? view.filterViewState(viewState) : viewState; 182 | } 183 | ERROR in ./node_modules/@deck.gl/core/src/lib/view-manager.ts:269:5 TS2322: Type 'unknown[]' is not assignable to type 'View<TransitionProps, CommonViewProps<TransitionProps>>[]'. Type 'unknown' is not assignable to type 'View<TransitionProps, CommonViewProps<TransitionProps>>'. 267 | // Does not actually rebuild the `Viewport`s until `getViewports` is called 268 | private _setViews(views: View[]): void { > 269 | views = flatten(views, Boolean); | ^^^^^ 270 | 271 | const viewsChanged = this._diffViews(views, this.views); 272 | if (viewsChanged) { ERROR in ./node_modules/@deck.gl/core/src/lib/view-manager.ts:306:21 TS7006: Parameter 'viewState' implicitly has an 'any' type. 304 | onViewStateChange: this._eventCallbacks.onViewStateChange, 305 | onStateChange: this._eventCallbacks.onInteractionStateChange, > 306 | makeViewport: viewState => | ^^^^^^^^^ 307 | this.getView(view.id)?.makeViewport({ 308 | viewState, 309 | width: this.width, ERROR in ./node_modules/@deck.gl/core/src/transitions/linear-interpolator.ts:100:7 TS7053: Element implicitly has an 'any' type because expression of type 'string' can't be used to index type '{}'. No index signature with a parameter of type 'string' was found on type '{}'. 98 | const propsInTransition = {}; 99 | for (const key of this._propsToExtract) { > 100 | propsInTransition[key] = lerp(startProps[key] || 0, endProps[key] || 0, t); | ^^^^^^^^^^^^^^^^^^^^^^ 101 | } 102 | 103 | if (endProps.aroundPosition && this.opts.makeViewport) { ERROR in ./node_modules/@deck.gl/core/src/transitions/transition-interpolator.ts:66:9 TS7053: Element implicitly has an 'any' type because expression of type 'string' can't be used to index type '{}'. No index signature with a parameter of type 'string' was found on type '{}'. 64 | for (const key of this._propsToExtract) { 65 | if (key in startProps || key in endProps) { > 66 | startViewStateProps[key] = startProps[key]; | ^^^^^^^^^^^^^^^^^^^^^^^^ 67 | endViewStateProps[key] = endProps[key]; 68 | } 69 | } ERROR in ./node_modules/@deck.gl/core/src/transitions/transition-interpolator.ts:67:9 TS7053: Element implicitly has an 'any' type because expression of type 'string' can't be used to index type '{}'. No index signature with a parameter of type 'string' was found on type '{}'. 65 | if (key in startProps || key in endProps) { 66 | startViewStateProps[key] = startProps[key]; > 67 | endViewStateProps[key] = endProps[key]; | ^^^^^^^^^^^^^^^^^^^^^^ 68 | } 69 | } 70 | ERROR in ./node_modules/@deck.gl/core/src/transitions/transition-interpolator.ts:100:23 TS7006: Parameter 'props' implicitly has an 'any' type. 98 | } 99 | > 100 | _checkRequiredProps(props) { | ^^^^^ 101 | if (!this._requiredProps) { 102 | return; 103 | } ERROR in ./node_modules/@deck.gl/core/src/types/types.ts:10:8 TS7019: Rest parameter 'args' implicitly has an 'any[]' type. 8 | 9 | export interface ConstructorOf<T> { > 10 | new (...args): T; | ^^^^^^^ 11 | } 12 | ERROR in ./node_modules/@deck.gl/core/src/utils/flatten.ts:44:28 TS7031: Binding element 'target' implicitly has an 'any' type. 42 | 43 | /** Uses copyWithin to significantly speed up typed array value filling */ > 44 | export function fillArray({target, source, start = 0, count = 1}) { | ^^^^^^ 45 | const length = source.length; 46 | const total = count * length; 47 | let copied = 0; ERROR in ./node_modules/@deck.gl/core/src/utils/flatten.ts:44:36 TS7031: Binding element 'source' implicitly has an 'any' type. 42 | 43 | /** Uses copyWithin to significantly speed up typed array value filling */ > 44 | export function fillArray({target, source, start = 0, count = 1}) { | ^^^^^^ 45 | const length = source.length; 46 | const total = count * length; 47 | let copied = 0; ERROR in ./node_modules/@deck.gl/core/src/utils/math-utils.ts:100:5 TS7034: Variable 'scratchArray' implicitly has type 'any' in some locations where its type cannot be determined. 98 | } 99 | > 100 | let scratchArray; | ^^^^^^^^^^^^ 101 | 102 | /** 103 | * Split a Float64Array into a double-length Float32Array ERROR in ./node_modules/@deck.gl/core/src/utils/math-utils.ts:121:45 TS7005: Variable 'scratchArray' implicitly has an 'any' type. 119 | 120 | const count = (endIndex - startIndex) / size; > 121 | scratchArray = typedArrayManager.allocate(scratchArray, count, { | ^^^^^^^^^^^^ 122 | type: Float32Array, 123 | size: size * 2 124 | }); ERROR in ./node_modules/@deck.gl/core/src/views/view.ts:155:11 TS7053: Element implicitly has an 'any' type because expression of type 'string' can't be used to index type 'TransitionProps'. No index signature with a parameter of type 'string' was found on type 'TransitionProps'. 153 | for (const key in this.props.viewState) { 154 | if (key !== 'id') { > 155 | newViewState[key] = this.props.viewState[key]; | ^^^^^^^^^^^^^^^^^ 156 | } 157 | } 158 | return newViewState; ERROR in ./node_modules/@deck.gl/core/src/views/view.ts:155:31 TS7053: Element implicitly has an 'any' type because expression of type 'string' can't be used to index type '{ id?: string | undefined; } & Partial<ViewState>'. No index signature with a parameter of type 'string' was found on type '{ id?: string | undefined; } & Partial<ViewState>'. 153 | for (const key in this.props.viewState) { 154 | if (key !== 'id') { > 155 | newViewState[key] = this.props.viewState[key]; | ^^^^^^^^^^^^^^^^^^^^^^^^^ 156 | } 157 | } 158 | return newViewState; webpack 5.102.1 compiled with 31 errors and 2 warnings in 40499 ms
最新发布
10-24
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值