1/* 2 * Copyright (c) 2025 Huawei Device Co., Ltd. 3 * Licensed under the Apache License, Version 2.0 (the "License"); 4 * you may not use this file except in compliance with the License. 5 * You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software 10 * distributed under the License is distributed on an "AS IS" BASIS, 11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 * See the License for the specific language governing permissions and 13 * limitations under the License. 14 */ 15 16import { inputMethod } from '@kit.IMEKit'; 17import { VerifyCodeConstants } from '../constants/VerifyCodeConstants'; 18import { logger } from '../utils/Logger'; 19import { emitter } from '@kit.BasicServicesKit'; 20 21@Extend(Text) 22function verifyCodeUnitStyle() { 23 .fontSize($r('sys.float.ohos_id_text_size_body1')) 24 .fontWeight(VerifyCodeConstants.VERIFY_CODE_FONTWEIGHT) 25 .textAlign(TextAlign.Center) 26 .width($r('app.integer.verify_code_code_unit_with')) 27 .height('100%') 28 .margin({ 29 left: $r('app.integer.verify_code_code_unit_margin'), 30 right: $r('app.integer.verify_code_code_unit_margin') 31 }) 32 .border({ 33 width: { bottom: $r('app.integer.verify_code_code_border_width') }, 34 color: { bottom: Color.Grey }, 35 style: { bottom: BorderStyle.Solid } 36 }) 37} 38 39@Component 40struct VerifyCodeComponentWithoutCursor { 41 @State codeText: string = ''; 42 private readonly verifyID: string = 'verifyCodeComponent'; 43 private inputController: inputMethod.InputMethodController = inputMethod.getController(); 44 // 监听键盘弹出收起状态 45 @State isKeyboardShow: boolean = false; 46 private verifyCodeLength: number = 6; 47 private isListen: boolean = false; 48 private textConfig: inputMethod.TextConfig = { 49 inputAttribute: { 50 textInputType: inputMethod.TextInputType.NUMBER, 51 enterKeyType: inputMethod.EnterKeyType.GO 52 }, 53 }; 54 private codeIndexArray: Array<number> = Array.from([0, 1, 2, 3, 4, 5]); 55 // 注册路由返回函数,案例插件不触发 56 popRouter: () => void = () => { 57 }; 58 59 aboutToAppear(): void { 60 // 注册返回监听,包括点击手机返回键返回与侧滑返回 61 this.listenBackPress(); 62 } 63 64 async attachAndListen(): Promise<void> { 65 focusControl.requestFocus(this.verifyID); 66 await this.inputController.attach(true, this.textConfig); 67 logger.info('attached'); 68 this.listen(); 69 this.isKeyboardShow = true; 70 } 71 72 listenBackPress() { 73 let innerEvent: emitter.InnerEvent = { 74 eventId: 5 75 }; 76 // 收到eventId为5的事件后执行回调函数 77 emitter.on(innerEvent, () => { 78 if (this.isKeyboardShow) { 79 // 退出文本编辑状态 80 this.inputController.hideTextInput(); 81 this.isKeyboardShow = false; 82 } else { 83 this.popRouter(); 84 } 85 }); 86 } 87 88 aboutToDisappear(): void { 89 this.off(); 90 // 关闭事件监听 91 emitter.off(5); 92 } 93 94 /** 95 * TODO 知识点:绑定输入法 96 */ 97 async attach() { 98 await this.inputController.attach(true, this.textConfig); 99 logger.info('attached'); 100 } 101 102 /** 103 * TODO:知识点:解绑 104 */ 105 off(): void { 106 this.inputController.off('insertText'); 107 this.inputController.off('deleteLeft'); 108 this.isListen = false; 109 logger.info('detached'); 110 // 退出文本编辑状态 111 this.inputController.hideTextInput(); 112 this.isKeyboardShow = false; 113 } 114 115 /** 116 * TODO 知识点:订阅输入法代插入、向左删除事件,从而获得键盘输入内容 117 */ 118 listen() { 119 if (this.isListen) { 120 return; 121 } 122 this.inputController.on('insertText', (text: string) => { 123 if (this.codeText.length >= this.verifyCodeLength || isNaN(Number(text)) || text === ' ') { 124 return; 125 } 126 this.codeText += text; 127 if (this.codeText.length === this.verifyCodeLength) { 128 logger.info('VerifyCode: %{public}s', this.codeText); 129 } 130 logger.info('VerifyCode [insert]: %{public}s', this.codeText); 131 }) 132 133 this.inputController.on('deleteLeft', (length: number) => { 134 this.codeText = this.codeText.substring(0, this.codeText.length - 1); 135 logger.info('VerifyCode [delete left]: %{public}s', this.codeText); 136 }) 137 this.isListen = true; 138 logger.info('listener added'); 139 } 140 141 /** 142 * TODO 知识点:部分验证码场景要完全禁止对输入验证码的选中、复制等功能,因此可以使用Text组件完成 143 */ 144 @Builder 145 buildVerifyCodeComponent() { 146 Flex({ direction: FlexDirection.Row, alignItems: ItemAlign.Center, justifyContent: FlexAlign.SpaceBetween }) { 147 ForEach(this.codeIndexArray, (item: number, index: number) => { 148 Text(this.codeText[item]) 149 .verifyCodeUnitStyle() 150 }, (item: number, index: number) => item.toString()) 151 } 152 .id(this.verifyID) 153 /** 154 * TODO:知识点:当可视面积变化时进行绑定注册与解绑 155 */ 156 .onBlur(() => { 157 this.off(); 158 }) 159 .backgroundColor(Color.Transparent) 160 .height($r('app.integer.verify_code_verify_code_height')) 161 .margin({ left: $r('sys.float.ohos_id_card_margin_start'), right: $r('sys.float.ohos_id_card_margin_start') }) 162 .defaultFocus(true) 163 .onClick(() => { 164 // TODO 知识点:点击本组件时弹出输入法,因为这里使用的是Text组件,因此需要重新attach,而不能直接使用showSoftKeyboard 165 this.attachAndListen(); 166 }) 167 } 168 169 build() { 170 Row() { 171 this.buildVerifyCodeComponent() 172 } 173 } 174} 175 176/** 177 * 验证码组件:禁用选中、复制、光标 178 */ 179@Component 180export struct VerifyCodeViewComponent { 181 popRouter: () => void = () => { 182 }; 183 184 build() { 185 Column() { 186 VerifyCodeComponentWithoutCursor({ popRouter: this.popRouter }) 187 } 188 .height('100%') 189 .width('100%') 190 .justifyContent(FlexAlign.Center) 191 } 192} 193