• 宝安水环境管控平台(Ionic/Angular 移动端) 问题记录


    目录

    一. 登录前的验证码校验功能

    1. 添加滑动校验 Angular 组件

    1.1 verifySlipping.component.html

    1.2 verifySlipping.component.css

    1.3 verifySlipping.component.ts

    1.4 verify.js

    1.5 aes-encrypt-decrypt.ts

    1.6 verify.service.ts

    2. 添加登录时的验证码校验逻辑

    2.1 保持 loginService 服务为单例,避免路由守卫出 bug

    2.2 获取密钥时,Angular Http 请求发送失败 

    2.3 点击登录按钮,初始化验证码

    2.4 初始化验证码

    2.5 验证码校验成功后 执行真正的登录

    2.6 效果展示

    二. 修改密码功能

    1. 进入页面后,加密登录名,获取新密钥

    2. 校验密码,要求包含大小写字母、数字、特殊字符

    3. 修改密码,传参格式 FORM['USER_ID'] 的写法


    一. 登录前的验证码校验功能

    验证码校验功能参考开源项目 AJ-Captcha

    将验证码功能,嵌入你的项目里,需要添加以下依赖:

    yarn add crypto-js

    yarn add jwt-decode

    yarn add jquery

    1. 添加滑动校验 Angular 组件

    AJ-Captcha: 行为验证码(滑动拼图、点选文字),前后端(java)交互,包含vue/h5/Android/IOS/flutter/uni-app/react/php/微信小程序的源码和实现https://gitee.com/anji-plus/captcha

    三点说明:

    • 我下载了上面仓库中的 Angular 前端示例,使用 ng serve 运行项目
    • 此示例和我项目中的实际逻辑,区别较大,我几乎只用到了组件
    • 需要参考的前提:项目后端系统已经配备了相应后端接口

    下面介绍下验证码组件相关内容

    1.1 verifySlipping.component.html

    注释掉的部分:验证码提示框的头部,一般情况下用不上,会导致显示混乱

    此处需要注意两个ID,这俩 ID 在组件中会被 jquery 操作控制显隐:

    • mpanel1:用于盛放验证码图片、拼图、滑块之类的容器
    • slipping:用于标识这是 滑动校验组件,不是 点选文字校验组件

    文件地址:src\app\components\verify\verifySlipping.component.html

    1. <div
    2. class="modal fade"
    3. id="slipping"
    4. tabindex="-1"
    5. role="dialog"
    6. aria-labelledby="myLargeModalLabel"
    7. >
    8. <div class="modal-dialog modal-dialog-centered" role="document">
    9. <div class="modal-content">
    10. <div class="modal-body">
    11. <div class="box">
    12. <div class="verifybox-top">
    13. <div class="box">
    14. <div id="mpanel1">div>
    15. div>
    16. div>
    17. div>
    18. div>
    19. div>
    20. div>
    21. div>

    1.2 verifySlipping.component.css

    验证码弹框组件的基本样式

    注意:验证码弹框的宽度是写死的,这会导致屏幕小的手机,显示不全验证码弹框

    由于时间紧促,我没研究改哪些宽度百分比,而是直接使用了 scale(0.85) 缩小移动端弹框

    文件地址:src\app\components\verify\verifySlipping.component.css

    1. .btn {
    2. border: none;
    3. outline: none;
    4. width: 300px;
    5. height: 40px;
    6. line-height: 40px;
    7. text-align: center;
    8. cursor: pointer;
    9. background-color: #409eff;
    10. color: #fff;
    11. font-size: 16px;
    12. letter-spacing: 1em;
    13. }
    14. .modal-dialog {
    15. width: 466px;
    16. }

    global.scss 下,补充了全局验证码弹框样式

    之前打算使用 important 强行覆盖 jq 动态写入的验证码弹框宽度,后来因为要修改的地方太多,懒得改了,所以采用了 transform: translate(-50%, -50%) scale(0.85);

    文件地址:src\global.scss

    1. /**
    2. * 安全验证
    3. */
    4. .verify-code {
    5. font-size: 20px;
    6. text-align: center;
    7. cursor: pointer;
    8. margin-bottom: 5px;
    9. border: 1px solid #ddd;
    10. }
    11. .cerify-code-panel {
    12. height: 100%;
    13. overflow: hidden;
    14. }
    15. .verify-code-area {
    16. float: left;
    17. }
    18. .verify-input-area {
    19. float: left;
    20. width: 60%;
    21. padding-right: 10px;
    22. }
    23. .verify-change-area {
    24. line-height: 30px;
    25. float: left;
    26. }
    27. .varify-input-code {
    28. display: inline-block;
    29. width: 100%;
    30. height: 25px;
    31. }
    32. .verify-change-code {
    33. color: #337ab7;
    34. cursor: pointer;
    35. }
    36. .verify-btn {
    37. width: 200px;
    38. height: 30px;
    39. background-color: #337ab7;
    40. color: #ffffff;
    41. border: none;
    42. margin-top: 10px;
    43. }
    44. .verifybox {
    45. position: relative;
    46. box-sizing: border-box;
    47. border-radius: 2px;
    48. background-color: #fff;
    49. left: 50%;
    50. top: 50%;
    51. transform: translate(-50%, -50%) scale(0.85);
    52. }
    53. .verifybox-top {
    54. padding: 0 15px;
    55. /* height: 50px; */
    56. line-height: 50px;
    57. text-align: left;
    58. font-size: 16px;
    59. color: #45494c;
    60. box-sizing: border-box;
    61. }
    62. .verifybox-bottom {
    63. padding: 15px;
    64. box-sizing: border-box;
    65. }
    66. .verifybox-close {
    67. position: absolute;
    68. top: 13px;
    69. right: 9px;
    70. width: 24px;
    71. height: 24px;
    72. line-height: 24px;
    73. text-align: center;
    74. cursor: pointer;
    75. }
    76. .mask {
    77. position: fixed;
    78. top: 0;
    79. left: 0;
    80. z-index: 1001;
    81. width: 100%;
    82. height: 100vh;
    83. background: rgba(0, 0, 0, 0.3);
    84. /* display: none; */
    85. transition: all 0.5s;
    86. display: none;
    87. }
    88. .verify-tips {
    89. position: absolute;
    90. display: none;
    91. left: 0px;
    92. bottom: -35px;
    93. width: 100%;
    94. height: 30px;
    95. /* transition: all .5s; */
    96. line-height: 30px;
    97. color: #fff;
    98. /* animation:move 1.5s linear; */
    99. }
    100. @keyframes move {
    101. 0% {
    102. bottom: -35px;
    103. }
    104. 50%,
    105. 80% {
    106. bottom: 0px;
    107. }
    108. 100% {
    109. bottom: -35px;
    110. }
    111. }
    112. .suc-bg {
    113. background-color: rgba(92, 184, 92, 0.5);
    114. filter: progid:DXImageTransform.Microsoft.gradient(startcolorstr=#7f5CB85C, endcolorstr=#7f5CB85C);
    115. }
    116. .err-bg {
    117. background-color: rgba(217, 83, 79, 0.5);
    118. filter: progid:DXImageTransform.Microsoft.gradient(startcolorstr=#7fD9534F, endcolorstr=#7fD9534F);
    119. }
    120. /*滑动验证码*/
    121. .verify-bar-area {
    122. position: relative;
    123. background: #ffffff;
    124. text-align: center;
    125. -webkit-box-sizing: content-box;
    126. -moz-box-sizing: content-box;
    127. box-sizing: content-box;
    128. border: 1px solid #ddd;
    129. -webkit-border-radius: 4px;
    130. }
    131. .verify-bar-area .verify-move-block {
    132. position: absolute;
    133. top: 0px;
    134. left: 0;
    135. background: #fff;
    136. cursor: pointer;
    137. -webkit-box-sizing: content-box;
    138. -moz-box-sizing: content-box;
    139. box-sizing: content-box;
    140. box-shadow: 0 0 2px #888888;
    141. -webkit-border-radius: 1px;
    142. }
    143. .verify-bar-area .verify-move-block:hover {
    144. background-color: #337ab7;
    145. color: #ffffff;
    146. }
    147. .verify-bar-area .verify-left-bar {
    148. position: absolute;
    149. top: -1px;
    150. left: -1px;
    151. background: #f0fff0;
    152. cursor: pointer;
    153. -webkit-box-sizing: content-box;
    154. -moz-box-sizing: content-box;
    155. box-sizing: content-box;
    156. border: 1px solid #ddd;
    157. }
    158. .verify-img-panel {
    159. margin: 0;
    160. -webkit-box-sizing: content-box;
    161. -moz-box-sizing: content-box;
    162. box-sizing: content-box;
    163. border: 1px solid #ddd;
    164. border-radius: 3px;
    165. position: relative;
    166. }
    167. .verify-img-panel .verify-refresh {
    168. width: 25px;
    169. height: 25px;
    170. text-align: center;
    171. padding: 5px;
    172. cursor: pointer;
    173. position: absolute;
    174. top: 0;
    175. right: 0;
    176. z-index: 2;
    177. }
    178. .verify-img-panel .icon-refresh {
    179. font-size: 20px;
    180. color: #fff;
    181. }
    182. .verify-img-panel .verify-gap {
    183. background-color: #fff;
    184. position: relative;
    185. z-index: 2;
    186. border: 1px solid #fff;
    187. }
    188. .verify-bar-area .verify-move-block .verify-sub-block {
    189. position: absolute;
    190. text-align: center;
    191. z-index: 3;
    192. /* border: 1px solid #fff; */
    193. }
    194. .verify-bar-area .verify-move-block .verify-icon {
    195. font-size: 18px;
    196. }
    197. .verify-bar-area .verify-msg {
    198. z-index: 3;
    199. }
    200. /* 字体图标的css */
    201. @font-face {
    202. font-family: "iconfont";
    203. src: url("./assets/font/iconfont.eot?t=1508229193188"); /* IE9*/
    204. src: url("./assets/font/iconfont.eot?t=1508229193188#iefix")
    205. format("embedded-opentype"),
    206. /* IE6-IE8 */
    207. url("data:application/x-font-woff;charset=utf-8;base64,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")
    208. format("woff"),
    209. url("./assets/font/iconfont.ttf?t=1508229193188") format("truetype"),
    210. /* chrome, firefox, opera, Safari, Android, iOS 4.2+*/
    211. url("./assets/font/iconfont.svg?t=1508229193188#iconfont") format("svg"); /* iOS 4.1- */
    212. }
    213. .iconfont {
    214. font-family: "iconfont" !important;
    215. font-size: 16px;
    216. font-style: normal;
    217. -webkit-font-smoothing: antialiased;
    218. -moz-osx-font-smoothing: grayscale;
    219. }
    220. .icon-check:before {
    221. content: "\e645";
    222. }
    223. .icon-close:before {
    224. content: "\e646";
    225. }
    226. .icon-right:before {
    227. content: "\e6a3";
    228. }
    229. .icon-refresh:before {
    230. content: "\e6a4";
    231. }

    1.3 verifySlipping.component.ts

    官网示例中,初始化验证码是在组件里进行的

    但是,我的项目在 初始化验证码请求图片 时,需要传入额外的参数;因此,我注释了组件内的 初始化验证码 逻辑

    文件地址:src\app\components\verify\verifySlipping.component.ts

    1. import { Component } from '@angular/core';
    2. import './verify/verify.js';
    3. @Component({
    4. selector: 'verify-slipping',
    5. templateUrl: './verifySlipping.component.html',
    6. styleUrls: ['./verifySlipping.component.css'],
    7. })
    8. export class verifySlippingComponent {
    9. /**
    10. * 页面初始化
    11. */
    12. ngOnInit(): void {
    13. // 引入 promise
    14. if (!window.Promise) {
    15. document.writeln(
    16. '