| | |
| | | <script setup lang="ts"> |
| | | <script setup lang="tsx"> |
| | | import Motion from "./utils/motion"; |
| | | 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, |
| | | h, |
| | | onMounted, |
| | | defineAsyncComponent, |
| | | onUnmounted, |
| | | watch |
| | | } from "vue"; |
| | | import { debounce } from "@pureadmin/utils"; |
| | | import { useNav } from "@/layout/hooks/useNav"; |
| | | import { useEventListener } from "@vueuse/core"; |
| | | import type { FormInstance } from "element-plus"; |
| | | import { |
| | | ElForm, |
| | | ElFormItem, |
| | | ElRadioGroup, |
| | | ElRadio, |
| | | ElRow, |
| | | ElTag |
| | | } from "element-plus"; |
| | | import { useLayout } from "@/layout/hooks/useLayout"; |
| | | 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"; |
| | | import Lock from "~icons/ri/lock-fill"; |
| | | import User from "~icons/ri/user-3-fill"; |
| | | const route = useRoute(); |
| | | import { captcha, phoneNumberCode, exRole } from "@/api/register/index"; |
| | | |
| | | defineOptions({ |
| | | name: "Login" |
| | |
| | | const { dataTheme, overallStyle, dataThemeChange } = useDataThemeChange(); |
| | | dataThemeChange(overallStyle.value); |
| | | const { title } = useNav(); |
| | | |
| | | const ruleForm = reactive({ |
| | | username: "admin", |
| | | password: "admin123" |
| | | // 获取验证码 |
| | | const getCaptcha = async () => { |
| | | // if (!state.captchaEnabled) return; |
| | | state.ruleForm.code = ""; |
| | | const res = await captcha(); |
| | | state.captchaImage = "data:text/html;base64," + res.result?.img; |
| | | state.expirySeconds = res.result?.expirySeconds; |
| | | state.ruleForm.codeId = res.result?.id; |
| | | }; |
| | | import { |
| | | addDialog, |
| | | closeDialog, |
| | | updateDialog, |
| | | closeAllDialog |
| | | } from "@/components/ReDialog"; |
| | | const state = reactive({ |
| | | isShowPassword: false, |
| | | ruleForm: { |
| | | account: "", |
| | | nickName: "", |
| | | phone: "", |
| | | phoneVCode: "", |
| | | code: "", |
| | | codeId: "", |
| | | 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: [], |
| | | myEnterpriseList: [], |
| | | nowRole: {} |
| | | }); |
| | | // 验证码过期计时器 |
| | | let timer: any = null; |
| | | let phonetimer: any = null; |
| | | |
| | | // 页面初始化 |
| | | onMounted(async () => { |
| | | getCaptcha(); |
| | | exRole().then(res => { |
| | | state.roleList = res.result; |
| | | const role = route.query; |
| | | if (role.code) { |
| | | state.nowRole = state.roleList.find(item => { |
| | | return item.code == role.code; |
| | | }); |
| | | } else { |
| | | state.nowRole = state.roleList[0]; |
| | | } |
| | | state.ruleForm.exRoleCode = state.nowRole.code; |
| | | }); |
| | | // 注册验证码过期计时器 |
| | | timer = setInterval(() => { |
| | | if (state.expirySeconds > 0) state.expirySeconds -= 1; |
| | | }, 1000); |
| | | }); |
| | | // 页面卸载 |
| | | onUnmounted(() => { |
| | | // 销毁验证码过期计时器 |
| | | clearInterval(timer); |
| | | timer = null; |
| | | clearInterval(phonetimer); |
| | | phonetimer = null; |
| | | }); |
| | | const openDialog = () => { |
| | | addDialog({ |
| | | width: "30%", |
| | | title: "选择登录公司", |
| | | contentRenderer: () => |
| | | h(ElForm, { |
| | | ref: ruleFormRef, |
| | | model: state.ruleForm, |
| | | rules: loginRules, |
| | | size: 'large' |
| | | }, [ |
| | | h(Motion, { delay: 150 }, [ |
| | | h(ElFormItem, { |
| | | prop: 'exRoleCode', |
| | | label: '主体角色' // 可添加表单项标签 |
| | | }, [ |
| | | // 单选框组 - 父容器添加样式控制 |
| | | h(ElRadioGroup, { |
| | | modelValue: state.ruleForm.exRoleCode, |
| | | 'onUpdate:modelValue': (val) => { |
| | | state.ruleForm.exRoleCode = val |
| | | }, |
| | | style: { |
| | | display: 'flex', |
| | | flexDirection: 'column', |
| | | gap: '8px' // 单选框之间的间距 |
| | | } |
| | | }, [ |
| | | // 循环渲染单选框 - 每个单选框单独占一行 |
| | | state.myEnterpriseList.map(item => h(ElRadio, { |
| | | key: item.id, |
| | | value: item.code, |
| | | style: { |
| | | display: 'block', // 让每个单选框分行显示 |
| | | padding: '4px 0' |
| | | } |
| | | }, item.name),) |
| | | ]), |
| | | |
| | | // 管理员标签 - 显示在单选框后面 |
| | | |
| | | ]) |
| | | ]) |
| | | ]) |
| | | // <> |
| | | // <el-form |
| | | // ref="ruleFormRef" |
| | | // 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.myEnterpriseList} |
| | | // key={item.id} |
| | | // value={item.code} |
| | | // > |
| | | // {item.name} |
| | | // </el-radio> |
| | | // </el-radio-group> |
| | | // </el-form-item> |
| | | // </Motion> |
| | | // </el-form> |
| | | // </> |
| | | // h( |
| | | // ElForm, |
| | | // { |
| | | // ref: ruleFormRef, |
| | | // model: state.ruleForm, |
| | | // rules: loginRules, |
| | | // size: "large" |
| | | // }, |
| | | // [ |
| | | // h(Motion, { delay: 150 }, [ |
| | | // h(ElFormItem, { prop: "exRoleCode" }, [ |
| | | // h( |
| | | // ElRadioGroup, |
| | | // { |
| | | // modelValue: state.ruleForm.exRoleCode, |
| | | // "onUpdate:modelValue": val => { |
| | | // state.ruleForm.exRoleCode = val; |
| | | // } |
| | | // }, |
| | | // [ |
| | | // // 用map实现v-for循环 |
| | | // state.myEnterpriseList.map(item => |
| | | // h(ElRow, {}, [ |
| | | // h( |
| | | // ElRadio, |
| | | // { |
| | | // key: item.customerUserID, |
| | | // value: item.customerUserID |
| | | // }, |
| | | // item.enterpriseName |
| | | // ), |
| | | // item.isManager ? 444 : "" // 标签内容 |
| | | // ]) |
| | | // ) |
| | | // ] |
| | | // ) |
| | | // ]) |
| | | // ]) |
| | | // ] |
| | | // ), |
| | | // jsx 语法 (注意在.vue文件启用jsx语法,需要在script开启lang="tsx") |
| | | closeCallBack: ({ options, args }) => { |
| | | // options.props 是响应式的 |
| | | // const { formInline } = options.props as FormProps; |
| | | // const text = `姓名:${formInline.user} 城市:${formInline.region}`; |
| | | if (args?.command === "cancel") { |
| | | // 您点击了取消按钮 |
| | | // active.value -= 1; |
| | | } else if (args?.command === "sure") { |
| | | let obj = { |
| | | phone: state.ruleForm.phone, |
| | | code: state.ruleForm.phoneVCode, |
| | | exRuleCode: state.ruleForm.exRoleCode |
| | | }; |
| | | useUserStoreHook() |
| | | .loginByUsername(obj) |
| | | .then(res => { |
| | | if (res?.code == 200) { |
| | | message("注册成功!", { type: "success" }); |
| | | router.replace({ |
| | | path: "/RegisterSucess", |
| | | query: { |
| | | code: obj.exRoleCode |
| | | } |
| | | }); |
| | | } else { |
| | | message(res?.message, { type: "warning" }); |
| | | } |
| | | }); |
| | | } else { |
| | | } |
| | | } |
| | | }); |
| | | }; |
| | | const onLogin = async (formEl: FormInstance | undefined) => { |
| | | if (!formEl) return; |
| | | await formEl.validate(valid => { |
| | | if (valid) { |
| | | let obj = { |
| | | phone: state.ruleForm.phone, |
| | | code: state.ruleForm.phoneVCode, |
| | | exRuleCode: state.ruleForm.exRoleCode |
| | | }; |
| | | loading.value = true; |
| | | useUserStoreHook() |
| | | .loginByUsername({ |
| | | username: ruleForm.username, |
| | | password: ruleForm.password |
| | | }) |
| | | .loginByUsername(obj) |
| | | .then(res => { |
| | | if (res.success) { |
| | | if (res.code == 200) { |
| | | // 获取后端路由 |
| | | return initRouter().then(() => { |
| | | disabled.value = true; |
| | | router |
| | | .push(getTopMenu(true).path) |
| | | .then(() => { |
| | | message("登录成功", { type: "success" }); |
| | | }) |
| | | .finally(() => (disabled.value = false)); |
| | | let obj = res.result; |
| | | if (obj.theLastLogo) { |
| | | router |
| | | .replace({ |
| | | name: "Index" |
| | | }) |
| | | .then(() => { |
| | | message("登录成功", { type: "success" }); |
| | | }) |
| | | .finally(() => (disabled.value = false)); |
| | | } else { |
| | | state.myEnterpriseList = obj.customerExs; |
| | | openDialog(); |
| | | } |
| | | if (res.result.exRoles.length == 0) { |
| | | disabled.value = true; |
| | | router |
| | | .replace({ |
| | | name: "RegisterNav", |
| | | query: { code: state.ruleForm.exRoleCode } |
| | | }) |
| | | .then(() => { |
| | | message("登录成功", { type: "success" }); |
| | | }) |
| | | .finally(() => (disabled.value = false)); |
| | | } else if (res.result.exRoles.length > 0) { |
| | | let data = res.result.exRoles.find(item => { |
| | | return item.code == state.ruleForm.exRoleCode; |
| | | }); |
| | | |
| | | if (!data) { |
| | | router.replace({ |
| | | name: "RegisterNav", |
| | | query: { code: state.ruleForm.exRoleCode } |
| | | }); |
| | | } else if (data?.hasFlsh) { |
| | | router.replace("index"); |
| | | } else { |
| | | router.replace("mine"); |
| | | } |
| | | } |
| | | useUserStoreHook().getCusExtendInfo(); |
| | | }); |
| | | } 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, |
| | |
| | | <img width="400px" :src="logo1" class="logo1" /> |
| | | </div> |
| | | <!-- <img :src="bg" class="wave" /> --> |
| | | <div class="flex-c absolute right-5 top-3"> |
| | | <!-- 主题 --> |
| | | <el-switch |
| | | <!-- <div class="flex-c absolute right-5 top-3"> --> |
| | | <!-- 主题 --> |
| | | <!-- <el-switch |
| | | v-model="dataTheme" |
| | | inline-prompt |
| | | :active-icon="dayIcon" |
| | | :inactive-icon="darkIcon" |
| | | @change="dataThemeChange" |
| | | /> |
| | | </div> |
| | | </div> --> |
| | | <div class="login-container"> |
| | | <div class="img"> |
| | | <!-- <component :is="toRaw(illustration)" /> --> |
| | |
| | | <div class="login-form"> |
| | | <!-- <avatar class="avatar" /> --> |
| | | <Motion> |
| | | <h2 class="outline-hidden">{{ title }}</h2> |
| | | <h2 class="logintitle">{{ state.nowRole.name }}登录</h2> |
| | | </Motion> |
| | | |
| | | <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 |
| | |
| | | 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!" |
| | |
| | | :deep(.el-input-group__append, .el-input-group__prepend) { |
| | | padding: 0; |
| | | } |
| | | #suffix-span { |
| | | cursor: pointer; |
| | | } |
| | | .login-content-code { |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: space-around; |
| | | position: relative; |
| | | |
| | | .login-content-code-img { |
| | | width: 100%; |
| | | height: 40px; |
| | | line-height: 40px; |
| | | background-color: #ffffff; |
| | | // /* border: 1px solid rgb(220, 223, 230); */ |
| | | cursor: pointer; |
| | | transition: all ease 0.2s; |
| | | border-radius: 4px; |
| | | user-select: none; |
| | | |
| | | &:hover { |
| | | border-color: #c0c4cc; |
| | | transition: all ease 0.2s; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .login-content-code-expired { |
| | | @extend .login-content-code; |
| | | &::before { |
| | | content: "验证码已过期"; |
| | | position: absolute; |
| | | top: 0; |
| | | left: 0; |
| | | right: 0; |
| | | bottom: 0; |
| | | border-radius: 4px; |
| | | background-color: rgba(0, 0, 0, 0.5); |
| | | color: #ffffff; |
| | | text-align: center; |
| | | line-height: 40px; |
| | | } |
| | | } |
| | | </style> |