mirror of
https://gitee.com/bootx/dax-pay-ui.git
synced 2025-09-11 06:29:21 +00:00
feat 登录和token对接
This commit is contained in:
@@ -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
|
||||
}
|
||||
|
@@ -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,
|
||||
params,
|
||||
},
|
||||
{
|
||||
errorMessageMode: mode,
|
||||
},
|
||||
)
|
||||
export function loginApi(params: LoginParams) {
|
||||
return defHttp.post<Result<string>>({
|
||||
url: '/token/login',
|
||||
params,
|
||||
})
|
||||
}
|
||||
/**
|
||||
* 登录后获取用户信息
|
||||
*/
|
||||
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 },
|
||||
|
@@ -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,
|
||||
}
|
||||
|
@@ -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
|
||||
},
|
||||
|
@@ -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
|
||||
},
|
||||
|
@@ -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 {
|
||||
|
@@ -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'])
|
||||
|
@@ -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">
|
||||
|
@@ -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
11
types/store.d.ts
vendored
@@ -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 {
|
||||
|
Reference in New Issue
Block a user