|
|
|
|
@ -2,312 +2,198 @@
|
|
|
|
|
import { reactive, ref, watch, onMounted, unref } from 'vue'
|
|
|
|
|
import { Form, FormSchema } from '@/components/Form'
|
|
|
|
|
import { useI18n } from '@/hooks/web/useI18n'
|
|
|
|
|
import { ElCheckbox, ElLink } from 'element-plus'
|
|
|
|
|
import { ElCheckbox, ElMessage } from 'element-plus'
|
|
|
|
|
import { useForm } from '@/hooks/web/useForm'
|
|
|
|
|
import { loginApi, getTestRoleApi, getAdminRoleApi } from '@/api/login'
|
|
|
|
|
import { loginApi } from '@/api/login' // 登录接口
|
|
|
|
|
import { useAppStore } from '@/store/modules/app'
|
|
|
|
|
import { usePermissionStore } from '@/store/modules/permission'
|
|
|
|
|
import { useRouter } from 'vue-router'
|
|
|
|
|
import type { RouteLocationNormalizedLoaded, RouteRecordRaw } from 'vue-router'
|
|
|
|
|
import { UserType } from '@/api/login/types'
|
|
|
|
|
import { useValidator } from '@/hooks/web/useValidator'
|
|
|
|
|
import { Icon } from '@/components/Icon'
|
|
|
|
|
import { useRouter } from 'vue-router' // 路由 hook
|
|
|
|
|
import type { RouteRecordRaw } from 'vue-router'
|
|
|
|
|
import { LoginResponse, IResponse, UserLoginRequest } from '@/api/login/types'
|
|
|
|
|
import { useValidator } from '@/hooks/web/useValidator' // 表单校验 hook
|
|
|
|
|
import { useUserStore } from '@/store/modules/user'
|
|
|
|
|
import { BaseButton } from '@/components/Button'
|
|
|
|
|
|
|
|
|
|
// 表单校验规则
|
|
|
|
|
const { required } = useValidator()
|
|
|
|
|
|
|
|
|
|
// 定义事件发射器,用于向父组件传递事件(去注册页面)
|
|
|
|
|
const emit = defineEmits(['to-register'])
|
|
|
|
|
|
|
|
|
|
const appStore = useAppStore()
|
|
|
|
|
|
|
|
|
|
const userStore = useUserStore()
|
|
|
|
|
|
|
|
|
|
const permissionStore = usePermissionStore()
|
|
|
|
|
|
|
|
|
|
const { currentRoute, addRoute, push } = useRouter()
|
|
|
|
|
|
|
|
|
|
const { t } = useI18n()
|
|
|
|
|
|
|
|
|
|
// 状态管理实例
|
|
|
|
|
const appStore = useAppStore() // app 状态
|
|
|
|
|
const userStore = useUserStore() // 用户状态
|
|
|
|
|
const permissionStore = usePermissionStore() // 权限路由状态
|
|
|
|
|
const { currentRoute, addRoute, push } = useRouter() // 路由实例
|
|
|
|
|
|
|
|
|
|
// 表单校验规则
|
|
|
|
|
const rules = {
|
|
|
|
|
username: [required()],
|
|
|
|
|
password: [required()]
|
|
|
|
|
username: [required()], // 用户名必填
|
|
|
|
|
password: [required()] // 密码必填
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 表单结构配置
|
|
|
|
|
const schema = reactive<FormSchema[]>([
|
|
|
|
|
{
|
|
|
|
|
field: 'title',
|
|
|
|
|
colProps: {
|
|
|
|
|
span: 24
|
|
|
|
|
},
|
|
|
|
|
formItemProps: {
|
|
|
|
|
slots: {
|
|
|
|
|
default: () => {
|
|
|
|
|
return <h2 class="text-2xl font-bold text-center w-[100%]">{t('login.login')}</h2>
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
field: 'username',
|
|
|
|
|
field: 'username', // 字段名
|
|
|
|
|
label: t('login.username'),
|
|
|
|
|
// value: 'admin',
|
|
|
|
|
component: 'Input',
|
|
|
|
|
colProps: {
|
|
|
|
|
span: 24
|
|
|
|
|
},
|
|
|
|
|
componentProps: {
|
|
|
|
|
placeholder: 'admin or test'
|
|
|
|
|
}
|
|
|
|
|
component: 'Input', // 组件类型
|
|
|
|
|
colProps: { span: 24 }, // 布局占比
|
|
|
|
|
componentProps: { placeholder: t('login.enterUsername') } // 占位符(国际化)
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
field: 'password',
|
|
|
|
|
label: t('login.password'),
|
|
|
|
|
// value: 'admin',
|
|
|
|
|
component: 'InputPassword',
|
|
|
|
|
colProps: {
|
|
|
|
|
span: 24
|
|
|
|
|
},
|
|
|
|
|
component: 'InputPassword', // 密码输入框组件
|
|
|
|
|
colProps: { span: 24 }, // 布局占比
|
|
|
|
|
componentProps: {
|
|
|
|
|
style: {
|
|
|
|
|
width: '100%'
|
|
|
|
|
},
|
|
|
|
|
placeholder: 'admin or test',
|
|
|
|
|
// 按下enter键触发登录
|
|
|
|
|
onKeydown: (_e: any) => {
|
|
|
|
|
if (_e.key === 'Enter') {
|
|
|
|
|
_e.stopPropagation() // 阻止事件冒泡
|
|
|
|
|
signIn()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
placeholder: t('login.enterPassword'),
|
|
|
|
|
onKeydown: (e: KeyboardEvent) => e.key === 'Enter' && signIn() // 回车触发登录
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
field: 'tool',
|
|
|
|
|
colProps: {
|
|
|
|
|
span: 24
|
|
|
|
|
},
|
|
|
|
|
field: 'remember',
|
|
|
|
|
colProps: { span: 24 },
|
|
|
|
|
formItemProps: {
|
|
|
|
|
slots: {
|
|
|
|
|
default: () => {
|
|
|
|
|
return (
|
|
|
|
|
<>
|
|
|
|
|
<div class="flex justify-between items-center w-[100%]">
|
|
|
|
|
<ElCheckbox v-model={remember.value} label={t('login.remember')} size="small" />
|
|
|
|
|
<ElLink type="primary" underline={false}>
|
|
|
|
|
{t('login.forgetPassword')}
|
|
|
|
|
</ElLink>
|
|
|
|
|
</div>
|
|
|
|
|
</>
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
// 自定义表单项内容,记住我复选框
|
|
|
|
|
default: () => (
|
|
|
|
|
<ElCheckbox v-model={remember.value} label={t('login.rememberMe')} size="small" />
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
field: 'login',
|
|
|
|
|
colProps: {
|
|
|
|
|
span: 24
|
|
|
|
|
},
|
|
|
|
|
colProps: { span: 24 },
|
|
|
|
|
formItemProps: {
|
|
|
|
|
slots: {
|
|
|
|
|
default: () => {
|
|
|
|
|
return (
|
|
|
|
|
<>
|
|
|
|
|
<div class="w-[100%]">
|
|
|
|
|
<BaseButton
|
|
|
|
|
loading={loading.value}
|
|
|
|
|
type="primary"
|
|
|
|
|
class="w-[100%]"
|
|
|
|
|
onClick={signIn}
|
|
|
|
|
>
|
|
|
|
|
{t('login.login')}
|
|
|
|
|
</BaseButton>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="w-[100%] mt-15px">
|
|
|
|
|
<BaseButton class="w-[100%]" onClick={toRegister}>
|
|
|
|
|
{t('login.register')}
|
|
|
|
|
</BaseButton>
|
|
|
|
|
</div>
|
|
|
|
|
</>
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
field: 'other',
|
|
|
|
|
component: 'Divider',
|
|
|
|
|
label: t('login.otherLogin'),
|
|
|
|
|
componentProps: {
|
|
|
|
|
contentPosition: 'center'
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
field: 'otherIcon',
|
|
|
|
|
colProps: {
|
|
|
|
|
span: 24
|
|
|
|
|
},
|
|
|
|
|
formItemProps: {
|
|
|
|
|
slots: {
|
|
|
|
|
default: () => {
|
|
|
|
|
return (
|
|
|
|
|
<>
|
|
|
|
|
<div class="flex justify-between w-[100%]">
|
|
|
|
|
<Icon
|
|
|
|
|
icon="vi-ant-design:github-filled"
|
|
|
|
|
size={iconSize}
|
|
|
|
|
class="cursor-pointer ant-icon"
|
|
|
|
|
color={iconColor}
|
|
|
|
|
hoverColor={hoverColor}
|
|
|
|
|
/>
|
|
|
|
|
<Icon
|
|
|
|
|
icon="vi-ant-design:wechat-filled"
|
|
|
|
|
size={iconSize}
|
|
|
|
|
class="cursor-pointer ant-icon"
|
|
|
|
|
color={iconColor}
|
|
|
|
|
hoverColor={hoverColor}
|
|
|
|
|
/>
|
|
|
|
|
<Icon
|
|
|
|
|
icon="vi-ant-design:alipay-circle-filled"
|
|
|
|
|
size={iconSize}
|
|
|
|
|
color={iconColor}
|
|
|
|
|
hoverColor={hoverColor}
|
|
|
|
|
class="cursor-pointer ant-icon"
|
|
|
|
|
/>
|
|
|
|
|
<Icon
|
|
|
|
|
icon="vi-ant-design:weibo-circle-filled"
|
|
|
|
|
size={iconSize}
|
|
|
|
|
color={iconColor}
|
|
|
|
|
hoverColor={hoverColor}
|
|
|
|
|
class="cursor-pointer ant-icon"
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
</>
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
// 自定义表单项内容,登录按钮
|
|
|
|
|
default: () => (
|
|
|
|
|
<BaseButton loading={loading.value} type="primary" class="w-full" onClick={signIn}>
|
|
|
|
|
{t('login.login')}
|
|
|
|
|
</BaseButton>
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
])
|
|
|
|
|
|
|
|
|
|
const iconSize = 30
|
|
|
|
|
// 响应式变量
|
|
|
|
|
const remember = ref(userStore.getRememberMe) // 记住我状态
|
|
|
|
|
const loading = ref(false) // 登录按钮加载状态
|
|
|
|
|
const redirect = ref<string>('') // 重定向地址
|
|
|
|
|
const { formRegister, formMethods } = useForm() // 表单注册和方法
|
|
|
|
|
const { getFormData, getElFormExpose, setValues } = formMethods // 表单数据获取和设置
|
|
|
|
|
|
|
|
|
|
const remember = ref(userStore.getRememberMe)
|
|
|
|
|
|
|
|
|
|
const initLoginInfo = () => {
|
|
|
|
|
const loginInfo = userStore.getLoginInfo
|
|
|
|
|
// 初始化登录信息
|
|
|
|
|
onMounted(() => {
|
|
|
|
|
// 从用户状态中获取登录信息
|
|
|
|
|
const loginInfo = userStore.getLoginInfo as unknown as UserLoginRequest | undefined
|
|
|
|
|
if (loginInfo) {
|
|
|
|
|
const { username, password } = loginInfo
|
|
|
|
|
setValues({ username, password })
|
|
|
|
|
// 设置表单值(用户名和密码)
|
|
|
|
|
setValues({ username: loginInfo.username, password: loginInfo.password })
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
onMounted(() => {
|
|
|
|
|
initLoginInfo()
|
|
|
|
|
// 设置路由未添加状态
|
|
|
|
|
permissionStore.setIsAddRouters(false)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
const { formRegister, formMethods } = useForm()
|
|
|
|
|
const { getFormData, getElFormExpose, setValues } = formMethods
|
|
|
|
|
|
|
|
|
|
const loading = ref(false)
|
|
|
|
|
|
|
|
|
|
const iconColor = '#999'
|
|
|
|
|
|
|
|
|
|
const hoverColor = 'var(--el-color-primary)'
|
|
|
|
|
|
|
|
|
|
const redirect = ref<string>('')
|
|
|
|
|
|
|
|
|
|
// 监听路由变化,获取重定向地址
|
|
|
|
|
watch(
|
|
|
|
|
() => currentRoute.value,
|
|
|
|
|
(route: RouteLocationNormalizedLoaded) => {
|
|
|
|
|
() => currentRoute.value, // 监听当前路由
|
|
|
|
|
(route) => {
|
|
|
|
|
// 从路由参数中获取重定向地址
|
|
|
|
|
redirect.value = route?.query?.redirect as string
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
immediate: true
|
|
|
|
|
}
|
|
|
|
|
{ immediate: true } // 立即执行
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// 登录
|
|
|
|
|
// 登录核心逻辑
|
|
|
|
|
const signIn = async () => {
|
|
|
|
|
// 获取表单实例
|
|
|
|
|
const formRef = await getElFormExpose()
|
|
|
|
|
await formRef?.validate(async (isValid) => {
|
|
|
|
|
if (isValid) {
|
|
|
|
|
loading.value = true
|
|
|
|
|
const formData = await getFormData<UserType>()
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const res = await loginApi(formData)
|
|
|
|
|
// 表单校验
|
|
|
|
|
const isValid = await formRef?.validate()
|
|
|
|
|
if (!isValid) return // 校验失败则返回
|
|
|
|
|
|
|
|
|
|
loading.value = true // 开始加载
|
|
|
|
|
try {
|
|
|
|
|
// 获取表单数据
|
|
|
|
|
const formData = await getFormData<UserLoginRequest>()
|
|
|
|
|
// 调用登录接口
|
|
|
|
|
const res = (await loginApi(formData)) as IResponse<LoginResponse>
|
|
|
|
|
|
|
|
|
|
// 登录成功
|
|
|
|
|
if (res.code === 200 && res.data) {
|
|
|
|
|
const { token, userInfo } = res.data
|
|
|
|
|
|
|
|
|
|
// 存储用户信息到状态管理
|
|
|
|
|
userStore.setToken(token) // 设置 token
|
|
|
|
|
userStore.setUserInfo(userInfo) // 设置用户信息
|
|
|
|
|
userStore.setRoleId(userInfo.roleId) // 设置角色 ID
|
|
|
|
|
userStore.setRememberMe(remember.value()) // 设置记住我状态
|
|
|
|
|
|
|
|
|
|
// 根据记住我状态存储登录信息
|
|
|
|
|
if (unref(remember)) {
|
|
|
|
|
userStore.setLoginInfo(formData) // 存储登录信息
|
|
|
|
|
} else {
|
|
|
|
|
userStore.setLoginInfo(undefined) // 清除登录信息
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (res) {
|
|
|
|
|
// 是否记住我
|
|
|
|
|
if (unref(remember)) {
|
|
|
|
|
userStore.setLoginInfo({
|
|
|
|
|
username: formData.username,
|
|
|
|
|
password: formData.password
|
|
|
|
|
})
|
|
|
|
|
} else {
|
|
|
|
|
userStore.setLoginInfo(undefined)
|
|
|
|
|
}
|
|
|
|
|
userStore.setRememberMe(unref(remember))
|
|
|
|
|
userStore.setUserInfo(res.data)
|
|
|
|
|
// 是否使用动态路由
|
|
|
|
|
if (appStore.getDynamicRouter) {
|
|
|
|
|
getRole()
|
|
|
|
|
} else {
|
|
|
|
|
await permissionStore.generateRoutes('static').catch(() => {})
|
|
|
|
|
permissionStore.getAddRouters.forEach((route) => {
|
|
|
|
|
addRoute(route as RouteRecordRaw) // 动态添加可访问路由表
|
|
|
|
|
})
|
|
|
|
|
permissionStore.setIsAddRouters(true)
|
|
|
|
|
push({ path: redirect.value || permissionStore.addRouters[0].path })
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} finally {
|
|
|
|
|
loading.value = false
|
|
|
|
|
// 根据配置决定是否需要动态获取路由
|
|
|
|
|
if (appStore.getDynamicRouter) {
|
|
|
|
|
await generateStaticRoutes() // 生成静态路由
|
|
|
|
|
} else {
|
|
|
|
|
// 生成静态路由
|
|
|
|
|
await permissionStore.generateRoutes('static').catch(() => {})
|
|
|
|
|
// 添加路由
|
|
|
|
|
permissionStore.getAddRouters.forEach((route) => addRoute(route as RouteRecordRaw))
|
|
|
|
|
// 设置路由已添加状态
|
|
|
|
|
permissionStore.setIsAddRouters(true)
|
|
|
|
|
// 跳转到重定向地址或默认地址
|
|
|
|
|
push({ path: redirect.value || permissionStore.getAddRouters[0]?.path || '/dashboard' })
|
|
|
|
|
}
|
|
|
|
|
// 登录成功提示
|
|
|
|
|
ElMessage.success(t('login.loginSuccess'))
|
|
|
|
|
} else {
|
|
|
|
|
// 登录失败提示
|
|
|
|
|
ElMessage.error(res.msg || t('login.loginFail'))
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 获取角色信息
|
|
|
|
|
const getRole = async () => {
|
|
|
|
|
const formData = await getFormData<UserType>()
|
|
|
|
|
const params = {
|
|
|
|
|
roleName: formData.username
|
|
|
|
|
}
|
|
|
|
|
const res =
|
|
|
|
|
appStore.getDynamicRouter && appStore.getServerDynamicRouter
|
|
|
|
|
? await getAdminRoleApi(params)
|
|
|
|
|
: await getTestRoleApi(params)
|
|
|
|
|
if (res) {
|
|
|
|
|
const routers = res.data || []
|
|
|
|
|
userStore.setRoleRouters(routers)
|
|
|
|
|
appStore.getDynamicRouter && appStore.getServerDynamicRouter
|
|
|
|
|
? await permissionStore.generateRoutes('server', routers).catch(() => {})
|
|
|
|
|
: await permissionStore.generateRoutes('frontEnd', routers).catch(() => {})
|
|
|
|
|
|
|
|
|
|
permissionStore.getAddRouters.forEach((route) => {
|
|
|
|
|
addRoute(route as RouteRecordRaw) // 动态添加可访问路由表
|
|
|
|
|
})
|
|
|
|
|
permissionStore.setIsAddRouters(true)
|
|
|
|
|
push({ path: redirect.value || permissionStore.addRouters[0].path })
|
|
|
|
|
} catch (error) {
|
|
|
|
|
// 异常处理
|
|
|
|
|
console.error('登录失败:', error)
|
|
|
|
|
ElMessage.error(t('login.networkError')) // 网络错误提示
|
|
|
|
|
} finally {
|
|
|
|
|
loading.value = false // 结束加载
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 去注册页面
|
|
|
|
|
const toRegister = () => {
|
|
|
|
|
emit('to-register')
|
|
|
|
|
// 生成静态路由(替代原角色路由逻辑)
|
|
|
|
|
const generateStaticRoutes = async () => {
|
|
|
|
|
// 生成静态路由
|
|
|
|
|
await permissionStore.generateRoutes('static').catch(() => {})
|
|
|
|
|
// 添加路由
|
|
|
|
|
permissionStore.getAddRouters.forEach((route) => addRoute(route as RouteRecordRaw))
|
|
|
|
|
// 设置路由已添加状态
|
|
|
|
|
permissionStore.setIsAddRouters(true)
|
|
|
|
|
// 跳转到重定向地址或默认地址
|
|
|
|
|
push({ path: redirect.value || permissionStore.getAddRouters[0]?.path || '/dashboard' })
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// // 去注册页面
|
|
|
|
|
// const toRegister = () => emit('to-register') // 触发去注册事件
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<template>
|
|
|
|
|
<!-- 表单组件 -->
|
|
|
|
|
// eslint-disable-next-line prettier/prettier
|
|
|
|
|
<Form
|
|
|
|
|
:schema="schema"
|
|
|
|
|
:rules="rules"
|
|
|
|
|
label-position="top"
|
|
|
|
|
hide-required-asterisk
|
|
|
|
|
size="large"
|
|
|
|
|
class="dark:(border-1 border-[var(--el-border-color)] border-solid)"
|
|
|
|
|
@register="formRegister"
|
|
|
|
|
/>
|
|
|
|
|
</template>
|
|
|
|
|
|