[RxJS] Get current value out of Subject (BehaviorSubject)

本文介绍了如何使用RxJS中的BehaviorSubject来获取最新值或错误。通过示例代码展示了创建BehaviorSubject实例的方法,如何调用getValue()获取当前值,完成操作后的值冻结及对象释放后的异常捕获。

When you want to get the current value of a subject, you need to switch BehaviorSubject, it always emit the latest value or throw error.

Then you can call 'getValue()':

var subject = new Rx.BehaviorSubject(56);

console.log('Value is: ' + subject.getValue());

// => Value is: 56

subject.onNext(42);

console.log('Value is: ' + subject.getValue());

// => Value is: 42

subject.onCompleted();

subject.onNext(100);

console.log('Value is frozen: ' + subject.getValue());

// => Value is frozen: 42

subject.dispose();

try {
    subject.getValue();
} catch (e) {
    console.log(e.message);
}

// => Object has been disposed

 

import { Component, OnInit, ViewChild } from '@angular/core'; import { FormBuilder } from '@angular/forms'; import { StateService } from '@uirouter/core'; import { BehaviorSubject, Observable, Subject, delay, map, of } from 'rxjs'; import { MATCH_DISABLED, MATCH_HIDDEN, MATCH_VALIDATOR_ADD, TI_GRID_KIT_ROW_ACTION_TYPE, TiDynamicFormComponent, TiDynamicFormGroupModel, TiDynamicGridModel, TiDynamicInputModel, TiDynamicSelectModel, TiGridKitEditEvents, } from '@cloud/ti-dynamic-form'; import { TiActionmenuItem, TiMessageService, TiModalRef, TiModalService, TiTableColumns, TiTableRowData, TiTableSrcData, TiValidators, } from '@cloud/tiny3'; import { TiRenderType, gridConstants } from '@cloud/tinycloud'; import { InstanceStatus } from 'src/ng2app/common/common.constant'; import { CloudBIService } from 'src/ng2app/common/service/cloudBI.service'; import { DMSCommonService } from 'src/ng2app/common/service/common.service'; import { DMSGridKitService } from 'src/ng2app/common/service/dms-grid-kit.service'; import { I18nService } from 'src/ng2app/common/service/i18n.service'; import { MessageService } from 'src/ng2app/common/service/message.service'; import { IInstanceDetail } from 'src/ng2app/instance-detail/instance-detail.interface'; import { TaskStatus } from 'src/ng2app/task-query/task-query.constant'; import { SupportFeature } from '../../instance-detail/instance-detail.constant'; import { BatchEditModalComponent } from '../batch-edit-modal/batch-edit-modal.component'; import { ConfigType, ConfigValueType, FormValidation, SupportFeatures } from '../instance-config.constant'; import { IEditConfig, IInstanceConfig } from '../instance-config.model'; import { KafkaConfigManagerService } from './kafka-config-manager.service'; @Component({ selector: 'kafka-config-manager', templateUrl: 'kafka-config-manager.component.html', }) export class KafkaConfigManagerComponent implements OnInit { @ViewChild('tiDynamicFormComponent') tiDynamicFormComponent: TiDynamicFormComponent; model = [ new TiDynamicGridModel({ ...((isSingle = false) => { const id = isSingle ? 'grid-with-batch-edit-single' : 'grid-with-batch-edit-global'; return { id, // 动态表单开启编辑能力 supportEdit: true, gridKitOption: { enableColsResponsive: true, rowSelection: 'multiple', colDefs: ((singleEdit = true) => [ { headerName: '名称', field: 'name', // 可通过该字段关闭该列修改能力。 supportEdit: false, }, { headerName: '策略', field: 'protocol', cellRenderer: TiRenderType.TextWithTipRenderer, }, { headerName: '类型', field: 'type', }, { headerName: '后端协议', field: 'protocolPort', valueGetter: params => `${params.data.protocolPort.protocol}: ${params.data.protocolPort.sessionTime || '80'}`, }, { headerName: '操作', field: gridConstants.OPERATION_FIELD, cellRenderer: TiRenderType.OperationRenderer, cellRendererParams: params => { const gridDataLimit = new BehaviorSubject({ min: 1, }).getValue(); const deleteBtn = { label: '删除', key: 'op_delete', actionCallback: obj => { new Subject().next({ action: TI_GRID_KIT_ROW_ACTION_TYPE.REMOVE, // 推荐使用data传递删除数据,兼容之前的rowIndex逻辑 // rowIndex: obj.rowIndex data: obj.data, }); }, }; // 如果是单行修改,其他行的修改按钮就需要禁用 const hasEditRows = !!new BehaviorSubject([]).getValue().length; const editDisabledTip = hasEditRows ? '请先保存或者取消处于修改状态的行' : ''; if (singleEdit) { ({ label: '修改策略', key: 'op-edit', actionCallback: obj => { new Subject().next({ // 当前修改的行数据 rowDatas: [obj.data], // 启动修改态 actionType: TiGridKitEditEvents.EDITING, callback: ({ editRows }) => { new BehaviorSubject([]).next(editRows); (editRows => {})(editRows); }, }); }, }).disabled = hasEditRows; ({ label: '修改策略', key: 'op-edit', actionCallback: obj => { new Subject().next({ // 当前修改的行数据 rowDatas: [obj.data], // 启动修改态 actionType: TiGridKitEditEvents.EDITING, callback: ({ editRows }) => { new BehaviorSubject([]).next(editRows); (editRows => {})(editRows); }, }); }, }).tipStr = editDisabledTip; } deleteBtn.disabled = hasEditRows; deleteBtn.tipStr = editDisabledTip; // 如果当前只有一行,删除按钮设置成禁用; if (params.context.parentArray.length <= (gridDataLimit?.min || 0)) { deleteBtn.disabled = true; deleteBtn.tipStr = '只有一条记录,不能删除。'; } return { ...params, operationList: params.data.isCurrentEdit ? [ { label: '保存', key: 'op-edit-cancel', actionCallback: obj => { new Subject().next({ // 当前修改的行数据 rowDatas: [obj.data], // 确定修改 actionType: TiGridKitEditEvents.CONFIRM_EDIT, // 如果在保存时需要在校验通过后先向后台请求保存,可以使用该接口。根据其返回的布尔值决定后续是否进行前台保存且 // 变成非编辑态(true: 进行后续;false或报错:不进行后续,停留在编辑态)。 // 13.0.120 版本新增 serverSideSaveFn: ({ editedRowDatas }) => (editedRowDatas => { console.log('编辑后的行数据集合:', editedRowDatas); // return throwError('This is an error!'); // 测试报错的场景 return of(editedRowDatas).pipe( delay(800), map(() => true) ); })(editedRowDatas), callback: ({ editRows }) => { new BehaviorSubject([]).next(editRows); (editRows => {})(editRows); }, }); }, }, { label: '取消', key: 'ti-grid-kit-op-edit-cancel', actionCallback: obj => { new Subject().next({ // 当前修改的行数据 rowDatas: [obj.data], // 取消修改 actionType: TiGridKitEditEvents.CANCEL_EDIT, callback: ({ editRows }) => { new BehaviorSubject([]).next(editRows); (editRows => {})(editRows); }, }); }, }, ] : [ { label: '修改策略', key: 'op-edit', actionCallback: obj => { new Subject().next({ // 当前修改的行数据 rowDatas: [obj.data], // 启动修改态 actionType: TiGridKitEditEvents.EDITING, callback: ({ editRows }) => { new BehaviorSubject([]).next(editRows); (editRows => {})(editRows); }, }); }, }, deleteBtn, ], }; }, flex: 1, }, ])(false), srcData: ((length = 2) => { const srcData = []; for (let i = 0; i < length; i++) { srcData.push({ name: `名称-${i}`, protocol: `容许-${i}`, type: `${i % 2 ? 'IPV4' : 'IPV6'}`, protocolPort: { sessionTime: '80', protocol: 'HTTP', }, }); } return srcData; })(1), // displayMode: 'ti-grid' }, // 编辑态使用 groupFactory: () => [ new TiDynamicInputModel({ id: 'protocol', value: '容许', validators: { required: null, }, }), new TiDynamicSelectModel({ id: 'type', value: 'IPV4', validators: { required: null, }, options: [ { label: 'IPV4', value: 'IPV4', }, { label: 'IPV6', value: 'IPV6', }, ], relations: [ { match: MATCH_DISABLED, when: [ { id: 'protocol', value: '', }, ], }, ], }), // 协议端口 new TiDynamicFormGroupModel({ id: 'protocolPort', group: [ new TiDynamicSelectModel({ id: 'protocol', options: [ { value: 'HTTP', label: 'HTTP', }, { value: 'TCP', label: 'TCP', }, ], defaultValueIndex: 0, }), new TiDynamicInputModel({ id: 'sessionTime', placeholder: '请输入保持会话时间', descriptions: 'HTTP保持会话时间区域是1-1440,TCP保持会话的时间是1-60', // 根据表单值动态添加的校验器 dynamicValidators: { // 如果relation匹配成功,使用该校验规则 match: { rangeValue: { validator: TiValidators.rangeValue(1, 1440), }, }, // 如果relation匹配不成功,使用该校验规则 notMatch: { rangeValue: { validator: TiValidators.rangeValue(1, 60), }, }, }, // 校验规则,始终需要的校验规则 validators: { // 设置为null,会调用TiValidators.required进行校验; required: null, }, // 报错信息配置,可选项 errorMessages: { rangeValue: '当前协议的会话时间范围是:{0} - {1}', }, relations: [ { // 如果匹配成功,动态添加相关校验规则 match: MATCH_VALIDATOR_ADD, // 当是layer是L7的后端协议,启用相关校验规则 when: [ { id: 'protocol', value: 'HTTP', }, ], }, { match: MATCH_HIDDEN, when: [ { value: 'IPV4', /** * 13.0.180 版本新增,用在formarray和dynamic-grid组件中和本行的formGroup其他control建立关联关系 * 解决之前通过rootPath(id.index.id)中行新增删除时index未同步变化时(groupFactory只能在初始化时调用一次)引起的关联关系不正确的问题 * * 注意:如果存在formarray里面嵌套formarry类,可能会存在查找到的行group不准确的场景 * 查找逻辑:当前control往上递归,如果其父元素是个formArray,则认为该group为要找的行group */ currentRowPath: 'type', }, ], }, ], }), ], }), ], // 行数据限制 limit: new BehaviorSubject({ min: 1, }), initialCount: 5, // 配置触发表格刷新和添加删除事件的流: 组件内部会监听这个流去触发对应的行为。 actionEvents: { // 行操作流 rowAction: new Subject(), // 编辑态流 editAction: new Subject(), // 刷新单元格流 refreshCells: new Subject(), }, }; })(), }), ]; dynamicDisplayed: Array<TiTableRowData> = []; staticDisplayed: Array<TiTableRowData> = []; enableEditStatic = false; columns: Array<TiTableColumns> = [ { label: this.i18n.get('common_config_name'), width: '32%', show: true, }, { label: this.i18n.get('common_config_range'), width: '17%', show: true, }, { label: this.i18n.get('common_config_default'), width: '17%', show: true, }, { label: this.i18n.get('common_config_running'), width: '17%', show: true, }, { label: this.i18n.get('common_config_option'), width: '17%', show: true, }, ]; dynamicSrcData: TiTableSrcData; staticSrcData: TiTableSrcData; staticItems: Array<TiActionmenuItem> = [ { label: this.i18n.get('common_term_edit_label'), }, ]; dynamicItems: Array<TiActionmenuItem> = [ { label: this.i18n.get('common_term_edit_label'), }, ]; editingItems: Array<TiActionmenuItem> = [ { label: this.i18n.get('common_term_save_btn'), }, { label: this.i18n.get('common_term_cancel_button'), }, { label: this.i18n.get('common_config_rest'), }, ]; restItems: Array<TiActionmenuItem> = [ { label: this.i18n.get('common_config_rest'), }, ]; levelOptions = [{ label: 'true' }, { label: 'false' }]; editingRow: TiTableRowData; isTaskChanging = false; isInstanceRunning = false; hasConfig = false; isLoading = true; isReadOnly = true; isInstanceErrorCode = false; roles: Array<string>; taskJobId: string; tipMessage: string; instanceName: string; showStaticDefaultBtn = false; isConfigEditing = { dynamic: false, static: false }; private instanceId: string; private instanceStatusInterval = null; private taskStatusInterval = null; private finishSetConfig: Subject<boolean> = new Subject<boolean>(); private supportFeatures = ''; private instanceDetailData: IInstanceDetail; customEvent = customEventObj => { if (customEventObj?.type === 'gridRowSelected') { } }; constructor( private fb: FormBuilder, private stateService: StateService, private tiMessage: TiMessageService, private tiModal: TiModalService, private cloudBIService: CloudBIService, private dmsGridKitService: DMSGridKitService, public i18n: I18nService, private messageService: MessageService, private kafkaConfigManagerService: KafkaConfigManagerService ) {} ngOnInit() { this.instanceId = this.stateService.params.instanceId; this.handleInstance(this.instanceId); } ngAfterViewInit() { DMSCommonService.getUserRoles$().subscribe(roles => { this.roles = roles; this.isReadOnly = DMSCommonService.isReadOnly(roles); if (this.isReadOnly) { this.columns[4].show = false; } else { this.columns[4].show = true; } }); } ngOnDestroy(): void { this.closeInterval(); } onBatchEditClick(configType: string): void { this.isConfigEditing[configType] = true; } onSaveClick(configType: string) { let srcData: Array<TiTableRowData> = []; srcData = configType === ConfigType.DYNAMIC ? this.dynamicSrcData.data : this.staticSrcData.data; this.isConfigEditing[configType] = false; const changedConfigs = this.kafkaConfigManagerService.fetchEditAllConfig(srcData); // Open the modal if user has changed some configs if (changedConfigs.length) { this.openModal(changedConfigs, configType); } } openModal(changedConfigs: IEditConfig[], configType: string): void { this.tiModal.open(BatchEditModalComponent, { id: 'batchEditModal', context: { config: changedConfigs, configType }, beforeClose: (modalRef: TiModalRef, reason: Boolean): void => { if (reason) { this.handleSetConfigs(changedConfigs); this.statusCheckToDestroyModal(modalRef); } else { modalRef.destroy(true); } }, close: (modalRef: TiModalRef): void => {}, dismiss: (modalRef: TiModalRef): void => {}, }); } handleSetConfigs(changedConfigs: IEditConfig[]): void { const requestConfigs = this.kafkaConfigManagerService.getRequestConfigs(changedConfigs); this.setConfigs(requestConfigs); } statusCheckToDestroyModal(modalRef: TiModalRef) { this.configSetFinished().subscribe(() => { modalRef.destroy(true); }); } onCancelClick(configType: string): void { this.isConfigEditing[configType] = false; const srcData = configType === ConfigType.DYNAMIC ? this.dynamicSrcData.data : this.staticSrcData.data; srcData.forEach(item => { if (item.type === ConfigValueType.enum) { item.currentValue = item.originValue; } }); } handleConfigs(instanceId: string): void { this.isLoading = true; this.kafkaConfigManagerService.configsRequest$(instanceId).subscribe( res => { this.isLoading = false; // RTTI this.cloudBIService.onRTTIEvent(); this.hasConfig = true; // 支持 "开启创建kafka消费组功能" const isSupportKafkaCreateConsumerGroup = this.supportFeatures?.includes( SupportFeature.kafkaCreateConsumerGroupEnable ); const srcData = res?.kafka_configs?.filter( item => isSupportKafkaCreateConsumerGroup || item.name !== SupportFeature.kafkaCreateConsumerGroupEnable ); const fetchedData = this.kafkaConfigManagerService.fetchSrcData(this.fb, srcData); this.dynamicSrcData = this.kafkaConfigManagerService.getDynamicSrcData(fetchedData); this.staticSrcData = this.kafkaConfigManagerService.getStaticSrcData(fetchedData); }, err => { this.hasConfig = false; this.isLoading = false; } ); } onReloadConfig(): void { this.handleConfigs(this.instanceId); } setConfigs(instanceConfigs: IInstanceConfig[]): void { this.kafkaConfigManagerService.setConfigRequest$(this.instanceId, instanceConfigs).subscribe( res => { this.taskJobId = res.job_id; this.finishSetConfig.next(true); this.messageService.showSuccessMessage( this.i18n.get('common_config_save_success').replace('{0}', this.instanceName) ); setTimeout(() => { if (this.taskJobId) { this.getTaskStatus(this.instanceId, this.taskJobId); } else { this.handleInstance(this.instanceId); } }, 800); }, err => { this.finishSetConfig.next(true); this.messageService.showException(err); } ); } onSelect(item: TiActionmenuItem, row: TiTableRowData): void { this.editingRow = { ...row }; this.editableRows(false, this.i18n.get('common_config_save_alert')); } onSelectEditing(item: TiActionmenuItem, row: TiTableRowData, configType: string): void { const isSave: boolean = item.label === this.i18n.get('common_term_save_btn'); const isReset: boolean = item.label === this.i18n.get('common_config_rest'); let isConfigChanged = true; isConfigChanged = this.editingRow.currentValue !== row.currentValue || isReset; let isSendConfig = true; isSendConfig = isConfigChanged && (isSave || isReset) && row.formGroupCtrl.status === FormValidation.VALID; if (isSendConfig) { const IEditConfig: IEditConfig = { name: '', value: '', param_original: row.currentValue, }; IEditConfig.name = this.editingRow.configName; IEditConfig.value = isSave ? this.editingRow.currentValue : row.defaultValue; const editConfigs = [IEditConfig]; this.openModal(editConfigs, configType); } this.editableRows(true); this.editingRow = undefined; } onResetClick(item: TiActionmenuItem, row: TiTableRowData): void { row.formGroupCtrl.patchValue({ newValue: row.defaultValue }); } toTaskQuery(): void { const params = { instanceId: this.instanceId }; this.stateService.go('queue.newKafkaDetail.taskQuery', params); } showResetConfirmPrompt(): void { this.tiMessage.open({ type: 'prompt', content: this.i18n.get('common_config_auto_restart_notice'), close: (modalRef: TiModalRef): void => { this.resetConfig(); }, }); } resetConfigClick(): void { this.showResetConfirmPrompt(); } resetConfig(): void { this.kafkaConfigManagerService.resetConfigRequest$(this.instanceId).subscribe( res => { this.taskJobId = res.job_id; this.toTaskQuery(); }, err => { this.messageService.showException(err); } ); } private editableRows(editable: boolean, message = ''): void { this.dynamicItems = this.kafkaConfigManagerService.getConfigTips(this.dynamicItems, editable, message); if (this.enableEditStatic) { this.staticItems = this.kafkaConfigManagerService.getConfigTips(this.staticItems, editable, message); } else { this.handleDisableStaticMsgTip(); } } private handleDisableStaticMsgTip(): void { this.staticItems = this.staticItems.map( (item: TiActionmenuItem): TiActionmenuItem => ({ ...item, disabled: true, tip: this.i18n.get('common_config_disable_edit_static_config'), }) ); } private getTaskStatus(instanceId: string, jobId: string): void { this.kafkaConfigManagerService.taskStatusRequest$(instanceId, jobId).subscribe( res => { const taskStatus = res.tasks[0].status; this.handleTaskStatus(taskStatus, jobId); }, err => { this.messageService.showException(err); } ); } private handleTaskStatus(taskStatus: string, jobId: string) { if (taskStatus !== TaskStatus.SUCCESS) { this.isTaskChanging = true; this.isInstanceRunning = false; this.editableRows(false, this.i18n.get('common_config_changing')); if (this.taskStatusInterval === null) { this.taskStatusInterval = setInterval(() => { this.getTaskStatus(this.instanceId, jobId); }, 5000); } } else { this.isTaskChanging = false; this.handleInstance(this.instanceId); this.closeInterval(); } } private handleInstance(instanceId: string): void { this.kafkaConfigManagerService.instanceStatusRequest$(instanceId).subscribe( res => { this.instanceDetailData = res; this.instanceName = res.name; this.supportFeatures = res?.support_features; this.checkStaticFeature(res?.support_features); this.handleRunningInstanceErrCode(res?.error_code); if (res?.status) { // 根据实例冻结和用户限制提示不可编辑原因 this.handleInstanceStatus(res?.error_code, res.status); } }, err => { this.messageService.showException(err); } ); } private checkStaticFeature(supportFeatures = '') { this.enableEditStatic = supportFeatures.includes(SupportFeatures.KafkaConfigStaticModify); } private handleRunningInstanceErrCode(error_code = null) { if (error_code) { this.isInstanceErrorCode = true; // 实例在运行,instance返回error_code时,实例不支持参数变更 this.editableRows(false, this.i18n.get(error_code)); } } private handleInstanceStatus(error_code: string, status: string): void { // 根据用户权限和实例冻结显示tips this.tipMessage = this.dmsGridKitService.getInstanceStatus(this.instanceDetailData).tip; this.checkGetConfigs(status); if (this.tipMessage !== '') { this.editableRows(false, this.tipMessage); } // 只有实例在Running的时候才可以编辑,其他状态不可以。如果Extending可以去任务界面查看 if (status === InstanceStatus.RUNNING) { this.isTaskChanging = false; this.isInstanceRunning = true; if (!error_code && !this.tipMessage) { this.editableRows(true); } this.closeInterval(); } else { this.isInstanceRunning = false; this.editableRows(false, this.i18n.get('common_config_cannot_change')); this.loopInstanceStatus(); } } private loopInstanceStatus() { if (status === InstanceStatus.EXTENDING) { this.isTaskChanging = true; if (this.instanceStatusInterval === null) { this.instanceStatusInterval = setInterval(() => { this.handleInstance(this.instanceId); }, 5000); } } } private checkGetConfigs(status: string) { // 特定状态的实例无法调用config接口 -- 404, 不调用get config,直接返回显示空表格 const noConfigList: Array<string> = [ InstanceStatus.CREATING, InstanceStatus.RESTARTING, InstanceStatus.STARTING, InstanceStatus.CREATEFAILED, InstanceStatus.UPGRADING, InstanceStatus.ROLLBACK, InstanceStatus.FREEZING, ]; if (noConfigList.includes(status)) { return; } this.handleConfigs(this.instanceId); } private configSetFinished(): Observable<boolean> { return this.finishSetConfig.asObservable(); } private closeInterval() { if (this.instanceStatusInterval) { clearInterval(this.instanceStatusInterval); this.instanceStatusInterval = null; } if (this.taskStatusInterval) { clearInterval(this.taskStatusInterval); this.taskStatusInterval = null; } } } 报错index.js:493 [webpack-dev-server] ERROR src/ng2app/instance-config/kafka-config-manager/kafka-config-manager.component.ts:151:31 - error TS2339: Property 'tipStr' does not exist on type '{ label: string; key: string; actionCallback: (obj: any) => void; }'. 151 deleteBtn.tipStr = '只有一条记录,不能删除。'; ~~~~~~ index.js:493 [webpack-dev-server] Errors while compiling. Reload prevented. index.js:493 [webpack-dev-server] ERROR src/ng2app/instance-config/kafka-config-manager/kafka-config-manager.component.ts:127:24 - error TS2339: Property 'disabled' does not exist on type '{ label: string; key: string; actionCallback: (obj: any) => void; }'. 127 }).disabled = hasEditRows; ~~~~~~~~ index.js:493 [webpack-dev-server] ERROR src/ng2app/instance-config/kafka-config-manager/kafka-config-manager.component.ts:143:24 - error TS2339: Property 'tipStr' does not exist on type '{ label: string; key: string; actionCallback: (obj: any) => void; }'. 143 }).tipStr = editDisabledTip; ~~~~~~ index.js:493 [webpack-dev-server] ERROR src/ng2app/instance-config/kafka-config-manager/kafka-config-manager.component.ts:145:29 - error TS2339: Property 'disabled' does not exist on type '{ label: string; key: string; actionCallback: (obj: any) => void; }'. 145 deleteBtn.disabled = hasEditRows; ~~~~~~~~ index.js:493 [webpack-dev-server] ERROR src/ng2app/instance-config/kafka-config-manager/kafka-config-manager.component.ts:146:29 - error TS2339: Property 'tipStr' does not exist on type '{ label: string; key: string; actionCallback: (obj: any) => void; }'. 146 deleteBtn.tipStr = editDisabledTip; ~~~~~~ index.js:493 [webpack-dev-server] ERROR src/ng2app/instance-config/kafka-config-manager/kafka-config-manager.component.ts:150:31 - error TS2339: Property 'disabled' does not exist on type '{ label: string; key: string; actionCallback: (obj: any) => void; }'. 150 deleteBtn.disabled = true; ~~~~~~~~ index.js:493 [webpack-dev-server] ERROR src/ng2app/instance-config/kafka-config-manager/kafka-config-manager.component.ts:151:31 - error TS2339: Property 'tipStr' does not exist on type '{ label: string; key: string; actionCallback: (obj: any) => void; }'. 151 deleteBtn.tipStr = '只有一条记录,不能删除。';
07-12
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值