zhengyiming
2 天以前 64eb1c2ebfc25f11f5757a0eef04de230fa8fa15
fix: 账号密码双因子登录
5个文件已修改
1个文件已添加
329 ■■■■■ 已修改文件
src/services/api/Account.ts 67 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/services/api/typings.d.ts 70 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/store/modules/user.ts 31 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/style/element/element-plus.scss 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/Login/Login.vue 57 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/Login/components/SendVerificationCodeView.vue 97 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/services/api/Account.ts
@@ -219,9 +219,17 @@
  });
}
/** 查询系统信息 GET /api/Account/GetSystemInfo */
export async function getSystemInfo(options?: API.RequestConfig) {
  return request<API.GetSystemInfoOutput>('/api/Account/GetSystemInfo', {
    method: 'GET',
    ...(options || {}),
  });
}
/** 电子签登录 POST /api/Account/GetTokenForUserSign */
export async function getTokenForUserSign(body: API.AccessRequestDto, options?: API.RequestConfig) {
  return request<API.IdentityModelTokenCacheItem>('/api/Account/GetTokenForUserSign', {
  return request<API.IdentityModelToken>('/api/Account/GetTokenForUserSign', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
@@ -233,7 +241,7 @@
/** 此处后端没有提供注释 POST /api/Account/GetTokenForWeb */
export async function getTokenForWeb(body: API.AccessRequestDto, options?: API.RequestConfig) {
  return request<API.IdentityModelTokenCacheItem>('/api/Account/GetTokenForWeb', {
  return request<API.IdentityModelToken>('/api/Account/GetTokenForWeb', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
@@ -313,7 +321,7 @@
/** 密码登录 POST /api/Account/PasswordLogin */
export async function passwordLogin(body: API.PasswordLoginInput, options?: API.RequestConfig) {
  return request<API.IdentityModelTokenCacheItem>('/api/Account/PasswordLogin', {
  return request<API.IdentityModelToken>('/api/Account/PasswordLogin', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
@@ -328,7 +336,7 @@
  body: API.PhoneMesssageCodeLoginInput,
  options?: API.RequestConfig
) {
  return request<API.IdentityModelTokenCacheItem>('/api/Account/PhoneMesssageCodeLogin', {
  return request<API.IdentityModelToken>('/api/Account/PhoneMesssageCodeLogin', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
@@ -395,6 +403,51 @@
  });
}
/** 双因子登录-第一步账号密码登录 POST /api/Account/TwoFactorLoginPassword */
export async function twoFactorLoginPassword(
  body: API.TwoFactorLoginPasswordInput,
  options?: API.RequestConfig
) {
  return request<API.TwoFactorLoginPasswordOutput>('/api/Account/TwoFactorLoginPassword', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
    },
    data: body,
    ...(options || {}),
  });
}
/** 双因子第二步-发送验证码 POST /api/Account/TwoFactorLoginSendVerificationCode */
export async function twoFactorLoginSendVerificationCode(
  body: API.TwoFactorLoginSendVerificationCodeInput,
  options?: API.RequestConfig
) {
  return request<number>('/api/Account/TwoFactorLoginSendVerificationCode', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
    },
    data: body,
    ...(options || {}),
  });
}
/** 双因子第三步-验证码登录 POST /api/Account/TwoFactorLoginSms */
export async function twoFactorLoginSms(
  body: API.TwoFactorLoginSmsInput,
  options?: API.RequestConfig
) {
  return request<API.IdentityModelToken>('/api/Account/TwoFactorLoginSms', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
    },
    data: body,
    ...(options || {}),
  });
}
/** 解绑用户邮箱 POST /api/Account/UnbindingUserEmail */
export async function unbindingUserEmail(
  body: API.UnbindingUserEmailInput,
@@ -445,7 +498,7 @@
  body: API.WxMiniAppPhoneLoginInput,
  options?: API.RequestConfig
) {
  return request<API.IdentityModelTokenCacheItem>('/api/Account/WxMiniAppPhoneAuthLogin', {
  return request<API.IdentityModelToken>('/api/Account/WxMiniAppPhoneAuthLogin', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
@@ -460,7 +513,7 @@
  body: API.WxMiniAppPhoneAuthLoginFromScanInput,
  options?: API.RequestConfig
) {
  return request<API.IdentityModelTokenCacheItem>('/api/Account/WxMiniAppPhoneAuthLoginFromScan', {
  return request<API.IdentityModelToken>('/api/Account/WxMiniAppPhoneAuthLoginFromScan', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
@@ -490,7 +543,7 @@
  body: API.WxMiniAppUserLoginFromScanInput,
  options?: API.RequestConfig
) {
  return request<API.IdentityModelTokenCacheItem>('/api/Account/WxMiniAppUserLoginFromScan', {
  return request<API.IdentityModelToken>('/api/Account/WxMiniAppUserLoginFromScan', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
src/services/api/typings.d.ts
@@ -6488,6 +6488,8 @@
  type EnumWalletSignStatus = 1 | 10 | 100 | 999;
  type EnumWalletTransactionStatus = 1 | 10 | 20 | 30 | 40 | 50;
  interface ExportBountyApplyData {
    /** 企业名称 */
    enterpriseName: string;
@@ -8004,6 +8006,13 @@
    amount?: number;
    remainAmount?: number;
    invoiceUrl?: string;
    transactionStatus?: EnumWalletTransactionStatus;
    /** 订单支付时间 */
    transactionDate?: string;
    /** 查询到的订单状态为FAIL失败或REFUND退票时,返回错误代码 */
    transactionErrorCode?: string;
    /** 查询到的订单状态为FAIL失败或REFUND退票时,返回具体的原因。 */
    transactionFailReason?: string;
    checkStatus?: EnterpriseRechargeStatusEnum;
    checkTime?: string;
    checkUserId?: string;
@@ -8034,7 +8043,15 @@
    parkType?: string;
    amount?: number;
    remainAmount?: number;
    transactionStatus?: EnumWalletTransactionStatus;
    /** 订单支付时间 */
    transactionDate?: string;
    /** 查询到的订单状态为FAIL失败或REFUND退票时,返回错误代码 */
    transactionErrorCode?: string;
    /** 查询到的订单状态为FAIL失败或REFUND退票时,返回具体的原因。 */
    transactionFailReason?: string;
    checkStatus?: EnterpriseRechargeStatusEnum;
    status?: GetEnterpriseDrawWithListOutputStatus;
    checkTime?: string;
    checkRemark?: string;
    checkFileUrl?: string;
@@ -8047,6 +8064,8 @@
    objectData?: any;
    data?: GetEnterpriseDrawWithListOutput[];
  }
  type GetEnterpriseDrawWithListOutputStatus = 10 | 20 | 21 | 22 | 30;
  interface GetEnterpriseMonthApplyFileOutput {
    id?: string;
@@ -9681,6 +9700,13 @@
    auditRemark?: string;
    /** 审核时间 */
    auditTime?: string;
    transactionStatus?: EnumWalletTransactionStatus;
    /** 订单支付时间 */
    transactionDate?: string;
    /** 查询到的订单状态为FAIL失败或REFUND退票时,返回错误代码 */
    transactionErrorCode?: string;
    /** 查询到的订单状态为FAIL失败或REFUND退票时,返回具体的原因。 */
    transactionFailReason?: string;
    financeAuditStatus?: EnumParkBountyTradeDetailAuditStatus;
    /** 财务审核备注 */
    financeAuditRemark?: string;
@@ -10617,6 +10643,10 @@
    signName?: string;
  }
  interface GetSystemInfoOutput {
    openTwoFactorLogin?: boolean;
  }
  interface GetTagsInput {
    /** 类型:0产品标签,1资讯标签,3快捷评论标签 */
    type?: number;
@@ -11549,6 +11579,15 @@
  interface IanaTimeZone {
    timeZoneName?: string;
  }
  interface IdentityModelToken {
    accessToken?: string;
    expiresIn?: number;
    creationTime?: string;
    refreshToken?: string;
    /** 用户Id */
    userId?: string;
  }
  interface IdentityModelTokenCacheItem {
@@ -22785,6 +22824,33 @@
  type TransferToStatusEnum = 1 | 2;
  interface TwoFactorLoginPasswordInput {
    clientId?: string;
    /** 登录名 */
    loginName: string;
    /** 登录密码 */
    password: string;
  }
  interface TwoFactorLoginPasswordOutput {
    /** 登录密钥 */
    loginKey?: string;
    /** 手机号 */
    phoneNumber?: string;
  }
  interface TwoFactorLoginSendVerificationCodeInput {
    /** 登录密钥 */
    loginKey?: string;
  }
  interface TwoFactorLoginSmsInput {
    /** 登录密钥 */
    loginKey?: string;
    /** 验证码 */
    code?: string;
  }
  interface TypeApiDescriptionModel {
    baseType?: string;
    isEnum?: boolean;
@@ -25929,6 +25995,8 @@
    iv: string;
    /** 获取会话密钥 */
    sessionKey: string;
    /** 鉴权 */
    sign?: string;
    /** 小程序OpenId */
    openId: string;
    wxMiniApp?: WxMiniAppEnum;
@@ -25941,6 +26009,8 @@
    iv: string;
    /** 获取会话密钥 */
    sessionKey: string;
    /** 鉴权 */
    sign?: string;
    /** 小程序OpenId */
    openId: string;
    wxMiniApp?: WxMiniAppEnum;
src/store/modules/user.ts
@@ -68,15 +68,7 @@
          .then((res) => {
            if (res) {
              console.log('res: ', res);
              this.setToken(res.accessToken);
              const accountInfo = getAccountInfoFromAccessToken(res.accessToken);
              this.setName(accountInfo.name);
              this.setAccountInfo(accountInfo);
              // 获取用户信息
              this.setUserInfo(res);
              this.loginSuccess(res);
              resolve();
            }
@@ -87,6 +79,27 @@
      });
    },
    async twoFactorLoginSms(params: API.TwoFactorLoginSmsInput) {
      try {
        let res = await accountServices.twoFactorLoginSms(params, { showLoading: false });
        if (res) {
          this.loginSuccess(res);
        }
      } catch (error) {}
    },
    loginSuccess(res: API.IdentityModelToken) {
      this.setToken(res.accessToken);
      const accountInfo = getAccountInfoFromAccessToken(res.accessToken);
      this.setName(accountInfo.name);
      this.setAccountInfo(accountInfo);
      // 获取用户信息
      this.setUserInfo(res);
    },
    // 登出 清空缓存
    logout(redirectPath = '/') {
      return new Promise(async (resolve) => {
src/style/element/element-plus.scss
@@ -129,3 +129,10 @@
    }
  }
}
.send-code-message-box {
  .el-message-box__container,
  .el-message-box__message {
    width: 100%;
  }
}
src/views/Login/Login.vue
@@ -137,6 +137,8 @@
import closeEye from '@/assets/svgIcons/close_eye.svg?component';
import openEye from '@/assets/svgIcons/close_eye.svg?component';
import * as accountServices from '@/services/api/Account';
import SendVerificationCodeView from './components/SendVerificationCodeView.vue';
// import { useSettingStoreHook } from '@/store/modules/settings';
import Config from '@config/config';
@@ -175,16 +177,44 @@
      return;
    }
    loading.value = true;
    await userStore.loginByUsername({
      userName: unref(user),
      userPassword: unref(pwd),
      clientId: 'goverend-admin-app-client',
    });
    loading.value = false;
    router.push({
      path: redirect.value || '/',
      query: otherQuery.value,
    });
    let systemInfo = await getSystemInfo();
    if (systemInfo.openTwoFactorLogin) {
      let twoFactorLoginPasswordRes = await accountServices.twoFactorLoginPassword({
        loginName: unref(user),
        password: unref(pwd),
        clientId: 'goverend-admin-app-client',
      });
      loading.value = false;
      ElMessageBox({
        title: `发送验证码到${twoFactorLoginPasswordRes.phoneNumber}`,
        customClass: 'send-code-message-box',
        //@ts-ignore
        modalClass: 'send-code-message-box-model',
        showConfirmButton: false,
        message: h(SendVerificationCodeView, {
          phoneNumber: twoFactorLoginPasswordRes.phoneNumber,
          loginKey: twoFactorLoginPasswordRes.loginKey,
          onSuccess: () => {
            router.push({
              path: redirect.value || '/',
              query: otherQuery.value,
            });
            document.querySelector('.send-code-message-box-model').remove();
          },
        }),
      });
    } else {
      await userStore.loginByUsername({
        userName: unref(user),
        userPassword: unref(pwd),
        clientId: 'goverend-admin-app-client',
      });
      loading.value = false;
      router.push({
        path: redirect.value || '/',
        query: otherQuery.value,
      });
    }
  } catch (error) {
    console.log(error);
    // ElMessage({
@@ -195,6 +225,13 @@
};
const beforeLog = useDebounceFn(onLogin, 1000);
async function getSystemInfo() {
  let res = await accountServices.getSystemInfo({
    showLoading: false,
  });
  return res;
}
function onUserFocus() {
  addClass(document.querySelector('.user'), 'focus');
}
src/views/Login/components/SendVerificationCodeView.vue
New file
@@ -0,0 +1,97 @@
<template>
  <ProForm :model="form" ref="formRef" class="el-message-box__input">
    <ProFormItemV2
      prop="code"
      :check-rules="[{ message: '请输入验证码' }]"
      label-width="0px"
      class="pro-form-item-label-hidden"
    >
      <ProFormText v-model.trim="form.code" placeholder="请输入验证码">
        <template #suffix>
          <ProFormCaptcha
            :onGetCaptcha="onGetCaptcha"
            phonePropName="loginKey"
            class="code-btn"
            link
          ></ProFormCaptcha>
        </template>
      </ProFormText>
    </ProFormItemV2>
    <div class="el-message-box__btns" style="padding-top: 0">
      <el-button type="primary" @click="handleConfirm" :loading="loading">确定</el-button>
    </div>
  </ProForm>
</template>
<script setup lang="ts">
import { ProForm, ProFormText, ProFormCaptcha, ProFormItemV2 } from '@bole-core/components';
import { FormInstance } from 'element-plus';
import * as accountServices from '@/services/api/Account';
import { useUserStore } from '@/store/modules/user';
defineOptions({
  name: 'SendVerificationCodeView',
});
type Props = {
  /** 登录密钥 */
  loginKey?: string;
  /** 手机号 */
  phoneNumber?: string;
};
const props = withDefaults(defineProps<Props>(), {});
const emit = defineEmits<{
  (e: 'success'): void;
}>();
const form = reactive({
  loginKey: props.loginKey,
  code: '',
});
const loading = ref(false);
async function onGetCaptcha() {
  await accountServices.twoFactorLoginSendVerificationCode(
    {
      loginKey: props.loginKey,
    },
    { showLoading: false }
  );
}
const formRef = ref<FormInstance>();
const handleConfirm = () => {
  if (!formRef.value) return;
  formRef.value.validate((valid) => {
    if (valid) {
      twoFactorLoginSms();
    } else {
      return;
    }
  });
};
const userStore = useUserStore();
async function twoFactorLoginSms() {
  try {
    loading.value = true;
    await userStore.twoFactorLoginSms({
      loginKey: props.loginKey,
      code: form.code,
    });
    emit('success');
  } catch (error) {
  } finally {
    loading.value = false;
  }
}
</script>
<style lang="scss" scoped>
@use '@/style/common.scss' as *;
</style>