feat 登录和token对接

This commit is contained in:
xxm
2022-10-12 23:34:27 +08:00
parent 014a6dcf5a
commit 8112891db2
10 changed files with 112 additions and 132 deletions

View File

@@ -1,9 +1,19 @@
/**
* @description: Login interface parameters
* 账密登录参数
*/
export interface LoginParams {
username: string
// 账号/手机号/邮箱
account: string
// 密码
password: string
// 终端
client: string
// 登录方式
loginType: string
// 验证码key
captchaKey: string
// 验证码
captcha: string
}
export interface RoleInfo {
@@ -12,27 +22,15 @@ export interface RoleInfo {
}
/**
* @description: Login interface return value
*/
export interface LoginResultModel {
userId: string | number
token: string
role: RoleInfo
}
/**
* @description: Get user information return value
* 登录后用户信息
*/
export interface GetUserInfoModel {
roles: RoleInfo[]
// 用户id
userId: string | number
// 用户
userId: number
// 名
name: string
// 账号
username: string
// 真实名字
realName: string
// 头像
avatar: string
// 介绍
desc?: string
}

View File

@@ -1,46 +1,51 @@
import { defHttp } from '/@/utils/http/axios'
import { LoginParams, LoginResultModel, GetUserInfoModel } from './model/userModel'
import { LoginParams, GetUserInfoModel } from './model/userModel'
import { ErrorMessageMode, Result } from '/#/axios'
enum Api {
Login = '/login',
Logout = '/logout',
GetUserInfo = '/getUserInfo',
GetPermCode = '/getPermCode',
TestRetry = '/testRetry',
}
/**
* @description: user login api
* 登录接口 返回token
*/
export function loginApi(params: LoginParams, mode: ErrorMessageMode = 'modal') {
return defHttp.post<Result<LoginResultModel>>(
{
url: Api.Login,
export function loginApi(params: LoginParams) {
return defHttp.post<Result<string>>({
url: '/token/login',
params,
},
{
errorMessageMode: mode,
},
)
})
}
/**
* 登录后获取用户信息
*/
export function getUserInfo() {
return defHttp.get<Result<GetUserInfoModel>>({ url: '/user/getLoginAfterUserInfo' })
}
/**
* @description: getUserInfo
* 获取用户菜单和资源权限
*/
export function getUserInfo() {
return defHttp.get<Result<GetUserInfoModel>>({ url: Api.GetUserInfo }, { errorMessageMode: 'none' })
export function getPermissions(clientCode: string) {
return defHttp.get<Result<GetUserInfoModel>>({ url: '/role/menu/getPermissions', params: { clientCode } })
}
export function getPermCode() {
return defHttp.get<string[]>({ url: Api.GetPermCode })
}
/**
* 退出
*/
export function doLogout() {
return defHttp.get({ url: Api.Logout })
return defHttp.post({ url: '/token/logout' })
}
/**
* 测试重试
*/
export function testRetry() {
return defHttp.get(
{ url: Api.TestRetry },

View File

@@ -4,17 +4,17 @@
<img :class="`${prefixCls}__header`" :src="getUserInfo.avatar" />
<span :class="`${prefixCls}__info hidden md:block`">
<span :class="`${prefixCls}__name `" class="truncate">
{{ getUserInfo.realName }}
{{ getUserInfo.name }}
</span>
</span>
</span>
<template #overlay>
<Menu @click="handleMenuClick">
<MenuItem key="doc" :text="t('layout.header.dropdownItemDoc')" icon="ion:document-text-outline" v-if="getShowDoc" />
<MenuDivider v-if="getShowDoc" />
<MenuItem v-if="getUseLockPage" key="lock" :text="t('layout.header.tooltipLock')" icon="ion:lock-closed-outline" />
<MenuItem key="logout" :text="t('layout.header.dropdownItemLoginOut')" icon="ion:power-outline" />
<!-- <MenuItem key="doc" text="文档" icon="ion:document-text-outline" v-if="getShowDoc" />-->
<!-- <MenuDivider v-if="getShowDoc" />-->
<MenuItem v-if="getUseLockPage" key="lock" text="锁定屏幕" icon="ion:lock-closed-outline" />
<MenuItem key="logout" text="退出系统" icon="ion:power-outline" />
</Menu>
</template>
</Dropdown>
@@ -49,7 +49,7 @@
Dropdown,
Menu,
MenuItem: createAsyncComponent(() => import('./DropMenuItem.vue')),
MenuDivider: Menu.Divider,
// MenuDivider: Menu.Divider,
LockAction: createAsyncComponent(() => import('../lock/LockModal.vue')),
},
props: {
@@ -57,15 +57,14 @@
},
setup() {
const { prefixCls } = useDesign('header-user-dropdown')
const { t } = useI18n()
const { getShowDoc, getUseLockPage } = useHeaderSetting()
const userStore = useUserStore()
// 用户信息
const getUserInfo = computed(() => {
const { realName = '', avatar, desc } = userStore.getUserInfo || {}
return { realName, avatar: avatar || headerImg, desc }
const { name = '', avatar } = userStore.getUserInfo || {}
return { name, avatar: headerImg }
})
const [register, { openModal }] = useModal()
function handleLock() {
@@ -98,10 +97,9 @@
return {
prefixCls,
t,
getUserInfo,
handleMenuClick,
getShowDoc,
// getShowDoc,
register,
getUseLockPage,
}

View File

@@ -57,6 +57,7 @@ export const useUserStore = defineStore({
},
},
actions: {
// token信心
setToken(info: string | undefined) {
this.token = info ? info : '' // for null or undefined value
setAuthCache(TOKEN_KEY, info)
@@ -80,36 +81,33 @@ export const useUserStore = defineStore({
this.sessionTimeout = false
},
/**
* @description: login
* 登录方法
*/
async login(
params: LoginParams & {
goHome?: boolean
mode?: ErrorMessageMode
},
): Promise<GetUserInfoModel | null> {
async login(params: LoginParams) {
try {
const { goHome = true, mode, ...loginParams } = params
const data = await loginApi(loginParams, mode)
const { token } = data.data
// save token
const { data: token } = await loginApi(params)
// 保存token
this.setToken(token)
return this.afterLoginAction(goHome)
await this.afterLoginAction(true)
return token
} catch (error) {
return Promise.reject(error)
}
},
async afterLoginAction(goHome?: boolean): Promise<GetUserInfoModel | null> {
/**
* 登录后操作
*/
async afterLoginAction(goHome?: boolean) {
if (!this.getToken) return null
// get user info
const userInfo = await this.getUserInfoAction()
// 获取用户信息
await this.getUserInfoAction()
const sessionTimeout = this.sessionTimeout
// 超时
if (sessionTimeout) {
this.setSessionTimeout(false)
} else {
const permissionStore = usePermissionStore()
console.log(permissionStore)
if (!permissionStore.isDynamicAddedRoute) {
const routes = await permissionStore.buildRoutesAction()
routes.forEach((route) => {
@@ -118,21 +116,13 @@ export const useUserStore = defineStore({
router.addRoute(PAGE_NOT_FOUND_ROUTE as unknown as RouteRecordRaw)
permissionStore.setDynamicAddedRoute(true)
}
goHome && (await router.replace(userInfo?.homePath || PageEnum.BASE_HOME))
goHome && (await router.replace(PageEnum.BASE_HOME))
}
return userInfo
},
async getUserInfoAction(): Promise<UserInfo | null> {
// 获取并存储用户信息
async getUserInfoAction() {
if (!this.getToken) return null
const { data: userInfo } = await getUserInfo()
const { roles = [] } = userInfo
if (isArray(roles)) {
const roleList = roles.map((item) => item.value) as RoleEnum[]
this.setRoleList(roleList)
} else {
userInfo.roles = []
this.setRoleList([])
}
this.setUserInfo(userInfo)
return userInfo
},

View File

@@ -135,8 +135,8 @@ const transform: AxiosTransform = {
// 请求之前处理config
const token = getToken()
if (token && (config as Recordable)?.requestOptions?.withToken !== false) {
// jwt token
;(config as Recordable).headers.Authorization = options.authenticationScheme ? `${options.authenticationScheme} ${token}` : token
// 添加 token 到请求头
;(config as Recordable).headers.AccessToken = token
}
return config
},

View File

@@ -10,13 +10,13 @@
<a-spin :spinning="confirmLoading">
<a-form class="small-from-item" ref="formRef" :label-col="labelCol" :wrapper-col="wrapperCol">
<a-form-item label="主键" :hidden="true">
<a-input v-model:value="form.id" :disabled="showable" />
<Input v-model:value="form.id" :disabled="showable" />
</a-form-item>
<a-form-item label="编码" v-bind="validateInfos.code" name="code">
<a-input v-model:value="form.code" :disabled="showable" @blur="validate('code')" placeholder="请输入编码" />
<Input v-model:value="form.code" :disabled="showable" @blur="validate('code')" placeholder="请输入编码" />
</a-form-item>
<a-form-item label="名称" v-bind="validateInfos.name" name="name">
<a-input v-model:value="form.name" :disabled="showable" @blur="validate('name')" placeholder="请输入名称" />
<Input v-model:value="form.name" :disabled="showable" @blur="validate('name')" placeholder="请输入名称" />
</a-form-item>
<a-form-item label="启用状态" v-bind="validateInfos.enable" name="enable">
<a-switch checked-children="" un-checked-children="" v-model:checked="form.enable" :disabled="showable || form.system" />
@@ -59,9 +59,8 @@
import { nextTick, reactive, ref } from 'vue'
import useFormEdit from '/@/hooks/bootx/useFormEdit'
import { add, Client, existsByCode, existsByCodeNotId, get, update } from './Client.api'
import { Rule, useForm } from "ant-design-vue/lib/form";
import { FormInstance, Rule, useForm } from 'ant-design-vue/lib/form'
import { FormEditType } from '/@/enums/formTypeEnum'
import { FormInstance } from 'ant-design-vue/es'
import { findAll, LoginType } from '/@/views/modules/system/loginType/LoginType.api'
const {

View File

@@ -10,13 +10,13 @@
<a-spin :spinning="confirmLoading">
<a-form class="small-from-item" ref="formRef" :model="form" :rules="rules" :label-col="labelCol" :wrapper-col="wrapperCol">
<a-form-item label="主键" name="id" :hidden="true">
<a-input v-model:value="form.id" :disabled="showable" />
<Input v-model:value="form.id" :disabled="showable" />
</a-form-item>
<a-form-item label="编码" name="code">
<a-input v-model:value="form.code" :disabled="showable" placeholder="请输入登录方式编码" />
<Input v-model:value="form.code" :disabled="showable" placeholder="请输入登录方式编码" />
</a-form-item>
<a-form-item label="名称" name="name">
<a-input v-model:value="form.name" :disabled="showable" placeholder="请输入登录方式名称" />
<Input v-model:value="form.name" :disabled="showable" placeholder="请输入登录方式名称" />
</a-form-item>
<a-form-item label="类型" name="type">
<a-radio-group v-model:value="form.type" button-style="solid">
@@ -35,7 +35,7 @@
<a-switch checked-children="" un-checked-children="" v-model:checked="form.captcha" :disabled="showable" />
</a-form-item>
<a-form-item label="超时时间(分钟)" name="timeout">
<a-input-number
<Input-number
v-model:value="form.timeout"
:disabled="showable"
:precision="0"
@@ -45,7 +45,7 @@
/>
</a-form-item>
<a-form-item label="密码可错误次数" name="pwdErrNum" v-show="form.type === PASSWORD">
<a-input-number
<Input-number
v-model:value="form.pwdErrNum"
:disabled="showable"
:min="-1"
@@ -73,8 +73,7 @@
import { nextTick, reactive, ref } from 'vue'
import { add, LoginType, get, existsByCode, existsByCodeNotId, update, PASSWORD, OPEN_ID } from './LoginType.api'
import { FormEditType } from '/@/enums/formTypeEnum'
import { FormInstance } from 'ant-design-vue/es'
import { Rule } from 'ant-design-vue/lib/form'
import { FormInstance, Rule } from 'ant-design-vue/lib/form'
const {
initFormModel,
@@ -99,7 +98,7 @@
enable: true,
description: '',
} as LoginType)
const formRef = ref<FormInstance>()
// 校验状态
const rules = reactive({
code: [
@@ -118,7 +117,6 @@
const res = formEditType.value === FormEditType.Edit ? await existsByCodeNotId(code, id) : await existsByCode(code)
return res.data ? Promise.reject('该编码已存在!') : Promise.resolve()
}
const formRef = ref<FormInstance>()
// 事件
const emits = defineEmits(['ok'])

View File

@@ -2,7 +2,7 @@
<div :class="prefixCls" class="relative w-full h-full px-4">
<div class="flex items-center absolute right-4 top-4">
<AppDarkModeToggle class="enter-x mr-2" v-if="!sessionTimeout" />
<AppLocalePicker class="text-white enter-x xl:text-gray-600" :show-text="false" v-if="!sessionTimeout && showLocale" />
<!-- <AppLocalePicker class="text-white enter-x xl:text-gray-600" :show-text="false" v-if="!sessionTimeout && showLocale" />-->
</div>
<span class="-enter-x xl:hidden">
@@ -16,10 +16,10 @@
<div class="my-auto">
<img :alt="title" src="../../../assets/svg/login-box-bg.svg" class="w-1/2 -mt-16 -enter-x" />
<div class="mt-10 font-medium text-white -enter-x">
<span class="inline-block mt-4 text-3xl"> {{ t('sys.login.signInTitle') }}</span>
<span class="inline-block mt-4 text-3xl"> 开箱即用的中后台管理系统</span>
</div>
<div class="mt-5 font-normal text-white dark:text-gray-500 -enter-x">
{{ t('sys.login.signInDesc') }}
输入您的个人详细信息开始使用
</div>
</div>
</div>
@@ -61,9 +61,9 @@
const globSetting = useGlobSetting()
const { prefixCls } = useDesign('login')
const { t } = useI18n()
// const { t } = useI18n()
const localeStore = useLocaleStore()
const showLocale = localeStore.getShowPicker
// const showLocale = localeStore.getShowPicker
const title = computed(() => globSetting?.title ?? '')
</script>
<style lang="less">

View File

@@ -2,58 +2,46 @@
<LoginFormTitle v-show="getShow" class="enter-x" />
<Form class="p-4 enter-x" :model="formData" :rules="getFormRules" ref="formRef" v-show="getShow" @keypress.enter="handleLogin">
<FormItem name="account" class="enter-x">
<Input size="large" v-model:value="formData.account" :placeholder="t('sys.login.userName')" class="fix-auto-fill" />
<Input size="large" v-model:value="formData.account" placeholder="账号" class="fix-auto-fill" />
</FormItem>
<FormItem name="password" class="enter-x">
<InputPassword size="large" visibilityToggle v-model:value="formData.password" :placeholder="t('sys.login.password')" />
<InputPassword size="large" visibilityToggle v-model:value="formData.password" placeholder="密码" />
</FormItem>
<ARow class="enter-x">
<ACol :span="12">
<FormItem>
<!-- No logic, you need to deal with it yourself -->
<Checkbox v-model:checked="rememberMe" size="small">
{{ t('sys.login.rememberMe') }}
</Checkbox>
<Checkbox v-model:checked="rememberMe" size="small"> 记住我 </Checkbox>
</FormItem>
</ACol>
<ACol :span="12">
<FormItem :style="{ 'text-align': 'right' }">
<!-- No logic, you need to deal with it yourself -->
<Button type="link" size="small" @click="setLoginState(LoginStateEnum.RESET_PASSWORD)">
{{ t('sys.login.forgetPassword') }}
</Button>
<!-- 没有逻辑你需要自己处理 -->
<Button type="link" size="small" @click="setLoginState(LoginStateEnum.RESET_PASSWORD)"> 忘记密码? </Button>
</FormItem>
</ACol>
</ARow>
<FormItem class="enter-x">
<Button type="primary" size="large" block @click="handleLogin" :loading="loading">
{{ t('sys.login.loginButton') }}
</Button>
<Button type="primary" size="large" block @click="handleLogin" :loading="loading"> 登录 </Button>
<!-- <Button size="large" class="mt-4 enter-x" block @click="handleRegister">
{{ t('sys.login.registerButton') }}
</Button> -->
</FormItem>
<ARow class="enter-x">
<ACol :md="8" :xs="24">
<Button block @click="setLoginState(LoginStateEnum.MOBILE)">
{{ t('sys.login.mobileSignInFormTitle') }}
</Button>
<Button block @click="setLoginState(LoginStateEnum.MOBILE)"> 手机登录 </Button>
</ACol>
<ACol :md="8" :xs="24" class="!my-2 !md:my-0 xs:mx-0 md:mx-2">
<Button block @click="setLoginState(LoginStateEnum.QR_CODE)">
{{ t('sys.login.qrSignInFormTitle') }}
</Button>
<Button block @click="setLoginState(LoginStateEnum.QR_CODE)"> 二维码登录 </Button>
</ACol>
<ACol :md="6" :xs="24">
<Button block @click="setLoginState(LoginStateEnum.REGISTER)">
{{ t('sys.login.registerButton') }}
</Button>
<Button block @click="setLoginState(LoginStateEnum.REGISTER)"> 注册 </Button>
</ACol>
</ARow>
<Divider class="enter-x">{{ t('sys.login.otherSignIn') }}</Divider>
<Divider class="enter-x">其他登录方式</Divider>
<div class="flex justify-evenly enter-x" :class="`${prefixCls}-sign-in-way`">
<GithubFilled />
@@ -93,10 +81,10 @@
const formRef = ref()
const loading = ref(false)
const rememberMe = ref(false)
const rememberMe = ref(true)
const formData = reactive({
account: 'vben',
account: 'xxm1995',
password: '123456',
})
@@ -111,15 +99,18 @@
if (!data) return
try {
loading.value = true
const userInfo = await userStore.login({
const token = await userStore.login({
client: 'admin',
loginType: 'password',
password: data.password,
username: data.account,
mode: 'none', //不要默认的错误提示
account: data.account,
captchaKey: data.captchaKey,
captcha: data.captcha,
})
if (userInfo) {
if (token) {
notification.success({
message: t('sys.login.loginSuccessTitle'),
description: `${t('sys.login.loginSuccessDesc')}: ${userInfo.realName}`,
description: `${t('sys.login.loginSuccessDesc')}`,
duration: 3,
})
}

11
types/store.d.ts vendored
View File

@@ -31,13 +31,14 @@ export interface ErrorLogInfo {
}
export interface UserInfo {
userId: string | number
// 用户id
userId: number
// 名称
name: string
// 账号
username: string
realName: string
// 头像
avatar: string
desc?: string
homePath?: string
roles: RoleInfo[]
}
export interface BeforeMiniState {