zhangwei
2025-06-25 ce5e84197b43dec8c01717b116cb77535ad3c91e
src/views/login/index.vue
@@ -3,7 +3,15 @@
import { useRouter } from "vue-router";
import { message } from "@/utils/message";
import { loginRules } from "./utils/rule";
import { ref, reactive, toRaw } from "vue";
import {
  reactive,
  computed,
  ref,
  onMounted,
  defineAsyncComponent,
  onUnmounted,
  watch
} from "vue";
import { debounce } from "@pureadmin/utils";
import { useNav } from "@/layout/hooks/useNav";
import { useEventListener } from "@vueuse/core";
@@ -12,13 +20,17 @@
import { useUserStoreHook } from "@/store/modules/user";
import { initRouter, getTopMenu } from "@/router/utils";
import { bg, avatar, logo1 } from "./utils/static";
import { useRenderIcon } from "@/components/ReIcon/src/hooks";
// import { useRenderIcon } from "@/components/ReIcon/src/hooks";
import { useDataThemeChange } from "@/layout/hooks/useDataThemeChange";
import { useRoute } from "vue-router";
import dayIcon from "@/assets/svg/day.svg?component";
import darkIcon from "@/assets/svg/dark.svg?component";
const route = useRoute();
// import dayIcon from "@/assets/svg/day.svg?component";
// import darkIcon from "@/assets/svg/dark.svg?component";
import Lock from "~icons/ri/lock-fill";
import User from "~icons/ri/user-3-fill";
import { captcha, phoneNumberCode, exRole } from "@/api/register/index.ts";
defineOptions({
  name: "Login"
@@ -35,12 +47,114 @@
const { dataTheme, overallStyle, dataThemeChange } = useDataThemeChange();
dataThemeChange(overallStyle.value);
const { title } = useNav();
// 获取验证码
const getCaptcha = async () => {
  // if (!state.captchaEnabled) return;
const ruleForm = reactive({
  username: "admin",
  password: "admin123"
  state.ruleForm.code = "";
  const res = await captcha();
  console.log(res);
  state.captchaImage = "data:text/html;base64," + res.result?.img;
  state.expirySeconds = res.result?.expirySeconds;
  state.ruleForm.codeId = res.result?.id;
};
const state = reactive({
  isShowPassword: false,
  ruleForm: {
    account: "",
    nickName: "",
    phone: "",
    phoneVCode: "",
    // tenantId: props.tenantInfo.id,
    code: "",
    codeId: 0,
    email: "",
    exRoleCode: ""
  },
  rules: {
    code: [
      {
        required: true,
        message: "请输入手机验证码",
        trigger: "blur"
      }
    ],
    phone: [
      {
        required: true,
        message: "请输入您的手机号码",
        trigger: "blur"
      }
    ],
    exRoleCode: [
      {
        required: true,
        message: "请选择角色",
        trigger: "blur"
      }
    ]
    // code: [{ required: true, message: t('message.account.placeholder4'), trigger: 'blur' }],
  },
  loading: {
    signIn: false
  },
  captchaImage: "",
  rotateVerifyVisible: false,
  // rotateVerifyImg: verifyImg,
  // rotateVerifyImg: themeConfig.value.logoUrl,
  secondVerEnabled: false,
  // captchaEnabled: false,
  isPassRotate: false,
  capsLockVisible: false,
  hideTenantForLogin: false,
  expirySeconds: 60, // 验证码过期时间
  phoneSeconds: 0, // 手机验证码倒计时
  roleList: []
});
// 验证码过期计时器
let timer: any = null;
let phonetimer: any = null;
// 页面初始化
onMounted(async () => {
  // 若URL带有Token参数(第三方登录)
  const accessToken = route.query.token;
  // if (accessToken) await saveTokenAndInitRoutes(accessToken);
  // watch(
  //   () => themeConfig.value.isLoaded,
  //   isLoaded => {
  //     if (isLoaded) {
  // 获取登录配置
  // state.hideTenantForLogin = themeConfig.value.hideTenantForLogin ?? true;
  // state.secondVerEnabled = themeConfig.value.secondVer ?? true;
  // state.captchaEnabled = themeConfig.value.captcha ?? true;
  // 获取验证码
  getCaptcha();
  exRole().then(res => {
    state.roleList = res.result;
  });
  // 注册验证码过期计时器
  // if (state.captchaEnabled) {
  timer = setInterval(() => {
    if (state.expirySeconds > 0) state.expirySeconds -= 1;
  }, 1000);
  // }
  // }
  // },
  // { immediate: true }
  // );
});
// 页面卸载
onUnmounted(() => {
  // 销毁验证码过期计时器
  clearInterval(timer);
  timer = null;
  clearInterval(phonetimer);
  phonetimer = null;
});
const onLogin = async (formEl: FormInstance | undefined) => {
  if (!formEl) return;
  await formEl.validate(valid => {
@@ -48,30 +162,46 @@
      loading.value = true;
      useUserStoreHook()
        .loginByUsername({
          username: ruleForm.username,
          password: ruleForm.password
          phone: state.ruleForm.phone,
          code: state.ruleForm.phoneVCode,
          exRuleCode: state.ruleForm.exRoleCode
        })
        .then(res => {
          if (res.success) {
          if (res.code == 200) {
            // 获取后端路由
            return initRouter().then(() => {
              disabled.value = true;
              router
                .push(getTopMenu(true).path)
                .push(getTopMenu(true)?.path)
                .then(() => {
                  message("登录成功", { type: "success" });
                })
                .finally(() => (disabled.value = false));
            });
          } else {
            message("登录失败", { type: "error" });
            message(res?.message || "登录失败", { type: "error" });
          }
        })
        .finally(() => (loading.value = false));
    }
  });
};
const sendValidationCode = async () => {
  if (!state.ruleForm.phone) {
    return message("请先输入手机号", { type: "warning" });
  }
  if (!state.ruleForm.code) {
    return message("请先输入验证码", { type: "warning" });
  }
  const res = await phoneNumberCode(state.ruleForm);
  if (res?.code != 200) {
    return message(res?.message, { type: "warning" });
  }
  state.phoneSeconds = 60;
  phonetimer = setInterval(() => {
    if (state.phoneSeconds > 0) state.phoneSeconds -= 1;
  }, 1000);
};
const immediateDebounce: any = debounce(
  formRef => onLogin(formRef),
  1000,
@@ -117,32 +247,76 @@
          <el-form
            ref="ruleFormRef"
            :model="ruleForm"
            :model="state.ruleForm"
            :rules="loginRules"
            size="large"
          >
            <Motion :delay="150">
              <el-form-item prop="exRoleCode">
                <el-radio-group v-model="state.ruleForm.exRoleCode">
                  <el-radio
                    v-for="item in state.roleList"
                    :key="item.id"
                    :value="item.code"
                    >{{ item.name }}</el-radio
                  >
                </el-radio-group>
              </el-form-item>
            </Motion>
            <Motion :delay="100">
              <el-form-item
                :rules="[
                  {
                    required: true,
                    message: '请输入账号',
                    message: '请输入手机号',
                    trigger: 'blur'
                  }
                ]"
                prop="username"
                prop="phone"
              >
                <el-input
                  v-model="ruleForm.username"
                  v-model="state.ruleForm.phone"
                  clearable
                  placeholder="账号"
                  :prefix-icon="useRenderIcon(User)"
                  placeholder="手机号"
                />
              </el-form-item>
            </Motion>
            <Motion :delay="150">
              <el-form-item prop="password">
              <el-form-item class="login-animation3" prop="code">
                <el-col :span="15">
                  <el-input
                    ref="codeRef"
                    v-model="state.ruleForm.code"
                    text
                    maxlength="4"
                    placeholder="请输入验证码"
                    clearable
                    autocomplete="off"
                  />
                </el-col>
                <el-col :span="1" />
                <el-col :span="8">
                  <div
                    :class="[
                      state.expirySeconds > 0
                        ? 'login-content-code'
                        : 'login-content-code-expired'
                    ]"
                    @click="getCaptcha"
                  >
                    <img
                      class="login-content-code-img"
                      width="130px"
                      height="38px"
                      :src="state.captchaImage"
                      style="cursor: pointer"
                    />
                  </div>
                </el-col>
              </el-form-item>
              <!-- <el-form-item prop="password">
                <el-input
                  v-model="ruleForm.password"
                  clearable
@@ -150,9 +324,34 @@
                  placeholder="密码"
                  :prefix-icon="useRenderIcon(Lock)"
                />
              </el-form-item> -->
            </Motion>
            <Motion :delay="150">
              <el-form-item prop="phoneVCode">
                <el-input
                  v-model.number="state.ruleForm.phoneVCode"
                  class="form-input"
                  placeholder="请输入验证码"
                >
                  <template #suffix>
                    <span v-if="state.phoneSeconds == 0" id="suffix-span">
                      <span
                        id="suffix-span-2"
                        ref="spanRef"
                        @click="sendValidationCode(state.ruleForm.phone)"
                      >
                        获取验证码
                      </span>
                    </span>
                    <span v-else id="suffix-span">
                      <span id="suffix-span-2" ref="spanRef">
                        {{ state.phoneSeconds }}秒后重新获取
                      </span>
                    </span>
                  </template>
                </el-input>
              </el-form-item>
            </Motion>
            <Motion :delay="250">
              <el-button
                class="w-full mt-4!"
@@ -180,4 +379,7 @@
:deep(.el-input-group__append, .el-input-group__prepend) {
  padding: 0;
}
#suffix-span {
  cursor: pointer;
}
</style>