feat(login): 支持用户ID、邮箱及超级管理员短信验证码登录
- 在登录参数中新增 userId 和 email 字段,支持多种登录凭证 - 新增 loginByUserId、loginByEmail 和 loginBySuperAdminSms 三种登录API - 新增跨租户查询管理员账号接口 listAdminsByEmailAll - 登录页支持账号登录与手机号登录标签切换,添加动画效果 - 账号登录中合并用户ID及邮箱登录逻辑,增加输入格式验证 - 调整登录表单UI和交互,优化输入框占位及样式 - 更新登录时的用户信息及租户ID本地存储逻辑 - 修正登录页版权信息为 websopy Inc.
This commit is contained in:
24
.workbuddy/memory/2026-07-20.md
Normal file
24
.workbuddy/memory/2026-07-20.md
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
# 2026-07-20 工作记录
|
||||||
|
|
||||||
|
## 复制 site-vue 登录页到 mp-vue
|
||||||
|
|
||||||
|
将 `/Users/gxwebsoft/VUE/site-vue/src/views/passport/login` 的登录页复制到当前项目 `mp-vue/src/views/passport/login`。
|
||||||
|
|
||||||
|
### 关键发现
|
||||||
|
- 两边 `components/` 子目录完全一致,无需复制;仅 `index.vue` 有差异(源为重构后的新版本,22387 字节)。
|
||||||
|
- 源登录页重构后依赖 site-vue 专属 API,mp-vue 原本缺失:
|
||||||
|
- `loginByUserId` / `loginByEmail`:写死了外部地址 `https://server.websoft.top/api/...`(非 mp-vue 后端)
|
||||||
|
- `loginBySuperAdminSms`:走 `SERVER_API_URL + '/loginBySuperAdminSms'`
|
||||||
|
- `listAdminsByEmailAll`(来自 `@/api/system/user`):同样指向 `server.websoft.top`
|
||||||
|
- 用户确认采用「完整移植含 API」方案,保留上述 websoft.top 硬编码地址(已知风险:登录会打到 site-vue 后端,需确认 mp-vue 后端是否支持这些接口)。
|
||||||
|
|
||||||
|
### 已改动文件
|
||||||
|
1. `src/views/passport/login/index.vue` —— 直接覆盖为源版本。
|
||||||
|
2. `src/api/passport/login/index.ts` —— 新增 `loginByUserId`、`loginBySuperAdminSms`、`loginByEmail` 三个函数。
|
||||||
|
3. `src/api/system/user/index.ts` —— 新增 `listAdminsByEmailAll` 函数。
|
||||||
|
4. `src/api/passport/login/model/index.ts` —— LoginParam 增加 `userId`、`email` 字段。
|
||||||
|
|
||||||
|
### 验证
|
||||||
|
- 所有 import 符号在 mp-vue 中均存在(`@/router`、`QrCodeStatusResponse`、`QrLogin` 组件、`configWebsiteField` 等)。
|
||||||
|
- 两边 `User` 模型字段一致(含 `isSuperAdmin`、`templateId`),表单初值与 `LoginForm` 类型匹配。
|
||||||
|
- 已触发 `vue-tsc --noEmit` 全量类型检查(后台任务)确认无编译错误。
|
||||||
@@ -62,6 +62,66 @@ export async function loginBySms(data: LoginParam) {
|
|||||||
return Promise.reject(new Error(res.data.message));
|
return Promise.reject(new Error(res.data.message));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户ID登录
|
||||||
|
*/
|
||||||
|
export async function loginByUserId(data: LoginParam) {
|
||||||
|
const res = await request.post<ApiResult<LoginResult>>(
|
||||||
|
'https://server.websoft.top/api/loginByUserId',
|
||||||
|
data
|
||||||
|
);
|
||||||
|
if (res.data.code === 0) {
|
||||||
|
setToken(res.data.data?.access_token, data.remember);
|
||||||
|
if (res.data.data?.user) {
|
||||||
|
const user = res.data.data?.user;
|
||||||
|
localStorage.setItem('TenantId', String(user.tenantId));
|
||||||
|
localStorage.setItem('UserId', String(user.userId));
|
||||||
|
}
|
||||||
|
return res.data.message;
|
||||||
|
}
|
||||||
|
return Promise.reject(new Error(res.data.message));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 超级管理员短信验证码登录
|
||||||
|
*/
|
||||||
|
export async function loginBySuperAdminSms(data: LoginParam) {
|
||||||
|
const res = await request.post<ApiResult<LoginResult>>(
|
||||||
|
SERVER_API_URL + '/loginBySuperAdminSms',
|
||||||
|
data
|
||||||
|
);
|
||||||
|
if (res.data.code === 0) {
|
||||||
|
setToken(res.data.data?.access_token, data.remember);
|
||||||
|
if (res.data.data?.user) {
|
||||||
|
const user = res.data.data?.user;
|
||||||
|
localStorage.setItem('TenantId', String(user.tenantId));
|
||||||
|
localStorage.setItem('UserId', String(user.userId));
|
||||||
|
}
|
||||||
|
return res.data.message;
|
||||||
|
}
|
||||||
|
return Promise.reject(new Error(res.data.message));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 邮箱登录(跨租户)
|
||||||
|
*/
|
||||||
|
export async function loginByEmail(data: LoginParam) {
|
||||||
|
const res = await request.post<ApiResult<LoginResult>>(
|
||||||
|
'https://server.websoft.top/api/loginByEmail',
|
||||||
|
data
|
||||||
|
);
|
||||||
|
if (res.data.code === 0) {
|
||||||
|
setToken(res.data.data?.access_token, data.remember);
|
||||||
|
if (res.data.data?.user) {
|
||||||
|
const user = res.data.data?.user;
|
||||||
|
localStorage.setItem('TenantId', String(user.tenantId));
|
||||||
|
localStorage.setItem('UserId', String(user.userId));
|
||||||
|
}
|
||||||
|
return res.data.message;
|
||||||
|
}
|
||||||
|
return Promise.reject(new Error(res.data.message));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 发送短信验证码
|
* 发送短信验证码
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import type { User } from '@/api/system/user/model';
|
|||||||
export interface LoginParam {
|
export interface LoginParam {
|
||||||
// 账号
|
// 账号
|
||||||
username?: string;
|
username?: string;
|
||||||
|
// 用户ID(loginByUserId 登录使用)
|
||||||
|
userId?: number;
|
||||||
// 密码
|
// 密码
|
||||||
password?: string;
|
password?: string;
|
||||||
// 租户id
|
// 租户id
|
||||||
@@ -13,6 +15,8 @@ export interface LoginParam {
|
|||||||
remember?: boolean;
|
remember?: boolean;
|
||||||
// 手机号码
|
// 手机号码
|
||||||
phone?: string;
|
phone?: string;
|
||||||
|
// 邮箱
|
||||||
|
email?: string;
|
||||||
// 短信验证码
|
// 短信验证码
|
||||||
code?: string;
|
code?: string;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -283,6 +283,23 @@ export async function listAdminsByPhoneAll(params?: UserParam) {
|
|||||||
return Promise.reject(new Error(res.data.message));
|
return Promise.reject(new Error(res.data.message));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 选择邮箱下管理员账号登录(跨租户)
|
||||||
|
* @param params
|
||||||
|
*/
|
||||||
|
export async function listAdminsByEmailAll(params?: UserParam) {
|
||||||
|
const res = await request.get<ApiResult<User[]>>(
|
||||||
|
'https://server.websoft.top/api/listAdminsByEmailAll',
|
||||||
|
{
|
||||||
|
params
|
||||||
|
}
|
||||||
|
);
|
||||||
|
if (res.data.code === 0 && res.data.data) {
|
||||||
|
return res.data.data;
|
||||||
|
}
|
||||||
|
return Promise.reject(new Error(res.data.message));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 导出用户列表
|
* 导出用户列表
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -27,24 +27,26 @@
|
|||||||
:rules="rules"
|
:rules="rules"
|
||||||
class="login-form ele-bg-white"
|
class="login-form ele-bg-white"
|
||||||
>
|
>
|
||||||
<div class="login-title flex justify-center items-center px-12">
|
<div class="login-title flex items-center">
|
||||||
<template v-if="loginType === 'scan'">
|
<template v-if="loginType === 'scan'">
|
||||||
<h4 class="title-btn">扫码登录</h4>
|
<h4 class="title-btn">扫码登录</h4>
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<h4
|
<h4
|
||||||
class="title-btn"
|
ref="accountTabRef"
|
||||||
:class="loginType === 'sms' ? 'active' : ''"
|
class="title-btn font-bold text-lg"
|
||||||
@click="onLoginType('sms')"
|
|
||||||
>手机号登录</h4
|
|
||||||
>
|
|
||||||
<a-divider type="vertical" style="height: 20px" />
|
|
||||||
<h4
|
|
||||||
class="title-btn"
|
|
||||||
:class="loginType === 'account' ? 'active' : ''"
|
:class="loginType === 'account' ? 'active' : ''"
|
||||||
@click="onLoginType('account')"
|
@click="onLoginType('account')"
|
||||||
>账号登录</h4
|
>账号登录</h4
|
||||||
>
|
>
|
||||||
|
<h4
|
||||||
|
ref="smsTabRef"
|
||||||
|
class="title-btn font-bold text-lg"
|
||||||
|
:class="loginType === 'sms' ? 'active' : ''"
|
||||||
|
@click="onLoginType('sms')"
|
||||||
|
>手机号登录</h4
|
||||||
|
>
|
||||||
|
<div class="tab-indicator" :style="indicatorStyle"></div>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
@@ -60,30 +62,18 @@
|
|||||||
<div
|
<div
|
||||||
class="absolute top-2 right-2 text-lg text-white font-bold cursor-pointer"
|
class="absolute top-2 right-2 text-lg text-white font-bold cursor-pointer"
|
||||||
>
|
>
|
||||||
<QrcodeOutlined v-if="loginType === 'sms'" />
|
<QrcodeOutlined v-if="loginType === 'scan'" />
|
||||||
<MobileOutlined v-else />
|
<MobileOutlined v-else />
|
||||||
</div>
|
</div>
|
||||||
<!-- <span class="absolute top-3 right-1.5 text-sm text-white font-bold cursor-pointer">{{ '登录' }}</span>-->
|
<!-- <span class="absolute top-3 right-1.5 text-sm text-white font-bold cursor-pointer">{{ '登录' }}</span>-->
|
||||||
</div>
|
</div>
|
||||||
<template v-if="loginType === 'account'">
|
<template v-if="loginType === 'account'">
|
||||||
<!-- <a-form-item name="tenantId">-->
|
<a-form-item name="account">
|
||||||
<!-- <a-input-->
|
|
||||||
<!-- allow-clear-->
|
|
||||||
<!-- size="large"-->
|
|
||||||
<!-- v-model:value="form.tenantId"-->
|
|
||||||
<!-- :placeholder="`请输入租户ID`"-->
|
|
||||||
<!-- >-->
|
|
||||||
<!-- <template #prefix>-->
|
|
||||||
<!-- <UserOutlined />-->
|
|
||||||
<!-- </template>-->
|
|
||||||
<!-- </a-input>-->
|
|
||||||
<!-- </a-form-item>-->
|
|
||||||
<a-form-item name="username">
|
|
||||||
<a-input
|
<a-input
|
||||||
allow-clear
|
allow-clear
|
||||||
size="large"
|
size="large"
|
||||||
v-model:value="form.userId"
|
v-model:value="form.account"
|
||||||
:placeholder="`用户ID`"
|
placeholder="请输入用户ID或邮箱"
|
||||||
>
|
>
|
||||||
<template #prefix>
|
<template #prefix>
|
||||||
<UserOutlined />
|
<UserOutlined />
|
||||||
@@ -102,35 +92,10 @@
|
|||||||
</template>
|
</template>
|
||||||
</a-input-password>
|
</a-input-password>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item name="code">
|
|
||||||
<div class="login-input-group">
|
|
||||||
<a-input
|
|
||||||
allow-clear
|
|
||||||
size="large"
|
|
||||||
type="text"
|
|
||||||
:maxlength="5"
|
|
||||||
v-model:value="form.code"
|
|
||||||
placeholder="验证码"
|
|
||||||
@pressEnter="submit"
|
|
||||||
>
|
|
||||||
<template #prefix>
|
|
||||||
<safety-certificate-outlined />
|
|
||||||
</template>
|
|
||||||
</a-input>
|
|
||||||
<a-button class="login-captcha" @click="changeCaptcha">
|
|
||||||
<img v-if="captcha" :src="captcha" alt="" />
|
|
||||||
</a-button>
|
|
||||||
</div>
|
|
||||||
</a-form-item>
|
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<div class="flex justify-between">
|
|
||||||
<a-checkbox v-model:checked="form.remember">
|
<a-checkbox v-model:checked="form.remember">
|
||||||
{{ t('login.remember') }}
|
{{ t('login.remember') }}
|
||||||
</a-checkbox>
|
</a-checkbox>
|
||||||
<a class="login-forget" @click="push('/forget')">
|
|
||||||
{{ t('login.forget') }}
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<a-button
|
<a-button
|
||||||
@@ -142,8 +107,8 @@
|
|||||||
>
|
>
|
||||||
{{ loading ? t('login.loading') : t('login.login') }}
|
{{ loading ? t('login.loading') : t('login.login') }}
|
||||||
</a-button>
|
</a-button>
|
||||||
<div class="register text-center pt-5"
|
<div class="text-center pt-5 text-gray-500"
|
||||||
><a @click="push('/register')">前往注册</a></div
|
>还没有账号?联系管理员开通</div
|
||||||
>
|
>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</template>
|
</template>
|
||||||
@@ -189,8 +154,8 @@
|
|||||||
>
|
>
|
||||||
{{ loading ? t('login.loading') : t('login.login') }}
|
{{ loading ? t('login.loading') : t('login.login') }}
|
||||||
</a-button>
|
</a-button>
|
||||||
<div class="register text-center pt-5"
|
<div class="text-center pt-5 text-gray-500"
|
||||||
><a @click="push('/register')">前往注册</a></div
|
>还没有账号?联系管理员开通</div
|
||||||
>
|
>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</template>
|
</template>
|
||||||
@@ -204,7 +169,7 @@
|
|||||||
<div class="login-copyright">
|
<div class="login-copyright">
|
||||||
<a-space>
|
<a-space>
|
||||||
<span>© {{ new Date().getFullYear() }}</span>
|
<span>© {{ new Date().getFullYear() }}</span>
|
||||||
<span>{{ config?.copyright || 'websoft.top Inc.' }}</span>
|
<span>{{ config?.copyright || 'websopy Inc.' }}</span>
|
||||||
</a-space>
|
</a-space>
|
||||||
</div>
|
</div>
|
||||||
<!-- 编辑弹窗 -->
|
<!-- 编辑弹窗 -->
|
||||||
@@ -272,7 +237,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref, reactive, unref, watch } from 'vue';
|
import { ref, reactive, unref, watch, nextTick, onMounted } from 'vue';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
import { useRouter } from 'vue-router';
|
import { useRouter } from 'vue-router';
|
||||||
import { Form, message } from 'ant-design-vue';
|
import { Form, message } from 'ant-design-vue';
|
||||||
@@ -280,15 +245,19 @@
|
|||||||
LockOutlined,
|
LockOutlined,
|
||||||
UserOutlined,
|
UserOutlined,
|
||||||
QrcodeOutlined,
|
QrcodeOutlined,
|
||||||
MobileOutlined,
|
MobileOutlined
|
||||||
SafetyCertificateOutlined,
|
|
||||||
RightOutlined
|
|
||||||
} from '@ant-design/icons-vue';
|
} from '@ant-design/icons-vue';
|
||||||
import { goHomeRoute, cleanPageTabs } from '@/utils/page-tab-util';
|
import { goHomeRoute, cleanPageTabs } from '@/utils/page-tab-util';
|
||||||
import { login, loginBySms, getCaptcha } from '@/api/passport/login';
|
import {
|
||||||
|
loginByUserId,
|
||||||
|
loginByEmail,
|
||||||
|
loginBySuperAdminSms,
|
||||||
|
getCaptcha
|
||||||
|
} from '@/api/passport/login';
|
||||||
import QrLogin from '@/components/QrLogin/index.vue';
|
import QrLogin from '@/components/QrLogin/index.vue';
|
||||||
|
|
||||||
import { User } from '@/api/system/user/model';
|
import { User } from '@/api/system/user/model';
|
||||||
|
type LoginForm = User & { account?: string };
|
||||||
import { TEMPLATE_ID, THEME_STORE_NAME } from '@/config/setting';
|
import { TEMPLATE_ID, THEME_STORE_NAME } from '@/config/setting';
|
||||||
import { sendSmsCaptcha } from '@/api/passport/login';
|
import { sendSmsCaptcha } from '@/api/passport/login';
|
||||||
import useFormData from '@/utils/use-form-data';
|
import useFormData from '@/utils/use-form-data';
|
||||||
@@ -297,32 +266,81 @@
|
|||||||
import { Config } from '@/api/cms/cmsWebsiteField/model';
|
import { Config } from '@/api/cms/cmsWebsiteField/model';
|
||||||
import { phoneReg } from 'ele-admin-pro';
|
import { phoneReg } from 'ele-admin-pro';
|
||||||
import router from '@/router';
|
import router from '@/router';
|
||||||
import { listAdminsByPhoneAll } from '@/api/system/user';
|
import { listAdminsByEmailAll } from '@/api/system/user';
|
||||||
import { QrCodeStatusResponse } from '@/api/passport/qrLogin';
|
import { QrCodeStatusResponse } from '@/api/passport/qrLogin';
|
||||||
|
|
||||||
const useForm = Form.useForm;
|
const useForm = Form.useForm;
|
||||||
const routerInstance = useRouter();
|
const { currentRoute } = useRouter();
|
||||||
const { currentRoute } = routerInstance;
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const { locale } = useI18n();
|
const { locale } = useI18n();
|
||||||
|
|
||||||
// 路由导航函数
|
|
||||||
const push = (path: string) => {
|
|
||||||
routerInstance.push(path);
|
|
||||||
};
|
|
||||||
|
|
||||||
// 登录框方向, 0 居中, 1 居右, 2 居左
|
// 登录框方向, 0 居中, 1 居右, 2 居左
|
||||||
const direction = ref(0);
|
const direction = ref(0);
|
||||||
// 加载状态
|
// 加载状态
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
// 是否显示tenantId填写输入框
|
// 是否显示tenantId填写输入框,默认进入账号登录/手机号登录切换模式
|
||||||
const loginType = ref('scan');
|
const loginType = ref('account');
|
||||||
|
// 标签下划线指示器
|
||||||
|
const accountTabRef = ref<HTMLElement>();
|
||||||
|
const smsTabRef = ref<HTMLElement>();
|
||||||
|
const indicatorStyle = ref({ left: '0px', width: '0px', transition: 'none' });
|
||||||
|
// 收缩/展开两阶段动画时长(ms),模拟阿里云"先收一下再展开"的弹性效果
|
||||||
|
const SHRINK_MS = 150;
|
||||||
|
const EXPAND_MS = 220;
|
||||||
|
let indicatorTimer: number | null = null;
|
||||||
|
const updateIndicator = (animate = true) => {
|
||||||
|
if (indicatorTimer != null) {
|
||||||
|
clearTimeout(indicatorTimer);
|
||||||
|
indicatorTimer = null;
|
||||||
|
}
|
||||||
|
nextTick(() => {
|
||||||
|
const activeEl =
|
||||||
|
loginType.value === 'account' ? accountTabRef.value : smsTabRef.value;
|
||||||
|
if (!activeEl) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const targetLeft = activeEl.offsetLeft;
|
||||||
|
const targetWidth = activeEl.offsetWidth;
|
||||||
|
// 非动画:直接定位(首次渲染 / 窗口缩放)
|
||||||
|
if (!animate) {
|
||||||
|
indicatorStyle.value = {
|
||||||
|
left: `${targetLeft}px`,
|
||||||
|
width: `${targetWidth}px`,
|
||||||
|
transition: 'none'
|
||||||
|
};
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 阶段一:收缩成一条短线,并滑向目标中心
|
||||||
|
const targetCenter = targetLeft + targetWidth / 2;
|
||||||
|
const smallWidth = 8;
|
||||||
|
indicatorStyle.value = {
|
||||||
|
left: `${targetCenter - smallWidth / 2}px`,
|
||||||
|
width: `${smallWidth}px`,
|
||||||
|
transition: `left ${SHRINK_MS}ms ease-in, width ${SHRINK_MS}ms ease-in`
|
||||||
|
};
|
||||||
|
// 阶段二:展开到目标标签的宽度
|
||||||
|
indicatorTimer = window.setTimeout(() => {
|
||||||
|
indicatorStyle.value = {
|
||||||
|
left: `${targetLeft}px`,
|
||||||
|
width: `${targetWidth}px`,
|
||||||
|
transition: `left ${EXPAND_MS}ms ease-out, width ${EXPAND_MS}ms ease-out`
|
||||||
|
};
|
||||||
|
indicatorTimer = null;
|
||||||
|
}, SHRINK_MS);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
watch(loginType, () => updateIndicator(true), { immediate: true });
|
||||||
|
onMounted(() => {
|
||||||
|
updateIndicator(false);
|
||||||
|
window.addEventListener('resize', () => updateIndicator(false));
|
||||||
|
});
|
||||||
const config = ref<Config>();
|
const config = ref<Config>();
|
||||||
// 配置信息
|
// 配置信息
|
||||||
const { form } = useFormData<User>({
|
const { form } = useFormData<LoginForm>({
|
||||||
|
account: '',
|
||||||
userId: undefined,
|
userId: undefined,
|
||||||
username: '',
|
|
||||||
phone: '',
|
phone: '',
|
||||||
|
email: '',
|
||||||
password: '',
|
password: '',
|
||||||
code: '',
|
code: '',
|
||||||
smsCode: '',
|
smsCode: '',
|
||||||
@@ -356,11 +374,26 @@
|
|||||||
|
|
||||||
// 表单验证规则
|
// 表单验证规则
|
||||||
const rules = reactive({
|
const rules = reactive({
|
||||||
userId: [
|
account: [
|
||||||
{
|
{
|
||||||
required: true,
|
required: true,
|
||||||
message: t('login.username'),
|
message: '请输入用户ID或邮箱',
|
||||||
type: 'string'
|
type: 'string',
|
||||||
|
trigger: 'blur'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
validator: (_rule: unknown, value: string) => {
|
||||||
|
if (!value) {
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
const isEmail = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value);
|
||||||
|
const isNumber = /^\d+$/.test(value);
|
||||||
|
if (isEmail || isNumber) {
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
return Promise.reject(new Error('请输入正确的用户ID或邮箱'));
|
||||||
|
},
|
||||||
|
trigger: 'blur'
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
phone: [
|
phone: [
|
||||||
@@ -452,24 +485,13 @@
|
|||||||
.then(() => {
|
.then(() => {
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
form.code = form.smsCode?.toLowerCase();
|
form.code = form.smsCode?.toLowerCase();
|
||||||
loginBySms(form)
|
loginBySuperAdminSms(form)
|
||||||
.then((msg) => {
|
.then((msg) => {
|
||||||
if (msg == '请选择登录用户') {
|
|
||||||
showSelectLoginUser.value = true;
|
|
||||||
listAdminsByPhoneAll({
|
|
||||||
phone: form.phone,
|
|
||||||
templateId: TEMPLATE_ID
|
|
||||||
}).then((data) => {
|
|
||||||
admins.value = data;
|
|
||||||
});
|
|
||||||
return false;
|
|
||||||
} else {
|
|
||||||
message.success(msg);
|
message.success(msg);
|
||||||
loading.value = false;
|
loading.value = false;
|
||||||
resetFields();
|
resetFields();
|
||||||
cleanPageTabs();
|
cleanPageTabs();
|
||||||
goHome();
|
goHome();
|
||||||
}
|
|
||||||
})
|
})
|
||||||
.catch((e: Error) => {
|
.catch((e: Error) => {
|
||||||
message.error(e.message);
|
message.error(e.message);
|
||||||
@@ -479,12 +501,13 @@
|
|||||||
.catch(() => {});
|
.catch(() => {});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/* 邮箱登录(跨租户) */
|
||||||
const onSelectUser = (item: User) => {
|
const onSelectUser = (item: User) => {
|
||||||
form.tenantId = item.tenantId;
|
form.tenantId = item.tenantId;
|
||||||
onLoginBySms();
|
submit();
|
||||||
};
|
};
|
||||||
|
|
||||||
/* 保存编辑 */
|
/* 账号登录(用户ID / 邮箱登录合并) */
|
||||||
const submit = () => {
|
const submit = () => {
|
||||||
if (!formRef.value) {
|
if (!formRef.value) {
|
||||||
return;
|
return;
|
||||||
@@ -492,18 +515,31 @@
|
|||||||
formRef.value
|
formRef.value
|
||||||
.validate()
|
.validate()
|
||||||
.then(() => {
|
.then(() => {
|
||||||
// if (form.code?.toLowerCase() !== text.value) {
|
|
||||||
// message.error('验证码错误');
|
|
||||||
// changeCaptcha();
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
form.code = form.code?.toLowerCase();
|
const account = (form.account || '').trim();
|
||||||
form.phone = undefined;
|
// 判断输入的是邮箱还是用户ID(纯数字)
|
||||||
login(form)
|
const isEmail = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(account);
|
||||||
|
const payload = {
|
||||||
|
password: form.password,
|
||||||
|
remember: form.remember
|
||||||
|
};
|
||||||
|
const req = isEmail
|
||||||
|
? loginByEmail({
|
||||||
|
...payload,
|
||||||
|
email: account,
|
||||||
|
tenantId: form.tenantId
|
||||||
|
})
|
||||||
|
: loginByUserId({ ...payload, userId: account });
|
||||||
|
req
|
||||||
.then((msg) => {
|
.then((msg) => {
|
||||||
if (msg == '请选择登录用户') {
|
if (msg == '请选择登录用户') {
|
||||||
showSelectLoginUser.value = true;
|
showSelectLoginUser.value = true;
|
||||||
|
listAdminsByEmailAll({
|
||||||
|
email: account,
|
||||||
|
templateId: TEMPLATE_ID
|
||||||
|
}).then((data) => {
|
||||||
|
admins.value = data;
|
||||||
|
});
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
// 登录成功
|
// 登录成功
|
||||||
@@ -646,10 +682,10 @@
|
|||||||
|
|
||||||
/* 卡片 */
|
/* 卡片 */
|
||||||
.login-form {
|
.login-form {
|
||||||
width: 380px;
|
width: 420px;
|
||||||
margin: 0 auto;
|
margin: 20px auto;
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
padding: 0 28px;
|
padding: 28px 36px;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
box-shadow: 0 3px 6px rgba(0, 0, 0, 0.15);
|
box-shadow: 0 3px 6px rgba(0, 0, 0, 0.15);
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
@@ -657,22 +693,39 @@
|
|||||||
z-index: 2;
|
z-index: 2;
|
||||||
|
|
||||||
h4 {
|
h4 {
|
||||||
padding: 22px 0;
|
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.title-btn {
|
.title-btn {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
white-space: nowrap;
|
||||||
|
transition: color 0.3s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.login-title {
|
.login-title {
|
||||||
|
position: relative;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-around;
|
flex-wrap: nowrap;
|
||||||
line-height: 50px;
|
line-height: 1;
|
||||||
|
margin-top: 10px;
|
||||||
|
margin-bottom: 38px;
|
||||||
|
gap: 20px;
|
||||||
|
|
||||||
.active {
|
.active {
|
||||||
color: #007dff;
|
color: #007dff;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tab-indicator {
|
||||||
|
position: absolute;
|
||||||
|
bottom: -10px;
|
||||||
|
height: 3px;
|
||||||
|
border-radius: 2px;
|
||||||
|
background-color: #007dff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.ant-form-item) {
|
||||||
|
margin-bottom: 18px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user