shishuangxi

提交登录模块

Showing 45 changed files with 1029 additions and 3 deletions
@@ -54,7 +54,7 @@ export class HttpUrlUtils { @@ -54,7 +54,7 @@ export class HttpUrlUtils {
54 */ 54 */
55 static readonly E_NEWSPAPER_LIST_PATH: string = "/api/rmrb-bff-display-zh/display/zh/c/paperApi/paperList"; 55 static readonly E_NEWSPAPER_LIST_PATH: string = "/api/rmrb-bff-display-zh/display/zh/c/paperApi/paperList";
56 56
57 - private static hostUrl: string = HttpUrlUtils.HOST_PRODUCT; 57 + private static hostUrl: string = HttpUrlUtils.HOST_UAT;
58 58
59 static getCommonHeaders(): HashMap<string, string> { 59 static getCommonHeaders(): HashMap<string, string> {
60 let headers: HashMap<string, string> = new HashMap<string, string>() 60 let headers: HashMap<string, string> = new HashMap<string, string>()
@@ -67,7 +67,7 @@ export class HttpUrlUtils { @@ -67,7 +67,7 @@ export class HttpUrlUtils {
67 headers.set('timestamp', HttpUrlUtils.getTimestamp()) 67 headers.set('timestamp', HttpUrlUtils.getTimestamp())
68 headers.set('RMRB-X-TOKEN', HttpUrlUtils.getXToken()) 68 headers.set('RMRB-X-TOKEN', HttpUrlUtils.getXToken())
69 headers.set('device_id', HttpUrlUtils.getDeviceId()) 69 headers.set('device_id', HttpUrlUtils.getDeviceId())
70 - headers.set('cookie', 'RMRB-X-TOKEN=eyJhbGciOiJIUzI1NiIsImtpZCI6ImQ4WkI2QkhxSEZrdjJ2U25BNlRwZEdKRjBHcjItVzBvS2FaYzdLOUUycmcifQ.eyJpc3MiOiJwZW9wbGVzLWRhaWx5LWZvdXJhIiwic3ViIjoicGVvcGxlcy1kYWlseS1mb3VyYSIsImV4cCI6MTcwMzY0OTYwNiwidXNlcklkIjo0NTk3NzYyOTc0NzQ5NDksInVzZXJWZXJzaW9uIjoiNDU5Nzc2Mjk3NDc0OTQ5XzIiLCJ1c2VyTmFtZSI6IkJ1bGlraWtpMTgxIiwidXNlclR5cGUiOjIsImNyZWF0b3JJZCI6NDI2NTM5MH0.jhQ9kylcm3FxWf0-lBMZuLkdtIQ6XpFnAi0AFZJNwfc') 70 + // headers.set('cookie', 'RMRB-X-TOKEN=eyJhbGciOiJIUzI1NiIsImtpZCI6ImQ4WkI2QkhxSEZrdjJ2U25BNlRwZEdKRjBHcjItVzBvS2FaYzdLOUUycmcifQ.eyJpc3MiOiJwZW9wbGVzLWRhaWx5LWZvdXJhIiwic3ViIjoicGVvcGxlcy1kYWlseS1mb3VyYSIsImV4cCI6MTcwMzY0OTYwNiwidXNlcklkIjo0NTk3NzYyOTc0NzQ5NDksInVzZXJWZXJzaW9uIjoiNDU5Nzc2Mjk3NDc0OTQ5XzIiLCJ1c2VyTmFtZSI6IkJ1bGlraWtpMTgxIiwidXNlclR5cGUiOjIsImNyZWF0b3JJZCI6NDI2NTM5MH0.jhQ9kylcm3FxWf0-lBMZuLkdtIQ6XpFnAi0AFZJNwfc')
71 headers.set('build_version', HttpUrlUtils.getVersion()) 71 headers.set('build_version', HttpUrlUtils.getVersion())
72 headers.set('adcode', HttpUrlUtils.getAdCode()) 72 headers.set('adcode', HttpUrlUtils.getAdCode())
73 headers.set('os_version', HttpUrlUtils.getOsVersion()) 73 headers.set('os_version', HttpUrlUtils.getOsVersion())
@@ -214,4 +214,19 @@ export class HttpUrlUtils { @@ -214,4 +214,19 @@ export class HttpUrlUtils {
214 private static getUserType() { 214 private static getUserType() {
215 return '2'; 215 return '2';
216 } 216 }
  217 +
  218 + static getVerifyCodeUrl() {
  219 + let url = HttpUrlUtils.hostUrl + "/api/rmrb-user-center/auth/zh/c/sendVerifyCode";
  220 + return url;
  221 + }
  222 +
  223 + static getAppLoginUrl() :string{
  224 + let url = HttpUrlUtils.getHost() + "/api/rmrb-user-center/auth/zh/c/appLogin";
  225 + return url;
  226 + }
  227 +
  228 + static getCheckVerifyCodeUrl() {
  229 + let url = HttpUrlUtils.hostUrl + "/api/rmrb-user-center/auth/zh/c/checkVerifyCode";
  230 + return url;
  231 + }
217 } 232 }
@@ -32,4 +32,8 @@ export class WDRouterPage { @@ -32,4 +32,8 @@ export class WDRouterPage {
32 static detailPlayVodPage = new WDRouterPage("wdDetailPlayVod", "ets/pages/DetailPlayVodPage"); 32 static detailPlayVodPage = new WDRouterPage("wdDetailPlayVod", "ets/pages/DetailPlayVodPage");
33 // 直播详情页 33 // 直播详情页
34 static detailPlayLivePage = new WDRouterPage("wdDetailPlayLive", "ets/pages/DetailPlayLivePage"); 34 static detailPlayLivePage = new WDRouterPage("wdDetailPlayLive", "ets/pages/DetailPlayLivePage");
  35 +
  36 + static loginPage = new WDRouterPage("wdLogin", "ets/pages/login/LoginPage");
  37 +
  38 + static forgetPasswordPage = new WDRouterPage("wdLogin", "ets/pages/login/ForgetPasswordPage");
35 } 39 }
@@ -14,7 +14,7 @@ export class WDRouterRule { @@ -14,7 +14,7 @@ export class WDRouterRule {
14 WDRouterRule.jumpWithPage(page, action) 14 WDRouterRule.jumpWithPage(page, action)
15 } 15 }
16 16
17 - private static jumpWithPage(page?: WDRouterPage, params?: object) { 17 + static jumpWithPage(page?: WDRouterPage, params?: object) {
18 if (page) { 18 if (page) {
19 if (params) { 19 if (params) {
20 // router.pushUrl({ url: 'pages/routerpage2', , params: params }) 20 // router.pushUrl({ url: 'pages/routerpage2', , params: params })
  1 +/node_modules
  2 +/oh_modules
  3 +/.preview
  4 +/build
  5 +/.cxx
  6 +/.test
  1 +export { add } from "./src/main/ets/utils/Calc"
  1 +{
  2 + "apiType": "stageMode",
  3 + "buildOption": {
  4 + "arkOptions": {
  5 + // "apPath": "./modules.ap" /* Profile used for profile-guided optimization (PGO), a compiler optimization technique to improve app runtime performance. */
  6 + }
  7 + },
  8 + "buildOptionSet": [
  9 + {
  10 + "name": "release",
  11 + "arkOptions": {
  12 + "obfuscation": {
  13 + "ruleOptions": {
  14 + "enable": true,
  15 + "files": [
  16 + "./obfuscation-rules.txt"
  17 + ]
  18 + }
  19 + }
  20 + }
  21 + },
  22 + ],
  23 + "targets": [
  24 + {
  25 + "name": "default"
  26 + }
  27 + ]
  28 +}
  1 +import { hspTasks } from '@ohos/hvigor-ohos-plugin';
  2 +
  3 +export default {
  4 + system: hspTasks, /* Built-in plugin of Hvigor. It cannot be modified. */
  5 + plugins:[] /* Custom plugin to extend the functionality of Hvigor. */
  6 +}
  1 +# Define project specific obfuscation rules here.
  2 +# You can include the obfuscation configuration files in the current module's build-profile.json5.
  3 +#
  4 +# For more details, see
  5 +# https://gitee.com/openharmony/arkcompiler_ets_frontend/blob/master/arkguard/README.md
  6 +
  7 +# Obfuscation options:
  8 +# -disable-obfuscation: disable all obfuscations
  9 +# -enable-property-obfuscation: obfuscate the property names
  10 +# -enable-toplevel-obfuscation: obfuscate the names in the global scope
  11 +# -compact: remove unnecessary blank spaces and all line feeds
  12 +# -remove-log: remove all console.* statements
  13 +# -print-namecache: print the name cache that contains the mapping from the old names to new names
  14 +# -apply-namecache: reuse the given cache file
  15 +
  16 +# Keep options:
  17 +# -keep-property-name: specifies property names that you want to keep
  18 +# -keep-global-name: specifies names that you want to keep in the global scope
  1 +{
  2 + "name": "wdlogin",
  3 + "version": "1.0.0",
  4 + "description": "Please describe the basic information.",
  5 + "main": "Index.ets",
  6 + "author": "",
  7 + "license": "Apache-2.0",
  8 + "dependencies": {
  9 + "wdConstant": "file:../../commons/wdConstant",
  10 + "wdKit": "file:../../commons/wdKit",
  11 + "wdWebComponent": "file:../../commons/wdWebComponent",
  12 + "wdBean": "file:../../features/wdBean",
  13 + "wdRouter": "file:../../commons/wdRouter",
  14 + "wdNetwork": "file:../../commons/wdNetwork"
  15 + }
  16 +}
  1 +@Entry
  2 +@Component
  3 +struct Index {
  4 + @State message: string = 'Hello World';
  5 +
  6 + build() {
  7 + Row() {
  8 + Column() {
  9 + Text(this.message)
  10 + .fontSize(50)
  11 + .fontWeight(FontWeight.Bold)
  12 + }
  13 + .width('100%')
  14 + }
  15 + .height('100%')
  16 + }
  17 +}
  1 +export interface CheckVerifyBean{
  2 + temToken: string
  3 + jwtToken: string
  4 +}
  1 +@CustomDialog
  2 +export struct CustomProtocolDialog {
  3 + cancel: () => void=()=>{}
  4 + confirm: () => void=()=>{}
  5 + controller: CustomDialogController
  6 +
  7 + build() {
  8 + Column() {
  9 + Text("温馨提示")
  10 + .fontColor("#222222")
  11 + .fontSize(23)
  12 + .width("100%")
  13 + .fontWeight(FontWeight.Bold)
  14 + .textAlign(TextAlign.Center)
  15 + .margin({ top: 26 })
  16 + Text() {
  17 + Span("为保障您的合法权益,请阅读并同意").fontSize(18).fontColor("#666666")
  18 + Span("《用户协议》").fontSize(18).fontColor("#ED2800")
  19 + Span("及").fontSize(18).fontColor("#666666")
  20 + Span("《隐私政策》").fontSize(18).fontColor("#ED2800")
  21 + Span("后进行登录").fontSize(18).fontColor("#666666")
  22 + }.margin({ top: 15, left: 20, right: 20 })
  23 +
  24 + Divider().color("#999999").width("100%").margin({ top: 18 }).height('0.5vp')
  25 + Row() {
  26 + Text('放弃登录')
  27 + .fontSize(20)
  28 + .fontColor("#999999")
  29 + .layoutWeight(1)
  30 + .fontWeight(FontWeight.Medium)
  31 + .textAlign(TextAlign.Center)
  32 + .onClick(() => {
  33 + this.controller.close()
  34 + if (this.cancel) {
  35 + this.cancel()
  36 + }
  37 +
  38 + }).height('100%')
  39 + // Divider().color("#999999").height('100%').width('0.5vp')
  40 + Text('同意并登录')
  41 + .fontSize(20)
  42 + .fontColor("#ED2800")
  43 + .layoutWeight(1)
  44 + .fontWeight(FontWeight.Medium)
  45 + .textAlign(TextAlign.Center)
  46 + .border({
  47 + width:{left:1},
  48 + color:"#999999",
  49 + style:{left:BorderStyle.Solid}
  50 +
  51 + })
  52 + .onClick(() => {
  53 + this.controller.close()
  54 + if (this.confirm) {
  55 + this.confirm()
  56 + }
  57 + }).height('100%')
  58 + }.layoutWeight(1).justifyContent(FlexAlign.Center)
  59 + }.height(206).backgroundColor(Color.White).borderRadius(8)
  60 + }
  61 +}
  1 +import { Logger } from 'wdKit/src/main/ets/utils/Logger'
  2 +import { LoginInputComponent } from './LoginInputComponent'
  3 +import { LoginViewModel } from './LoginViewModel'
  4 +import router from '@ohos.router'
  5 +import promptAction from '@ohos.promptAction'
  6 +
  7 +const TAG = 'ForgetPasswordPage'
  8 +/**
  9 + * 找回密码页面
  10 + * */
  11 +@Entry
  12 +@Component
  13 +struct ForgetPasswordPage {
  14 + @State phoneContent: string = ''
  15 + @State codeContent: string = ''
  16 + @State isSubmit: boolean = false //是否可以提交 默认不可以
  17 + loginViewModel: LoginViewModel = new LoginViewModel()
  18 + @State @Watch('onCodeSend') isCodeSend: boolean = false //验证码点击发送事件
  19 + onCodeSend() {
  20 + if (this.isCodeSend) {
  21 + this.sendVerifyCode()
  22 + }
  23 + }
  24 +
  25 + build() {
  26 + Column() {
  27 + Image($r('app.media.login_back_icon')).width(24).height(24).margin({ left: 15, top: 10 }).onClick(() => {
  28 + router.back()
  29 + })
  30 + Text('找回密码').fontSize(22).fontColor('#333333').fontWeight(FontWeight.Bold).margin({ left: 25, top: 112 })
  31 + LoginInputComponent({
  32 + phoneContent: $phoneContent,
  33 + codeContent: $codeContent,
  34 + isSubmit: $isSubmit,
  35 + isCodeSend: $isCodeSend
  36 + })
  37 + Row() {
  38 + Button("确认", { type: ButtonType.Normal })
  39 + .layoutWeight(1)
  40 + .borderRadius(4)
  41 + .fontSize(16)
  42 + .fontWeight(FontWeight.Medium)
  43 + .margin({ top: 26 })
  44 + .height(44)
  45 + .backgroundColor("#ED2800")
  46 + .opacity(this.isSubmit ? 1 : 0.6)
  47 + .onClick(() => {
  48 + this.checkVerifyCode()
  49 +
  50 + })
  51 + }.padding({ left: 25, right: 25 }).width('100%')
  52 +
  53 + }.width('100%').height('100%').alignItems(HorizontalAlign.Start)
  54 + }
  55 +
  56 + aboutToAppear() {
  57 + if (this.isCodeSend) {
  58 + this.sendVerifyCode()
  59 + }
  60 + }
  61 +
  62 + //发送验证码
  63 + sendVerifyCode() {
  64 + if (this.isEmpty(this.phoneContent)) {
  65 + return
  66 + }
  67 + this.loginViewModel.sendVerifyCode(this.phoneContent).then((verifyCode) => {
  68 + promptAction.showToast({ message: "验证码已发送成功" })
  69 + Logger.debug(TAG, "sendVerifyCode: " + verifyCode)
  70 + })
  71 +
  72 + }
  73 +
  74 +
  75 + //校验验证码
  76 + checkVerifyCode() {
  77 + if (!this.isSubmit) {
  78 + return
  79 + }
  80 + if (this.isEmpty(this.phoneContent)) {
  81 + return
  82 + }
  83 + if (this.isEmpty(this.codeContent)) {
  84 + return
  85 + }
  86 +
  87 + this.loginViewModel.checkVerifyCode(this.phoneContent, this.codeContent).then(() => {
  88 + //todo 跳转密码设置页面
  89 + promptAction.showToast({message:"校验成功,准备跳转设置页面"})
  90 + Logger.debug(TAG,"校验成功")
  91 + }).catch((error:string)=>{
  92 + promptAction.showToast({message:error})
  93 + Logger.debug(TAG,"校验失败")
  94 + })
  95 + }
  96 +
  97 + isEmpty(obj: undefined|null|string): boolean {
  98 + return (obj == undefined || obj == null || obj == '');
  99 + }
  100 +}
  1 +export interface LoginBean {
  2 + // {"code":"0","data":{"firstMark":1,"id":569350079889669,"jwtToken":"eyJhbGciOiJIUzI1NiIsImtpZCI6ImQ4WkI2QkhxSEZrdjJ2U25BNlRwZEdKRjBHcjItVzBvS2FaYzdLOUUycmcifQ.eyJpc3MiOiJwZW9wbGVzLWRhaWx5LWZvdXJhIiwic3ViIjoicGVvcGxlcy1kYWlseS1mb3VyYSIsImV4cCI6MTcxMDY0NzE0MiwidXNlcklkIjo1NjkzNTAwNzk4ODk2NjksInVzZXJWZXJzaW9uIjoiNTY5MzUwMDc5ODg5NjY5XzAiLCJ1c2VyTmFtZSI6IiVFNCVCQSVCQSVFNiVCMCU5MSVFNiU5NyVBNSVFNiU4QSVBNSVFNyVCRCU5MSVFNSU4RiU4QjJrRDJ4VyIsInVzZXJUeXBlIjoxLCJjcmVhdG9ySWQiOm51bGx9.1M0E_6V60opL7AxamGzINpieTHP11fRbiXKDPc-ywWg","longTimeNoLoginMark":0,"refreshToken":"83fe2f1a-77af-4f73-8490-1cebed037f73","status":1,"userName":"人民日报网友2kD2xW","userType":1},"message":"Success","success":true,"timestamp":1710467142214}
  3 + firstMark: number
  4 + id: number
  5 + jwtToken: string
  6 + longTimeNoLoginMark: number
  7 + refreshToken: string
  8 + status: number
  9 + userName: string
  10 + userType: number
  11 + temToken: string
  12 +}
  1 +import { Logger } from 'wdKit'
  2 +
  3 +@Component
  4 +export struct LoginInputComponent {
  5 + @State timeCount: number = 60
  6 + @Link phoneContent: string
  7 + @Link codeContent: string
  8 + @State codeBtnState: boolean = false //发送验证码控件是否可以 默认不可用
  9 + @Link isCodeSend: boolean //验证码控件是否点击 默认没有 发送接口
  10 + @Link isSubmit: boolean //是否可以提交
  11 +
  12 + build() {
  13 + Column() {
  14 + this.addCodeLayout()
  15 + }.width('100%').padding({ left: 25, right: 25 })
  16 + }
  17 +
  18 + @Builder
  19 + addCodeLayout() {
  20 + TextInput({ placeholder: "请输入手机号" })
  21 + .fontSize(16)
  22 + .height(48)
  23 + .margin({ top: 36 })
  24 + .backgroundColor("#F5F5F5")
  25 + .borderRadius(4)
  26 + .type(InputType.PhoneNumber)
  27 + .onChange((content) => {
  28 + this.phoneContent = content
  29 + this.isSubmit = (this.phoneContent.length >= 11 && this.codeContent.length >= 6)
  30 + if (content.length >= 11) {
  31 + this.codeBtnState = true
  32 + } else {
  33 + this.codeBtnState = false
  34 + }
  35 + })
  36 +
  37 + Row() {
  38 + TextInput({ placeholder: "验证码" })
  39 + .layoutWeight(1)
  40 + .fontSize(16)
  41 + .height(48)
  42 + .type(InputType.Number)
  43 + .fontColor("#222222")
  44 + .backgroundColor("#00000000")
  45 + .borderRadius({ topLeft: 4, bottomLeft: 4 })
  46 + .backgroundImage($r('app.media.login_code_bg_one'), ImageRepeat.NoRepeat)
  47 + .backgroundImageSize(ImageSize.Contain)
  48 + .onChange((value) => {
  49 + this.codeContent = value
  50 + this.isSubmit = (this.phoneContent.length >= 11 && this.codeContent.length >= 6)
  51 + })
  52 +
  53 + Text(this.isCodeSend ? this.timeCount + "s" : "发送验证码")
  54 + .backgroundImage($r('app.media.login_code_bg'), ImageRepeat.NoRepeat)
  55 + .backgroundImageSize(ImageSize.Cover)
  56 + .fontColor('#ED2800')
  57 + .width(110)
  58 + .fontSize(14)
  59 + .fontWeight(this.codeBtnState ? FontWeight.Bold : FontWeight.Normal)
  60 + .height(48)
  61 + .enabled(this.codeBtnState)// .align(Alignment.End)
  62 + .textAlign(TextAlign.Center)
  63 + .onClick(() => {
  64 + if (this.phoneContent.length < 11) {
  65 + return
  66 + }
  67 + this.isCodeSend = true
  68 + let time = setInterval(() => {
  69 + Logger.debug("倒计时:" + this.timeCount)
  70 + this.timeCount--
  71 + if (this.timeCount < 1) {
  72 + this.isCodeSend = false
  73 + this.timeCount = 60
  74 + clearInterval(time)
  75 + }
  76 + }, 1000)
  77 +
  78 + })
  79 +
  80 +
  81 + }.margin({ top: 15 }).height(61).alignItems(VerticalAlign.Center).justifyContent(FlexAlign.Start)
  82 +
  83 + }
  84 +}
  1 +import HashMap from '@ohos.util.HashMap';
  2 +import { HttpUrlUtils, ResponseDTO } from 'wdNetwork';
  3 +import { Logger } from 'wdKit';
  4 +import { HttpRequest } from 'wdNetwork/src/main/ets/http/HttpRequest';
  5 +import { LoginBean } from './LoginBean';
  6 +import { CheckVerifyBean } from './CheckVerifyBean';
  7 +
  8 +const TAG = 'LoginModel'
  9 +
  10 +export class LoginModel {
  11 + //获取验证码
  12 + sendVerifyCode(number: string) {
  13 + let bean: Record<string, string> = {};
  14 + bean['phoneNum'] = number
  15 + let headers: HashMap<string, string> = HttpUrlUtils.getCommonHeaders();
  16 + return new Promise<string>((success, fail) => {
  17 + HttpRequest.post<ResponseDTO<string>>(HttpUrlUtils.getVerifyCodeUrl(), bean, headers).then((data: ResponseDTO<string>) => {
  18 + if (!data || !data.data) {
  19 + fail("数据为空")
  20 + return
  21 + }
  22 + if (data.code != 0) {
  23 + fail(data.message)
  24 + return
  25 + }
  26 + success(data.data)
  27 + }, (error: Error) => {
  28 + fail(error.message)
  29 + Logger.debug("LoginViewModel:error ", error.toString())
  30 + })
  31 + })
  32 +
  33 + }
  34 +
  35 +
  36 + //{"phone":"13625644528","loginType":2,"deviceId":"60da5af6-9c59-3566-8622-8c6c00710994","verificationCode":"644528"}
  37 + appLogin(phone: string, loginType: number, verificationCode: string) {
  38 + let headers: HashMap<string, string> = HttpUrlUtils.getCommonHeaders();
  39 + let bean: Record<string, Object> = {};
  40 + bean['phone'] = phone
  41 + bean['loginType'] = loginType
  42 + bean['deviceId'] = '60da5af6-9c59-3566-8622-8c6c00710994'
  43 + bean['verificationCode'] = verificationCode
  44 + return new Promise<LoginBean>((success, fail) => {
  45 + HttpRequest.post<ResponseDTO<LoginBean>>(HttpUrlUtils.getAppLoginUrl(), bean, headers).then((data: ResponseDTO<LoginBean>) => {
  46 + Logger.debug("LoginViewModel:success2 ", data.message)
  47 + if (!data || !data.data) {
  48 + fail("数据为空")
  49 + return
  50 + }
  51 + if (data.code != 0) {
  52 + fail(data.message)
  53 + return
  54 + }
  55 + success(data.data)
  56 + }, (error: Error) => {
  57 + fail(error.message)
  58 + Logger.debug("LoginViewModel:error2 ", error.toString())
  59 + })
  60 + })
  61 + }
  62 +
  63 + // {"password":"523acd319228efde34e8a30268ee8ca5e4fc421d72affa531676e1765940d22c","phone":"13625644528","loginType":0,"oldPassword":"BA5FD74F827AF9B271FE17CADC489C36","deviceId":"60da5af6-9c59-3566-8622-8c6c00710994"}
  64 + appLoginByPassword(phone: string, loginType: number, password: string, oldPassword: string) {
  65 + let headers: HashMap<string, string> = HttpUrlUtils.getCommonHeaders();
  66 + let bean: Record<string, string | number> = {};
  67 + bean['phone'] = phone
  68 + bean['loginType'] = loginType
  69 + bean['deviceId'] = '60da5af6-9c59-3566-8622-8c6c00710994'
  70 + bean['password'] = password
  71 + bean['oldPassword'] = oldPassword
  72 + return new Promise<LoginBean>((success, fail) => {
  73 + HttpRequest.post<ResponseDTO<LoginBean>>(HttpUrlUtils.getAppLoginUrl(), bean, headers).then((data: ResponseDTO<LoginBean>) => {
  74 + Logger.debug("LoginViewModel:success2 ", data.message)
  75 + if (!data || !data.data) {
  76 + fail("数据为空")
  77 + return
  78 + }
  79 + if (data.code != 0) {
  80 + fail(data.message)
  81 + return
  82 + }
  83 + success(data.data)
  84 + }, (error: Error) => {
  85 + Logger.debug("LoginViewModel:error2 ", error.toString())
  86 + fail(error.message)
  87 + })
  88 + })
  89 + }
  90 +
  91 + //忘记密码 校验验证码 {"verifyCode":"644528","phone":"13625644528"} 去除头部cookie 可以正常访问
  92 + checkVerifyCode(phone: string, verifyCode: string) {
  93 + let bean: Record<string, Object> = {};
  94 + bean['verifyCode'] = verifyCode
  95 + bean['phone'] = phone
  96 + let headers: HashMap<string, string> = HttpUrlUtils.getCommonHeaders();
  97 + return new Promise<CheckVerifyBean>((success, fail) => {
  98 + HttpRequest.post<ResponseDTO<CheckVerifyBean>>(HttpUrlUtils.getCheckVerifyCodeUrl(), bean, headers).then((data: ResponseDTO<CheckVerifyBean>) => {
  99 + Logger.debug("LoginViewModel:success2 ", data.message)
  100 + if (!data || !data.data) {
  101 + fail("数据为空")
  102 + return
  103 + }
  104 + if (data.code != 0) {
  105 + fail(data.message)
  106 + return
  107 + }
  108 + success(data.data)
  109 + }, (error: Error) => {
  110 + Logger.debug("LoginViewModel:error2 ", error.toString())
  111 + fail(error.message)
  112 + })
  113 + })
  114 + }
  115 +}
  116 +
  1 +import { Logger } from 'wdKit/src/main/ets/utils/Logger'
  2 +import { CustomProtocolDialog } from './CustomProtocolDialog'
  3 +import router from '@ohos.router'
  4 +import { LoginViewModel } from './LoginViewModel'
  5 +import { LoginInputComponent } from './LoginInputComponent'
  6 +import promptAction from '@ohos.promptAction'
  7 +import { SPHelper } from 'wdKit'
  8 +import { WDRouterPage } from 'wdRouter/src/main/ets/router/WDRouterPage';
  9 +import { WDRouterRule } from 'wdRouter/src/main/ets/router/WDRouterRule';
  10 +
  11 +@Extend(Row)
  12 +function otherStyle() {
  13 + .backgroundImageSize(ImageSize.Cover)
  14 + .layoutWeight(1)
  15 + .height("100%")
  16 + .justifyContent(FlexAlign.Center)
  17 + .alignItems(VerticalAlign.Center)
  18 +}
  19 +
  20 +function isEmpty(obj: undefined | string | null): boolean {
  21 + return (obj == undefined || obj == null || obj == '');
  22 +}
  23 +
  24 +const TAG = "LoginPage"
  25 +
  26 +@Preview
  27 +@Entry
  28 +@Component
  29 +struct LoginPage {
  30 + @State codeBtnState: boolean = false
  31 + @State timeCount: number = 60
  32 + phoneController: TextInputController = new TextInputController()
  33 + codeSendText = ""
  34 + @State @Watch('onCodeSend') isCodeSend: boolean = false //验证码是否发送
  35 + @State phoneContent: string = "" //手机号
  36 + @State codeContent: string = "" //验证码
  37 + @State protocolState: boolean = false //协议勾选状态
  38 + accountContent = '' //账号
  39 + passwordContent = ''
  40 + @State isSubmit: boolean = false
  41 + @State checkCodePage: boolean = true //判断是否是验证码页面 默认验证码登录
  42 + @State passwordSwitch: boolean = true //密码显示
  43 + // @State isPasswordSubmit: boolean = false //账户密码状态 是否出发登录
  44 +
  45 + dialogController: CustomDialogController = new CustomDialogController({
  46 + builder: CustomProtocolDialog({
  47 + cancel: () => {
  48 +
  49 + },
  50 + confirm: () => {
  51 + this.requestLogin()
  52 + }
  53 + }),
  54 + // alignment:DialogAlignment.Center
  55 + })
  56 + loginViewModel = new LoginViewModel()
  57 +
  58 + onCodeSend() {
  59 + Logger.debug(TAG, "isCodeSend:" + this.isCodeSend + "")
  60 + if (this.isCodeSend) {
  61 + this.sendVerifyCode()
  62 + }
  63 + }
  64 +
  65 + aboutToAppear() {
  66 + Logger.debug(TAG, "aboutToAppear:" + this.isCodeSend + "")
  67 + }
  68 +
  69 + onPageShow() {
  70 + Logger.debug(TAG, "onPageShow:" + this.isCodeSend + "")
  71 + }
  72 +
  73 + build() {
  74 + RelativeContainer() {
  75 +
  76 + //注册内容
  77 + Column() {
  78 + Image($r("app.media.login_logo"))
  79 + .width(120)
  80 + .height(66)
  81 + .margin({ top: 78 })
  82 + .align(Alignment.Center)
  83 +
  84 + if (this.checkCodePage) {
  85 + LoginInputComponent({
  86 + phoneContent: $phoneContent,
  87 + codeContent: $codeContent,
  88 + isSubmit: $isSubmit,
  89 + isCodeSend: $isCodeSend
  90 + })
  91 + } else {
  92 + this.addPassword()
  93 + }
  94 +
  95 +
  96 + Row() {
  97 + // Checkbox().selectedColor("#ED2800").onChange((value) => {
  98 + // this.protocolState = value
  99 + // })
  100 + Image(this.protocolState ? $r('app.media.login_checkbox_select') : $r('app.media.login_checkbox_unselected'))
  101 + .width(15)
  102 + .height(15)
  103 + .onClick(() => {
  104 + this.protocolState = !this.protocolState
  105 + })
  106 +
  107 + Text() {
  108 + Span("我已阅读并同意").fontColor("#999999").fontSize(12)
  109 + Span("《用户协议》").fontColor("#ED2800").fontSize(12).onClick(() => {
  110 + //todo 协议
  111 + })
  112 + Span("及").fontColor("#999999").fontSize(12)
  113 + Span("《隐私政策》").fontColor("#ED2800").fontSize(12).onClick(() => {
  114 + //todo 协议
  115 + })
  116 + }
  117 + }.margin({ top: 28 }).alignItems(VerticalAlign.Center)
  118 +
  119 + Row() {
  120 + Button("登录", { type: ButtonType.Normal })
  121 + .borderRadius(4)
  122 + .fontSize(20)
  123 + .fontWeight(FontWeight.Medium)
  124 + .margin({ top: 20 })
  125 + .height(44)
  126 + .opacity(this.isSubmit ? 1 : 0.6)
  127 + .enabled(this.isSubmit ? true : false)
  128 + .width("100%")
  129 + .backgroundColor("#ED2800")
  130 + .onClick(() => {
  131 + //todo 登录
  132 + this.loginSubmit()
  133 +
  134 + })
  135 + }.padding({ left: 25, right: 25 }).width('100%')
  136 +
  137 +
  138 + if (!this.checkCodePage) {
  139 + Text('忘记密码').fontColor('#666666').fontSize(14).margin({ top: 16 })
  140 + .onClick(() => {
  141 + // router.pushUrl({ url: 'pages/login/ForgetPasswordPage' })
  142 + WDRouterRule.jumpWithPage(WDRouterPage.forgetPasswordPage)
  143 + })
  144 + }
  145 +
  146 + }.width("100%")
  147 + .alignRules({
  148 + top: { anchor: "__container__", align: VerticalAlign.Top },
  149 + }).id("register")
  150 +
  151 + //其他登录方式
  152 + Column() {
  153 + this.addOtherLogin()
  154 + }.width('100%')
  155 + .alignRules({
  156 + bottom: { anchor: "__container__", align: VerticalAlign.Bottom },
  157 + }).id("other")
  158 +
  159 + //关闭按钮
  160 + Image($r('app.media.login_closed'))
  161 + .width(24)
  162 + .height(24)
  163 + .margin({ top: 10, right: 15 })
  164 + .alignRules({
  165 + top: { anchor: "__container__", align: VerticalAlign.Top },
  166 + right: { anchor: "__container__", align: HorizontalAlign.End }
  167 + })
  168 + .onClick(() => {
  169 + router.back()
  170 + })
  171 + .id('id_close')
  172 +
  173 + }.width('100%').height('100%')
  174 + }
  175 +
  176 + @Builder
  177 + addPassword() {
  178 + Column() {
  179 + TextInput({ placeholder: "请输入账号", controller: this.phoneController })
  180 + .fontSize(16)
  181 + .height(48)
  182 + .backgroundColor("#F5F5F5")
  183 + .borderRadius(4)
  184 + .type(InputType.PhoneNumber)
  185 + .onChange((content) => {
  186 + this.accountContent = content
  187 + this.isSubmit = (this.accountContent.length >= 11 && this.passwordContent.length >= 6)
  188 + })
  189 +
  190 + RelativeContainer() {
  191 + if (this.passwordSwitch) {
  192 + this.addPasswordInputLayout()
  193 + } else {
  194 + this.addPasswordInputLayout()
  195 + }
  196 +
  197 + Image(this.passwordSwitch ? $r('app.media.login_password_off') : $r('app.media.login_password_on'))
  198 + .onClick(() => {
  199 + this.passwordSwitch = !this.passwordSwitch
  200 + })
  201 + .width(24)
  202 + .height(24)
  203 + .alignRules({
  204 + right: { anchor: "__container__", align: HorizontalAlign.End },
  205 + center: { anchor: '__container__', align: VerticalAlign.Center }
  206 + })
  207 + .margin({ right: 12 })
  208 + .id("password_icon")
  209 +
  210 + }.margin({ top: 12 })
  211 + .height(61)
  212 + .width('100%')
  213 + }.padding({ left: 32, right: 32 }).width('100%').margin({ top: 46 })
  214 +
  215 + }
  216 +
  217 + @Builder
  218 + addPasswordInputLayout() {
  219 + TextInput({ placeholder: "请输入密码", text: this.passwordContent })
  220 + .layoutWeight(1)
  221 + .fontSize(16)
  222 + .height(48)
  223 + .fontColor("#222222")
  224 + .backgroundColor("#F5F5F5")
  225 + .type(this.passwordSwitch ? InputType.Password : InputType.Normal)
  226 + .showPasswordIcon(false)
  227 + .borderRadius({ topLeft: 4, bottomLeft: 4 })
  228 + .alignRules({
  229 + // top:{anchor:"__container__",align:VerticalAlign.Top} ,
  230 + left: { anchor: "__container__", align: HorizontalAlign.Start },
  231 +
  232 + })
  233 + .onChange((value) => {
  234 + // Logger.debug(TAG, "onChange" + value + "/" + this.passwordContent)
  235 + this.passwordContent = value
  236 + this.isSubmit = (this.accountContent.length >= 11 && this.passwordContent.length >= 6)
  237 + })
  238 + .id("password")
  239 + }
  240 +
  241 + @Builder
  242 + addOtherLogin() {
  243 + Column() {
  244 + Row() {
  245 + Divider().layoutWeight(1).margin({ left: 25 })
  246 + Text('其他登录方式').margin({ left: 16, right: 16 }).fontSize(14).fontColor("#666666")
  247 + Divider().layoutWeight(1).margin({ right: 25 })
  248 + }.width('100%')
  249 +
  250 + Row() {
  251 + Row() {
  252 + Image($r('app.media.login_wx'))
  253 + .width(20).height(20)
  254 + }.backgroundImage($r('app.media.login_other_left'), ImageRepeat.NoRepeat)
  255 + .otherStyle()
  256 +
  257 + Row() {
  258 + Image($r('app.media.login_qq')).size({ width: 20, height: 20 })
  259 + }.backgroundImage($r('app.media.login_other_middle'), ImageRepeat.NoRepeat)
  260 + .otherStyle()
  261 +
  262 + Row() {
  263 + Image($r('app.media.login_wb')).size({ width: 20, height: 20 })
  264 + }.backgroundImage($r('app.media.login_other_middle'), ImageRepeat.NoRepeat)
  265 + .otherStyle()
  266 +
  267 + Row() {
  268 + Image(this.checkCodePage ? $r('app.media.login_qt') : $r('app.media.login_other_password'))
  269 + .size({ width: 20, height: 20 })
  270 + }.backgroundImage($r('app.media.login_other_right'), ImageRepeat.NoRepeat)
  271 + .otherStyle().onClick(() => {
  272 + this.checkCodePage = !this.checkCodePage;
  273 + this.passwordContent = ''
  274 + this.passwordSwitch = true
  275 + this.isSubmit = false
  276 + })
  277 +
  278 + }.height(36)
  279 + .width('100%')
  280 + .padding({ left: 25, right: 25 })
  281 + // .justifyContent(FlexAlign.SpaceEvenly)
  282 + .margin({ top: 24 })
  283 + }.width('100%').margin({ bottom: 40 })
  284 + }
  285 +
  286 + //发送验证码
  287 + sendVerifyCode() {
  288 + this.loginViewModel.sendVerifyCode(this.phoneContent).then((verifyCode) => {
  289 + promptAction.showToast({ message: "验证码已发送成功" })
  290 + Logger.debug(TAG, "sendVerifyCode: " + verifyCode)
  291 + }).catch((message:string)=>{
  292 + Logger.debug(TAG, "sendVerifyCode: " + message)
  293 + })
  294 + }
  295 +
  296 + requestLogin() {
  297 + Logger.debug('LoginViewModel', "requestLogin")
  298 + if (this.checkCodePage) {
  299 + this.loginViewModel.appLogin(this.phoneContent, 2, this.codeContent).then((data) => {
  300 + Logger.debug(TAG, "requestLogin: " + data.jwtToken)
  301 + let dd = SPHelper.default.get('userName', 'dd').then((value) => {
  302 + Logger.debug(TAG, 'SP:' + value)
  303 + })
  304 + router.back()
  305 + })
  306 + } else {
  307 + this.loginViewModel.appLoginByPassword(this.accountContent, 0, this.passwordContent, "").then((data) => {
  308 + Logger.debug(TAG, "requestLogin: " + data.jwtToken)
  309 + let dd = SPHelper.default.get('userName', 'dd').then((value) => {
  310 + Logger.debug(TAG, 'SP:' + value)
  311 + })
  312 + promptAction.showToast({ message: '登录成功' })
  313 + router.back()
  314 + }).catch((value: string) => {
  315 + promptAction.showToast({ message: value })
  316 + })
  317 + }
  318 +
  319 + }
  320 +
  321 + //登录
  322 + loginSubmit() {
  323 + Logger.debug(TAG, "loginSubmit " + this.checkCodePage)
  324 + //todo 判断是验证码登录还是密码登录
  325 + if (this.checkCodePage) {
  326 + if (isEmpty(this.phoneContent)) {
  327 + Logger.debug(TAG, "手机号为空")
  328 + return
  329 + }
  330 + if (isEmpty(this.codeContent)) {
  331 + Logger.debug(TAG, "验证码为空")
  332 + return
  333 + }
  334 +
  335 + } else {
  336 + if (isEmpty(this.accountContent)) {
  337 + Logger.debug(TAG, "账号为空")
  338 + return
  339 + }
  340 + if (isEmpty(this.passwordContent)) {
  341 + Logger.debug(TAG, "密码为空")
  342 + return
  343 + }
  344 + }
  345 + if (this.protocolState) {
  346 + this.requestLogin()
  347 + } else {
  348 + this.dialogController.open()
  349 + }
  350 +
  351 + }
  352 +}
  1 +import { Logger } from 'wdKit/src/main/ets/utils/Logger'
  2 +import { LoginModel } from './LoginModel'
  3 +import { LoginBean } from './LoginBean'
  4 +import { SPHelper } from 'wdKit'
  5 +import { CheckVerifyBean } from './CheckVerifyBean'
  6 +import cryptoFramework from '@ohos.security.cryptoFramework'
  7 +import buffer from '@ohos.buffer'
  8 +
  9 +const TAG = "LoginViewModel"
  10 +
  11 +export class LoginViewModel {
  12 + loginModel: LoginModel
  13 +
  14 + constructor() {
  15 + this.loginModel = new LoginModel()
  16 + }
  17 +
  18 + //发送验证码
  19 + sendVerifyCode(number: string) {
  20 + return new Promise<string>((success, fail) => {
  21 + this.loginModel.sendVerifyCode(number).then((data) => {
  22 + success(data)
  23 + }).catch((message: string) => {
  24 + fail(message)
  25 + })
  26 + })
  27 +
  28 + }
  29 +
  30 + appLogin(phone: string, loginType: number, verificationCode: string) {
  31 +
  32 + return new Promise<LoginBean>((success, fail) => {
  33 + this.loginModel.appLogin(phone, loginType, verificationCode).then((data: LoginBean) => {
  34 + //todo 保存登录数据
  35 + SPHelper.default.save("firstMark", data.firstMark)
  36 + SPHelper.default.save("id", data.id)
  37 + SPHelper.default.save("jwtToken", data.jwtToken)
  38 + SPHelper.default.save("longTimeNoLoginMark", data.longTimeNoLoginMark)
  39 + SPHelper.default.save("refreshToken", data.refreshToken)
  40 + SPHelper.default.save("status", data.status)
  41 + SPHelper.default.save("userType", data.userType)
  42 + SPHelper.default.save("userName", data.userName)
  43 +
  44 + success(data)
  45 + }).catch(() => {
  46 + fail()
  47 + })
  48 + })
  49 + }
  50 +
  51 + async appLoginByPassword(phone: string, loginType: number, password: string, oldPassword: string) {
  52 +
  53 + return new Promise<LoginBean>(async (success, fail) => {
  54 + let passwordNew = await this.doMd(password)
  55 + Logger.debug(TAG, "PASSWORD:" + passwordNew)
  56 + this.loginModel.appLoginByPassword(phone, loginType, passwordNew, oldPassword).then((data: LoginBean) => {
  57 + //todo 保存登录数据
  58 + SPHelper.default.save("userName", data.userName)
  59 +
  60 + success(data)
  61 + }).catch((value: string) => {
  62 + fail(value)
  63 + })
  64 + })
  65 + }
  66 +
  67 + //{"code":"0","data":{"tempToken":"eyJhbGciOiJIUzI1NiIsImtpZCI6ImQ4WkI2QkhxSEZrdjJ2U25BNlRwZEdKRjBHcjItVzBvS2FaYzdLOUUycmcifQ.eyJpc3MiOiJwZW9wbGVzLWRhaWx5LWZvdXJhIiwic3ViIjoicGVvcGxlcy1kYWlseS1mb3VyYSIsImV4cCI6MTcxMDQ2ODE5NCwidXNlcklkIjpudWxsLCJ1c2VyVmVyc2lvbiI6Im51bGxfbnVsbCIsInVzZXJOYW1lIjpudWxsLCJ1c2VyVHlwZSI6bnVsbCwiY3JlYXRvcklkIjpudWxsLCJ1c2VySWRaaCI6bnVsbH0.R5gv44Gyni3QTxtWvSxYn0eMuUD5_bI1hh9TaThq25g","jwtToken":"eyJhbGciOiJIUzI1NiIsImtpZCI6ImQ4WkI2QkhxSEZrdjJ2U25BNlRwZEdKRjBHcjItVzBvS2FaYzdLOUUycmcifQ.eyJpc3MiOiJwZW9wbGVzLWRhaWx5LWZvdXJhIiwic3ViIjoicGVvcGxlcy1kYWlseS1mb3VyYSIsImV4cCI6MTcxMDY0NzU5NCwidXNlcklkIjo1NjkzNTAwNzk4ODk2NjksInVzZXJWZXJzaW9uIjoiNTY5MzUwMDc5ODg5NjY5XzAiLCJ1c2VyTmFtZSI6IiVFNCVCQSVCQSVFNiVCMCU5MSVFNiU5NyVBNSVFNiU4QSVBNSVFNyVCRCU5MSVFNSU4RiU4QjJrRDJ4VyIsInVzZXJUeXBlIjoxLCJjcmVhdG9ySWQiOm51bGwsInVzZXJJZFpoIjpudWxsfQ.Ai19vg8-rhJNXt2nwFTfir7s01eLfTtOCKcptIpeyG0"},"message":"Success","success":true,"timestamp":1710467595186}
  68 + checkVerifyCode(phone: string, verifyCode: string) {
  69 + return new Promise<CheckVerifyBean>((success, reject) => {
  70 + this.loginModel.checkVerifyCode(phone, verifyCode).then((data: CheckVerifyBean) => {
  71 + //todo 保存数据
  72 + SPHelper.default.save("tempToken", data.temToken)
  73 + SPHelper.default.save("jwtToken", data.jwtToken)
  74 + success(data)
  75 +
  76 + }, (value: string) => {
  77 + reject(value)
  78 + })
  79 + })
  80 + }
  81 +
  82 +
  83 + async doMd(content: string): Promise<string> {
  84 + let mdAlgName = 'SHA256'; // 摘要算法名
  85 + let message = content; // 待摘要的数据
  86 + let md = cryptoFramework.createMd(mdAlgName);
  87 + // 数据量较少时,可以只做一次update,将数据全部传入,接口未对入参长度做限制
  88 + await md.update({ data: new Uint8Array(buffer.from(message, 'utf-8').buffer) });
  89 + let mdResult = await md.digest();
  90 + console.info('Md result:' + mdResult.data);
  91 + return this.byte2Hex(mdResult.data)
  92 + }
  93 +
  94 + byte2Hex(data: Uint8Array): string {
  95 + let bufferStr = ''
  96 + for (let i = 0; i < data.length; i++) {
  97 + let temp = data[i].toString(16)
  98 + if (temp.length == 1) {
  99 + temp += "0"
  100 + }
  101 + bufferStr += temp
  102 +
  103 + }
  104 + console.info('Md result2:' + bufferStr);
  105 + return bufferStr;
  106 +
  107 + }
  108 +}
  1 +export function add(a:number, b:number) {
  2 + return a + b;
  3 +}
  1 +{
  2 + "module": {
  3 + "name": "wdLogin",
  4 + "type": "shared",
  5 + "description": "$string:shared_desc",
  6 + "deviceTypes": [
  7 + "phone",
  8 + "tablet",
  9 + "2in1"
  10 + ],
  11 + "deliveryWithInstall": true,
  12 + "pages": "$profile:main_pages"
  13 + }
  14 +}
  1 +{
  2 + "color": [
  3 + {
  4 + "name": "white",
  5 + "value": "#FFFFFF"
  6 + }
  7 + ]
  8 +}
  1 +{
  2 + "string": [
  3 + {
  4 + "name": "shared_desc",
  5 + "value": "description"
  6 + }
  7 + ]
  8 +}
  1 +{
  2 + "src": [
  3 + "pages/Index",
  4 + "pages/login/LoginPage",
  5 + "pages/login/ForgetPasswordPage"
  6 + ]
  7 +}
  1 +import localUnitTest from './LocalUnit.test';
  2 +
  3 +export default function testsuite() {
  4 + localUnitTest();
  5 +}
  1 +import { describe, beforeAll, beforeEach, afterEach, afterAll, it, expect } from '@ohos/hypium';
  2 +
  3 +export default function localUnitTest() {
  4 + describe('localUnitTest',() => {
  5 + // Defines a test suite. Two parameters are supported: test suite name and test suite function.
  6 + beforeAll(() => {
  7 + // Presets an action, which is performed only once before all test cases of the test suite start.
  8 + // This API supports only one parameter: preset action function.
  9 + });
  10 + beforeEach(() => {
  11 + // Presets an action, which is performed before each unit test case starts.
  12 + // The number of execution times is the same as the number of test cases defined by **it**.
  13 + // This API supports only one parameter: preset action function.
  14 + });
  15 + afterEach(() => {
  16 + // Presets a clear action, which is performed after each unit test case ends.
  17 + // The number of execution times is the same as the number of test cases defined by **it**.
  18 + // This API supports only one parameter: clear action function.
  19 + });
  20 + afterAll(() => {
  21 + // Presets a clear action, which is performed after all test cases of the test suite end.
  22 + // This API supports only one parameter: clear action function.
  23 + });
  24 + it('assertContain', 0, () => {
  25 + // Defines a test case. This API supports three parameters: test case name, filter parameter, and test case function.
  26 + let a = 'abc';
  27 + let b = 'b';
  28 + // Defines a variety of assertion methods, which are used to declare expected boolean conditions.
  29 + expect(a).assertContain(b);
  30 + expect(a).assertEqual(a);
  31 + });
  32 + });
  33 +}