login.component.ts 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366
  1. /*
  2. * Copyright [2022] [MaxKey of copyright http://www.maxkey.top]
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Inject, OnInit, OnDestroy, AfterViewInit, Optional } from '@angular/core';
  17. import { AbstractControl, FormBuilder, FormGroup, Validators } from '@angular/forms';
  18. import { Router, ActivatedRoute } from '@angular/router';
  19. import { throwIfAlreadyLoaded } from '@core';
  20. import { ReuseTabService } from '@delon/abc/reuse-tab';
  21. import { SettingsService, _HttpClient } from '@delon/theme';
  22. import { environment } from '@env/environment';
  23. import { NzSafeAny } from 'ng-zorro-antd/core/types';
  24. import { NzMessageService } from 'ng-zorro-antd/message';
  25. import { NzTabChangeEvent } from 'ng-zorro-antd/tabs';
  26. import { finalize } from 'rxjs/operators';
  27. import { AuthnService } from '../../../service/authn.service';
  28. import { ImageCaptchaService } from '../../../service/image-captcha.service';
  29. import { SocialsProviderService } from '../../../service/socials-provider.service';
  30. import { CONSTS } from '../../../shared/consts';
  31. import { stringify } from 'querystring';
  32. @Component({
  33. selector: 'passport-login',
  34. templateUrl: './login.component.html',
  35. styleUrls: ['./login.component.less'],
  36. changeDetection: ChangeDetectionStrategy.OnPush
  37. })
  38. export class UserLoginComponent implements OnInit, OnDestroy {
  39. socials: {
  40. providers: NzSafeAny[];
  41. qrScan: string;
  42. } = {
  43. providers: [],
  44. qrScan: ''
  45. };
  46. form: FormGroup;
  47. error = '';
  48. loginType = 'normal';
  49. loading = false;
  50. passwordVisible = false;
  51. imageCaptcha = '';
  52. captchaType = '';
  53. state = '';
  54. count = 0;
  55. interval$: any;
  56. constructor(
  57. fb: FormBuilder,
  58. private router: Router,
  59. private settingsService: SettingsService,
  60. private authnService: AuthnService,
  61. private socialsProviderService: SocialsProviderService,
  62. private imageCaptchaService: ImageCaptchaService,
  63. @Optional()
  64. @Inject(ReuseTabService)
  65. private reuseTabService: ReuseTabService,
  66. private route: ActivatedRoute,
  67. private msg: NzMessageService,
  68. private cdr: ChangeDetectorRef
  69. ) {
  70. this.form = fb.group({
  71. userName: [null, [Validators.required]],
  72. password: [null, [Validators.required]],
  73. captcha: [null, [Validators.required]],
  74. mobile: [null, [Validators.required, Validators.pattern(/^1\d{10}$/)]],
  75. otpCaptcha: [null, [Validators.required]],
  76. remember: [false]
  77. });
  78. }
  79. ngOnInit(): void {
  80. //set redirect_uri , is BASE64URL
  81. if (this.route.snapshot.queryParams[CONSTS.REDIRECT_URI]) {
  82. this.authnService.setRedirectUri(this.route.snapshot.queryParams[CONSTS.REDIRECT_URI]);
  83. }
  84. //congress login
  85. if (this.route.snapshot.queryParams[CONSTS.CONGRESS]) {
  86. this.congressLogin(this.route.snapshot.queryParams[CONSTS.CONGRESS]);
  87. }
  88. //init socials,state
  89. this.authnService.clear();
  90. this.authnService
  91. .get({ remember_me: localStorage.getItem(CONSTS.REMEMBER) })
  92. .pipe(
  93. finalize(() => {
  94. this.loading = false;
  95. this.cdr.detectChanges();
  96. })
  97. )
  98. .subscribe(res => {
  99. this.loading = true;
  100. if (res.code !== 0) {
  101. this.error = res.msg;
  102. } else {
  103. // 清空路由复用信息
  104. //console.log(res.data);
  105. //REMEMBER ME
  106. if (res.data.token) {
  107. // 清空路由复用信息
  108. this.reuseTabService.clear();
  109. // 设置用户Token信息
  110. this.authnService.auth(res.data);
  111. this.authnService.navigate({});
  112. } else {
  113. this.socials = res.data.socials;
  114. this.state = res.data.state;
  115. this.captchaType = res.data.captchaType;
  116. //init image captcha
  117. this.imageCaptchaService.captcha({ state: this.state, captcha: this.captchaType }).subscribe(res => {
  118. this.imageCaptcha = res.data.image;
  119. this.cdr.detectChanges();
  120. });
  121. }
  122. }
  123. });
  124. this.cdr.detectChanges();
  125. }
  126. congressLogin(congress: string) {
  127. this.authnService
  128. .congress({
  129. congress: congress
  130. })
  131. .pipe(
  132. finalize(() => {
  133. this.loading = false;
  134. this.cdr.detectChanges();
  135. })
  136. )
  137. .subscribe(res => {
  138. this.loading = true;
  139. if (res.code !== 0) {
  140. this.error = res.msg;
  141. } else {
  142. // 清空路由复用信息
  143. this.reuseTabService.clear();
  144. // 设置用户Token信息
  145. this.authnService.auth(res.data);
  146. this.authnService.navigate({});
  147. }
  148. });
  149. }
  150. // #region fields
  151. get userName(): AbstractControl {
  152. return this.form.get('userName')!;
  153. }
  154. get password(): AbstractControl {
  155. return this.form.get('password')!;
  156. }
  157. get mobile(): AbstractControl {
  158. return this.form.get('mobile')!;
  159. }
  160. get captcha(): AbstractControl {
  161. return this.form.get('captcha')!;
  162. }
  163. get otpCaptcha(): AbstractControl {
  164. return this.form.get('otpCaptcha')!;
  165. }
  166. get remember(): AbstractControl {
  167. return this.form.get('remember')!;
  168. }
  169. // #endregion
  170. // #region get captcha
  171. getImageCaptcha(): void {
  172. this.imageCaptchaService.captcha({ state: this.state, captcha: this.captchaType }).subscribe(res => {
  173. this.imageCaptcha = res.data.image;
  174. this.cdr.detectChanges();
  175. });
  176. }
  177. //send sms
  178. sendOtpCode(): void {
  179. if (this.mobile.invalid) {
  180. this.mobile.markAsDirty({ onlySelf: true });
  181. this.mobile.updateValueAndValidity({ onlySelf: true });
  182. return;
  183. }
  184. this.authnService.produceOtp({ mobile: this.mobile.value }).subscribe(res => {
  185. if (res.code !== 0) {
  186. this.msg.success(`发送失败`);
  187. }
  188. });
  189. this.count = 59;
  190. this.interval$ = setInterval(() => {
  191. this.count -= 1;
  192. if (this.count <= 0) {
  193. clearInterval(this.interval$);
  194. }
  195. this.cdr.detectChanges();
  196. }, 1000);
  197. }
  198. // #endregion
  199. submit(): void {
  200. this.error = '';
  201. if (this.loginType === 'normal') {
  202. this.userName.markAsDirty();
  203. this.userName.updateValueAndValidity();
  204. this.password.markAsDirty();
  205. this.password.updateValueAndValidity();
  206. this.captcha.markAsDirty();
  207. this.captcha.updateValueAndValidity();
  208. if (this.userName.invalid || this.password.invalid || this.captcha.invalid) {
  209. return;
  210. }
  211. } else {
  212. this.mobile.markAsDirty();
  213. this.mobile.updateValueAndValidity();
  214. this.otpCaptcha.markAsDirty();
  215. this.otpCaptcha.updateValueAndValidity();
  216. if (this.mobile.invalid || this.otpCaptcha.invalid) {
  217. return;
  218. }
  219. }
  220. localStorage.setItem(CONSTS.REMEMBER, this.form.get(CONSTS.REMEMBER)?.value);
  221. this.loading = true;
  222. this.cdr.detectChanges();
  223. this.authnService
  224. .login({
  225. authType: this.loginType,
  226. state: this.state,
  227. username: this.userName.value,
  228. password: this.password.value,
  229. captcha: this.captcha.value,
  230. mobile: this.mobile.value,
  231. otpCaptcha: this.otpCaptcha.value,
  232. remeberMe: this.remember.value
  233. })
  234. .pipe(
  235. finalize(() => {
  236. this.loading = false;
  237. this.cdr.detectChanges();
  238. })
  239. )
  240. .subscribe(res => {
  241. this.loading = true;
  242. if (res.code !== 0) {
  243. this.error = '登录失败,请重新登录'; //res.msg;
  244. //this.msg.success(`登录失败,请重新登录!`);
  245. if (this.loginType === 'normal') {
  246. this.getImageCaptcha();
  247. }
  248. } else {
  249. // 清空路由复用信息
  250. this.reuseTabService.clear();
  251. // 设置用户Token信息
  252. this.authnService.auth(res.data);
  253. this.authnService.navigate({});
  254. }
  255. this.cdr.detectChanges();
  256. });
  257. }
  258. // #region social
  259. socialauth(provider: string): void {
  260. this.socialsProviderService.authorize(provider).subscribe(res => {
  261. //console.log(res.data);
  262. window.location.href = res.data;
  263. });
  264. }
  265. getQrCode(): void {
  266. this.authnService.clearUser();
  267. this.socialsProviderService.scanqrcode(this.socials.qrScan).subscribe(res => {
  268. if (res.code === 0) {
  269. if (this.socials.qrScan === 'workweixin') {
  270. this.qrScanWorkweixin(res.data);
  271. } else if (this.socials.qrScan === 'dingtalk') {
  272. this.qrScanDingtalk(res.data);
  273. } else if (this.socials.qrScan === 'feishu') {
  274. this.qrScanFeishu(res.data);
  275. }
  276. }
  277. });
  278. }
  279. // #endregion
  280. ngOnDestroy(): void {
  281. if (this.interval$) {
  282. clearInterval(this.interval$);
  283. }
  284. }
  285. // #region QR Scan for workweixin, dingtalk ,feishu
  286. qrScanWorkweixin(data: any) {
  287. //see doc https://developer.work.weixin.qq.com/document/path/91025
  288. // @ts-ignore
  289. let wwLogin = new WwLogin({
  290. id: 'div_qrcodelogin',
  291. appid: data.clientId,
  292. agentid: data.agentId,
  293. redirect_uri: encodeURIComponent(data.redirectUri),
  294. state: data.state,
  295. href: 'data:text/css;base64,LmltcG93ZXJCb3ggLnFyY29kZSB7d2lkdGg6IDI1MHB4O30NCi5pbXBvd2VyQm94IC50aXRsZSB7ZGlzcGxheTogbm9uZTt9DQouaW1wb3dlckJveCAuaW5mbyB7d2lkdGg6IDI1MHB4O30NCi5zdGF0dXNfaWNvbiB7ZGlzcGxheTpub25lfQ0KLmltcG93ZXJCb3ggLnN0YXR1cyB7dGV4dC1hbGlnbjogY2VudGVyO30='
  296. });
  297. }
  298. qrScanFeishu(data: any) {
  299. //see doc https://open.feishu.cn/document/common-capabilities/sso/web-application-sso/qr-sdk-documentation
  300. //remove old div
  301. var qrcodeDiv = document.querySelector('#div_qrcodelogin');
  302. qrcodeDiv?.childNodes.forEach(function (value, index, array) {
  303. qrcodeDiv?.removeChild(value);
  304. });
  305. // @ts-ignore
  306. fsredirectUri = `https://passport.feishu.cn/suite/passport/oauth/authorize?client_id=${data.clientId}&redirect_uri=${encodeURIComponent(
  307. data.redirectUri
  308. )}&response_type=code&state=${data.state}`;
  309. // @ts-ignore
  310. var redirectUri = fsredirectUri;
  311. // @ts-ignore
  312. QRLoginObj = QRLogin({
  313. id: 'div_qrcodelogin',
  314. goto: redirectUri,
  315. width: '300',
  316. height: '300',
  317. style: 'border: 0;'
  318. });
  319. }
  320. qrScanDingtalk(data: any) {
  321. //see doc https://open.dingtalk.com/document/isvapp-server/scan-qr-code-to-log-on-to-third-party-websites
  322. var url = encodeURIComponent(data.redirectUri);
  323. var gotodingtalk = encodeURIComponent(
  324. `https://oapi.dingtalk.com/connect/oauth2/sns_authorize?appid=${data.clientId}&response_type=code&scope=snsapi_login&state=${data.state}&redirect_uri=${url}`
  325. );
  326. // @ts-ignore
  327. ddredirect_uri = `https://oapi.dingtalk.com/connect/oauth2/sns_authorize?appid=${data.clientId}&response_type=code&scope=snsapi_login&state=${data.state}&redirect_uri=${data.redirectUri}`;
  328. // @ts-ignore
  329. var obj = DDLogin({
  330. id: 'div_qrcodelogin', //这里需要你在自己的页面定义一个HTML标签并设置id,例如<div id="login_container"></div>或<span id="login_container"></span>
  331. goto: gotodingtalk, //请参考注释里的方式
  332. style: 'border:none;background-color:#FFFFFF;',
  333. width: '360',
  334. height: '400'
  335. });
  336. }
  337. // #region QR Scan end
  338. }