完成代码,提交了再删除其他页面
This commit is contained in:
@@ -35,6 +35,7 @@
|
|||||||
"dayjs": "^1.11.13",
|
"dayjs": "^1.11.13",
|
||||||
"echarts": "^5.5.1",
|
"echarts": "^5.5.1",
|
||||||
"element-resize-detector": "^1.2.4",
|
"element-resize-detector": "^1.2.4",
|
||||||
|
"jsencrypt": "3.5.4",
|
||||||
"lodash-es": "^4.17.21",
|
"lodash-es": "^4.17.21",
|
||||||
"mockjs": "^1.1.0",
|
"mockjs": "^1.1.0",
|
||||||
"naive-ui": "^2.39.0",
|
"naive-ui": "^2.39.0",
|
||||||
|
|||||||
9
pnpm-lock.yaml
generated
9
pnpm-lock.yaml
generated
@@ -38,6 +38,9 @@ importers:
|
|||||||
element-resize-detector:
|
element-resize-detector:
|
||||||
specifier: ^1.2.4
|
specifier: ^1.2.4
|
||||||
version: 1.2.4
|
version: 1.2.4
|
||||||
|
jsencrypt:
|
||||||
|
specifier: 3.5.4
|
||||||
|
version: 3.5.4
|
||||||
lodash-es:
|
lodash-es:
|
||||||
specifier: ^4.17.21
|
specifier: ^4.17.21
|
||||||
version: 4.17.21
|
version: 4.17.21
|
||||||
@@ -1389,6 +1392,7 @@ packages:
|
|||||||
|
|
||||||
acorn-import-assertions@1.9.0:
|
acorn-import-assertions@1.9.0:
|
||||||
resolution: {integrity: sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==}
|
resolution: {integrity: sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==}
|
||||||
|
deprecated: package has been renamed to acorn-import-attributes
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
acorn: ^8
|
acorn: ^8
|
||||||
|
|
||||||
@@ -2899,6 +2903,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==}
|
resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
|
jsencrypt@3.5.4:
|
||||||
|
resolution: {integrity: sha512-kNjfYEMNASxrDGsmcSQh/rUTmcoRfSUkxnAz+MMywM8jtGu+fFEZ3nJjHM58zscVnwR0fYmG9sGkTDjqUdpiwA==}
|
||||||
|
|
||||||
jsesc@2.5.2:
|
jsesc@2.5.2:
|
||||||
resolution: {integrity: sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==}
|
resolution: {integrity: sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==}
|
||||||
engines: {node: '>=4'}
|
engines: {node: '>=4'}
|
||||||
@@ -7511,6 +7518,8 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
argparse: 2.0.1
|
argparse: 2.0.1
|
||||||
|
|
||||||
|
jsencrypt@3.5.4: {}
|
||||||
|
|
||||||
jsesc@2.5.2: {}
|
jsesc@2.5.2: {}
|
||||||
|
|
||||||
json-buffer@3.0.1: {}
|
json-buffer@3.0.1: {}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { Alova } from '@/utils/http/alova/index';
|
|||||||
export interface LoginParams {
|
export interface LoginParams {
|
||||||
username: string;
|
username: string;
|
||||||
password: string;
|
password: string;
|
||||||
|
isLock?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface LoginData {
|
export interface LoginData {
|
||||||
@@ -17,14 +18,38 @@ export interface LoginData {
|
|||||||
export interface LoginRes {
|
export interface LoginRes {
|
||||||
code: number;
|
code: number;
|
||||||
message: string;
|
message: string;
|
||||||
data: LoginData;
|
data?: LoginData;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UserInfoData {
|
||||||
|
id: number;
|
||||||
|
username: string;
|
||||||
|
nickname: string;
|
||||||
|
avatarUrl: string;
|
||||||
|
userStatus: string;
|
||||||
|
permissions?: Array<{
|
||||||
|
label: string;
|
||||||
|
value: string;
|
||||||
|
}>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UserInfoRes {
|
||||||
|
code: number;
|
||||||
|
message: string;
|
||||||
|
data?: UserInfoData;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface LogoutRes {
|
||||||
|
code: number;
|
||||||
|
message: string;
|
||||||
|
data?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description: 获取用户信息
|
* @description: 获取用户信息
|
||||||
*/
|
*/
|
||||||
export function getUserInfo() {
|
export function getUserInfo() {
|
||||||
return Alova.Get<InResult>('/admin_info', {
|
return Alova.Get<UserInfoRes>('/api/user/info', {
|
||||||
meta: {
|
meta: {
|
||||||
isReturnNativeResponse: true,
|
isReturnNativeResponse: true,
|
||||||
},
|
},
|
||||||
@@ -56,8 +81,10 @@ export function changePassword(params, uid) {
|
|||||||
/**
|
/**
|
||||||
* @description: 用户登出
|
* @description: 用户登出
|
||||||
*/
|
*/
|
||||||
export function logout(params) {
|
export function logout() {
|
||||||
return Alova.Post('/login/logout', {
|
return Alova.Get<LogoutRes>('/api/user/logout', {
|
||||||
params,
|
meta: {
|
||||||
|
isReturnNativeResponse: true,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
export enum ResultEnum {
|
export enum ResultEnum {
|
||||||
SUCCESS = 200,
|
SUCCESS = 200,
|
||||||
ERROR = -1,
|
ERROR = -1,
|
||||||
|
LOGIN_EXPIRED = 50001,
|
||||||
TIMEOUT = 10042,
|
TIMEOUT = 10042,
|
||||||
TYPE = 'success',
|
TYPE = 'success',
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,9 +6,9 @@ export enum PageEnum {
|
|||||||
REDIRECT = '/redirect',
|
REDIRECT = '/redirect',
|
||||||
REDIRECT_NAME = 'Redirect',
|
REDIRECT_NAME = 'Redirect',
|
||||||
// 首页
|
// 首页
|
||||||
BASE_HOME = '/dashboard',
|
BASE_HOME = '/loan/application',
|
||||||
//首页跳转默认路由
|
//首页跳转默认路由
|
||||||
BASE_HOME_REDIRECT = '/dashboard/console',
|
BASE_HOME_REDIRECT = '/loan/application',
|
||||||
// 错误
|
// 错误
|
||||||
ERROR_PAGE_NAME = 'ErrorPage',
|
ERROR_PAGE_NAME = 'ErrorPage',
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -72,78 +72,40 @@
|
|||||||
</n-breadcrumb>
|
</n-breadcrumb>
|
||||||
</div>
|
</div>
|
||||||
<div class="layout-header-right">
|
<div class="layout-header-right">
|
||||||
<div
|
<div class="layout-header-trigger layout-header-trigger-min user-entry">
|
||||||
class="layout-header-trigger layout-header-trigger-min"
|
<div class="avatar">
|
||||||
v-for="item in iconList"
|
<n-avatar :src="websiteConfig.logo">
|
||||||
:key="item.icon"
|
<template #icon>
|
||||||
>
|
<UserOutlined />
|
||||||
<n-tooltip placement="bottom">
|
</template>
|
||||||
<template #trigger>
|
</n-avatar>
|
||||||
<n-icon size="18">
|
<span class="username">{{ nickname }}</span>
|
||||||
<component :is="item.icon" v-on="item.eventObject || {}" />
|
</div>
|
||||||
</n-icon>
|
|
||||||
</template>
|
|
||||||
<span>{{ item.tips }}</span>
|
|
||||||
</n-tooltip>
|
|
||||||
</div>
|
</div>
|
||||||
<!--切换全屏-->
|
<div class="layout-header-trigger layout-header-trigger-min logout-entry" @click="doLogout">
|
||||||
<div class="layout-header-trigger layout-header-trigger-min">
|
<n-icon size="18">
|
||||||
<n-tooltip placement="bottom">
|
<LogoutOutlined />
|
||||||
<template #trigger>
|
</n-icon>
|
||||||
<n-icon size="18">
|
<span>退出登录</span>
|
||||||
<component :is="fullscreenIcon" @click="toggleFullScreen" />
|
|
||||||
</n-icon>
|
|
||||||
</template>
|
|
||||||
<span>全屏</span>
|
|
||||||
</n-tooltip>
|
|
||||||
</div>
|
|
||||||
<!-- 个人中心 -->
|
|
||||||
<div class="layout-header-trigger layout-header-trigger-min">
|
|
||||||
<n-dropdown trigger="hover" @select="avatarSelect" :options="avatarOptions">
|
|
||||||
<div class="avatar">
|
|
||||||
<n-avatar :src="websiteConfig.logo">
|
|
||||||
<template #icon>
|
|
||||||
<UserOutlined />
|
|
||||||
</template>
|
|
||||||
</n-avatar>
|
|
||||||
<n-divider vertical />
|
|
||||||
<span>{{ username }}</span>
|
|
||||||
</div>
|
|
||||||
</n-dropdown>
|
|
||||||
</div>
|
|
||||||
<!--设置-->
|
|
||||||
<div class="layout-header-trigger layout-header-trigger-min" @click="openSetting">
|
|
||||||
<n-tooltip placement="bottom-end">
|
|
||||||
<template #trigger>
|
|
||||||
<n-icon size="18" style="font-weight: bold">
|
|
||||||
<SettingOutlined />
|
|
||||||
</n-icon>
|
|
||||||
</template>
|
|
||||||
<span>项目配置</span>
|
|
||||||
</n-tooltip>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!--项目配置-->
|
|
||||||
<ProjectSetting ref="drawerSetting" />
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent, reactive, toRefs, ref, computed, unref } from 'vue';
|
import { defineComponent, reactive, toRefs, computed, unref } from 'vue';
|
||||||
import { useRouter, useRoute } from 'vue-router';
|
import { useRouter, useRoute } from 'vue-router';
|
||||||
import components from './components';
|
import components from './components';
|
||||||
import { NDialogProvider, useDialog, useMessage } from 'naive-ui';
|
import { useDialog, useMessage } from 'naive-ui';
|
||||||
import { TABS_ROUTES } from '@/store/mutation-types';
|
import { TABS_ROUTES } from '@/store/mutation-types';
|
||||||
import { useUserStore } from '@/store/modules/user';
|
import { useUserStore } from '@/store/modules/user';
|
||||||
import { useScreenLockStore } from '@/store/modules/screenLock';
|
|
||||||
import ProjectSetting from './ProjectSetting.vue';
|
|
||||||
import { AsideMenu } from '@/layout/components/Menu';
|
import { AsideMenu } from '@/layout/components/Menu';
|
||||||
import { useProjectSetting } from '@/hooks/setting/useProjectSetting';
|
import { useProjectSetting } from '@/hooks/setting/useProjectSetting';
|
||||||
import { websiteConfig } from '@/config/website.config';
|
import { websiteConfig } from '@/config/website.config';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'PageHeader',
|
name: 'PageHeader',
|
||||||
components: { ...components, NDialogProvider, ProjectSetting, AsideMenu },
|
components: { ...components, AsideMenu },
|
||||||
props: {
|
props: {
|
||||||
collapsed: {
|
collapsed: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
@@ -155,16 +117,12 @@
|
|||||||
emits: ['update:collapsed'],
|
emits: ['update:collapsed'],
|
||||||
setup(props, { emit }) {
|
setup(props, { emit }) {
|
||||||
const userStore = useUserStore();
|
const userStore = useUserStore();
|
||||||
const useLockscreen = useScreenLockStore();
|
|
||||||
const message = useMessage();
|
const message = useMessage();
|
||||||
const dialog = useDialog();
|
const dialog = useDialog();
|
||||||
const { navMode, navTheme, headerSetting, menuSetting, crumbsSetting } = useProjectSetting();
|
const { navMode, navTheme, headerSetting, menuSetting, crumbsSetting } = useProjectSetting();
|
||||||
|
|
||||||
const drawerSetting = ref();
|
|
||||||
|
|
||||||
const state = reactive({
|
const state = reactive({
|
||||||
username: userStore?.info?.username ?? '',
|
nickname: userStore?.info?.nickname || userStore?.info?.username || '',
|
||||||
fullscreenIcon: 'FullscreenOutlined',
|
|
||||||
navMode,
|
navMode,
|
||||||
navTheme,
|
navTheme,
|
||||||
headerSetting,
|
headerSetting,
|
||||||
@@ -255,89 +213,18 @@
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// 切换全屏图标
|
|
||||||
const toggleFullscreenIcon = () =>
|
|
||||||
(state.fullscreenIcon =
|
|
||||||
document.fullscreenElement !== null ? 'FullscreenExitOutlined' : 'FullscreenOutlined');
|
|
||||||
|
|
||||||
// 监听全屏切换事件
|
|
||||||
document.addEventListener('fullscreenchange', toggleFullscreenIcon);
|
|
||||||
|
|
||||||
// 全屏切换
|
|
||||||
const toggleFullScreen = () => {
|
|
||||||
if (!document.fullscreenElement) {
|
|
||||||
document.documentElement.requestFullscreen();
|
|
||||||
} else {
|
|
||||||
if (document.exitFullscreen) {
|
|
||||||
document.exitFullscreen();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 图标列表
|
|
||||||
const iconList = [
|
|
||||||
{
|
|
||||||
icon: 'SearchOutlined',
|
|
||||||
tips: '搜索',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: 'GithubOutlined',
|
|
||||||
tips: 'github',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: 'LockOutlined',
|
|
||||||
tips: '锁屏',
|
|
||||||
eventObject: {
|
|
||||||
click: () => useLockscreen.setLock(true),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
];
|
|
||||||
const avatarOptions = [
|
|
||||||
{
|
|
||||||
label: '个人设置',
|
|
||||||
key: 1,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: '退出登录',
|
|
||||||
key: 2,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
//头像下拉菜单
|
|
||||||
const avatarSelect = (key) => {
|
|
||||||
switch (key) {
|
|
||||||
case 1:
|
|
||||||
router.push({ name: 'Setting' });
|
|
||||||
break;
|
|
||||||
case 2:
|
|
||||||
doLogout();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
function openSetting() {
|
|
||||||
const { openDrawer } = drawerSetting.value;
|
|
||||||
openDrawer();
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleMenuCollapsed() {
|
function handleMenuCollapsed() {
|
||||||
emit('update:collapsed', !props.collapsed);
|
emit('update:collapsed', !props.collapsed);
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...toRefs(state),
|
...toRefs(state),
|
||||||
iconList,
|
|
||||||
toggleFullScreen,
|
|
||||||
doLogout,
|
doLogout,
|
||||||
route,
|
route,
|
||||||
dropdownSelect,
|
dropdownSelect,
|
||||||
avatarOptions,
|
|
||||||
getChangeStyle,
|
getChangeStyle,
|
||||||
avatarSelect,
|
|
||||||
breadcrumbList,
|
breadcrumbList,
|
||||||
reloadPage,
|
reloadPage,
|
||||||
drawerSetting,
|
|
||||||
openSetting,
|
|
||||||
getInverted,
|
getInverted,
|
||||||
getMenuLocation,
|
getMenuLocation,
|
||||||
mixMenu,
|
mixMenu,
|
||||||
@@ -407,6 +294,27 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
height: 64px;
|
height: 64px;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.username {
|
||||||
|
line-height: 64px;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-entry {
|
||||||
|
cursor: default;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.logout-entry {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
> * {
|
> * {
|
||||||
|
|||||||
@@ -22,14 +22,18 @@ export function createRouterGuards(router: Router) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const token = storage.get(ACCESS_TOKEN);
|
||||||
|
|
||||||
// Whitelist can be directly entered
|
// Whitelist can be directly entered
|
||||||
if (whitePathList.includes(to.path as PageEnum)) {
|
if (whitePathList.includes(to.path as PageEnum)) {
|
||||||
|
if (to.path === LOGIN_PATH && token) {
|
||||||
|
next({ path: PageEnum.BASE_HOME, replace: true });
|
||||||
|
return;
|
||||||
|
}
|
||||||
next();
|
next();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const token = storage.get(ACCESS_TOKEN);
|
|
||||||
|
|
||||||
if (!token) {
|
if (!token) {
|
||||||
// You can access without permissions. You need to set the routing meta.ignoreAuth to true
|
// You can access without permissions. You need to set the routing meta.ignoreAuth to true
|
||||||
if (to.meta.ignoreAuth) {
|
if (to.meta.ignoreAuth) {
|
||||||
@@ -71,9 +75,7 @@ export function createRouterGuards(router: Router) {
|
|||||||
router.addRoute(ErrorPageRoute as unknown as RouteRecordRaw);
|
router.addRoute(ErrorPageRoute as unknown as RouteRecordRaw);
|
||||||
}
|
}
|
||||||
|
|
||||||
const redirectPath = (from.query.redirect || to.path) as string;
|
const nextData = { ...to, replace: true };
|
||||||
const redirect = decodeURIComponent(redirectPath);
|
|
||||||
const nextData = to.path === redirect ? { ...to, replace: true } : { path: redirect };
|
|
||||||
asyncRouteStore.setDynamicRouteAdded(true);
|
asyncRouteStore.setDynamicRouteAdded(true);
|
||||||
next(nextData);
|
next(nextData);
|
||||||
Loading && Loading.finish();
|
Loading && Loading.finish();
|
||||||
|
|||||||
@@ -89,10 +89,11 @@ export const useAsyncRouteStore = defineStore({
|
|||||||
async generateRoutes(data) {
|
async generateRoutes(data) {
|
||||||
let accessedRouters;
|
let accessedRouters;
|
||||||
const permissionsList = data.permissions ?? [];
|
const permissionsList = data.permissions ?? [];
|
||||||
|
const hasPermissions = permissionsList.length > 0;
|
||||||
const routeFilter = (route) => {
|
const routeFilter = (route) => {
|
||||||
const { meta } = route;
|
const { meta } = route;
|
||||||
const { permissions } = meta || {};
|
const { permissions } = meta || {};
|
||||||
if (!permissions) return true;
|
if (!permissions || !hasPermissions) return true;
|
||||||
return permissionsList.some((item) => permissions.includes(item.value));
|
return permissionsList.some((item) => permissions.includes(item.value));
|
||||||
};
|
};
|
||||||
const { permissionMode } = useProjectSetting();
|
const { permissionMode } = useProjectSetting();
|
||||||
|
|||||||
@@ -3,13 +3,15 @@ import { store } from '@/store';
|
|||||||
import { ACCESS_TOKEN, CURRENT_USER, IS_SCREENLOCKED } from '@/store/mutation-types';
|
import { ACCESS_TOKEN, CURRENT_USER, IS_SCREENLOCKED } from '@/store/mutation-types';
|
||||||
import { ResultEnum } from '@/enums/httpEnum';
|
import { ResultEnum } from '@/enums/httpEnum';
|
||||||
|
|
||||||
import { getUserInfo as getUserInfoApi, login } from '@/api/system/user';
|
import { getUserInfo as getUserInfoApi, login, logout as logoutApi } from '@/api/system/user';
|
||||||
|
import type { LoginParams, LoginRes, UserInfoData } from '@/api/system/user';
|
||||||
|
import { encryptPassword } from '@/utils/encrypt';
|
||||||
import { storage } from '@/utils/Storage';
|
import { storage } from '@/utils/Storage';
|
||||||
|
|
||||||
export type UserInfoType = {
|
export type UserInfoType = Partial<UserInfoData> & {
|
||||||
// TODO: add your own data
|
|
||||||
username: string;
|
username: string;
|
||||||
email: string;
|
email?: string;
|
||||||
|
permissions?: any[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface IUserState {
|
export interface IUserState {
|
||||||
@@ -62,10 +64,21 @@ export const useUserStore = defineStore({
|
|||||||
this.info = info;
|
this.info = info;
|
||||||
},
|
},
|
||||||
// 登录
|
// 登录
|
||||||
async login(params: any) {
|
async login(params: LoginParams): Promise<LoginRes> {
|
||||||
const response = await login(params);
|
const encryptedPassword = encryptPassword(params.password);
|
||||||
|
if (!encryptedPassword) {
|
||||||
|
return {
|
||||||
|
code: ResultEnum.ERROR,
|
||||||
|
message: '密码加密失败',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await login({
|
||||||
|
...params,
|
||||||
|
password: encryptedPassword,
|
||||||
|
});
|
||||||
const { code, data } = response;
|
const { code, data } = response;
|
||||||
if (code === ResultEnum.SUCCESS) {
|
if (code === ResultEnum.SUCCESS && data) {
|
||||||
const ex = data.tokenTimeout || 7 * 24 * 60 * 60;
|
const ex = data.tokenTimeout || 7 * 24 * 60 * 60;
|
||||||
storage.set(ACCESS_TOKEN, data.token, ex);
|
storage.set(ACCESS_TOKEN, data.token, ex);
|
||||||
storage.set(CURRENT_USER, data, ex);
|
storage.set(CURRENT_USER, data, ex);
|
||||||
@@ -81,25 +94,37 @@ export const useUserStore = defineStore({
|
|||||||
|
|
||||||
// 获取用户信息
|
// 获取用户信息
|
||||||
async getInfo() {
|
async getInfo() {
|
||||||
const data = await getUserInfoApi();
|
const response = await getUserInfoApi();
|
||||||
const { result } = data;
|
const { data } = response;
|
||||||
if (result.permissions && result.permissions.length) {
|
if (!data) {
|
||||||
const permissionsList = result.permissions;
|
throw new Error(response.message || 'getInfo: user info is empty');
|
||||||
this.setPermissions(permissionsList);
|
|
||||||
this.setUserInfo(result);
|
|
||||||
} else {
|
|
||||||
throw new Error('getInfo: permissionsList must be a non-null array !');
|
|
||||||
}
|
}
|
||||||
this.setAvatar(result.avatar);
|
|
||||||
return result;
|
const permissionsList = data.permissions ?? [];
|
||||||
|
const userInfo: UserInfoType = {
|
||||||
|
...data,
|
||||||
|
email: '',
|
||||||
|
permissions: permissionsList,
|
||||||
|
};
|
||||||
|
this.setPermissions(permissionsList);
|
||||||
|
this.setUserInfo(userInfo);
|
||||||
|
this.setAvatar(data.avatarUrl || '');
|
||||||
|
return userInfo;
|
||||||
},
|
},
|
||||||
|
|
||||||
// 登出
|
// 登出
|
||||||
async logout() {
|
async logout() {
|
||||||
this.setPermissions([]);
|
try {
|
||||||
this.setUserInfo({ username: '', email: '' });
|
await logoutApi();
|
||||||
storage.remove(ACCESS_TOKEN);
|
} finally {
|
||||||
storage.remove(CURRENT_USER);
|
this.setToken('');
|
||||||
|
this.setPermissions([]);
|
||||||
|
this.setAvatar('');
|
||||||
|
this.setUserInfo({ username: '', email: '', permissions: [] });
|
||||||
|
storage.remove(ACCESS_TOKEN);
|
||||||
|
storage.remove(CURRENT_USER);
|
||||||
|
storage.remove(IS_SCREENLOCKED);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -10,3 +10,14 @@ export function formatToDateTime(date: Date | number, formatStr = DATE_TIME_FORM
|
|||||||
export function formatToDate(date: Date | number, formatStr = DATE_FORMAT): string {
|
export function formatToDate(date: Date | number, formatStr = DATE_FORMAT): string {
|
||||||
return format(date, formatStr);
|
return format(date, formatStr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function formatBackendDateTime(value?: string | null): string {
|
||||||
|
if (!value) return '-';
|
||||||
|
|
||||||
|
const normalizedValue = value.trim().replace('T', ' ');
|
||||||
|
const [datePart, rawTimePart] = normalizedValue.split(' ');
|
||||||
|
if (!rawTimePart) return normalizedValue;
|
||||||
|
|
||||||
|
const timePart = rawTimePart.split('.')[0].slice(0, 8);
|
||||||
|
return `${datePart} ${timePart}`;
|
||||||
|
}
|
||||||
|
|||||||
@@ -34,6 +34,17 @@ const mockAdapter = createAlovaMockAdapter([...mocks], {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function redirectToLogin() {
|
||||||
|
const loginPath = PageEnum.BASE_LOGIN;
|
||||||
|
const { pathname, search, hash } = window.location;
|
||||||
|
const currentPath = `${pathname}${search}${hash}`;
|
||||||
|
const redirectPath =
|
||||||
|
pathname === loginPath ? loginPath : `${loginPath}?redirect=${encodeURIComponent(currentPath)}`;
|
||||||
|
|
||||||
|
storage.clear();
|
||||||
|
window.location.replace(redirectPath);
|
||||||
|
}
|
||||||
|
|
||||||
export const Alova = createAlova({
|
export const Alova = createAlova({
|
||||||
baseURL: apiUrl,
|
baseURL: apiUrl,
|
||||||
statesHook: VueHook,
|
statesHook: VueHook,
|
||||||
@@ -59,7 +70,7 @@ export const Alova = createAlova({
|
|||||||
const token = userStore.getToken;
|
const token = userStore.getToken;
|
||||||
// 添加 token 到请求头
|
// 添加 token 到请求头
|
||||||
if (!method.meta?.ignoreToken && token) {
|
if (!method.meta?.ignoreToken && token) {
|
||||||
method.config.headers['token'] = token;
|
method.config.headers['Authorization'] = `Bearer ${token}`;
|
||||||
}
|
}
|
||||||
// 处理 api 请求前缀
|
// 处理 api 请求前缀
|
||||||
const isUrlStr = isUrl(method.url as string);
|
const isUrlStr = isUrl(method.url as string);
|
||||||
@@ -91,6 +102,12 @@ export const Alova = createAlova({
|
|||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const responseCode = Number(res?.code);
|
||||||
|
if (responseCode === ResultEnum.LOGIN_EXPIRED) {
|
||||||
|
redirectToLogin();
|
||||||
|
throw new Error(res?.message || '请先登录');
|
||||||
|
}
|
||||||
|
|
||||||
// 是否返回原生响应头 比如:需要获取响应头时使用该属性
|
// 是否返回原生响应头 比如:需要获取响应头时使用该属性
|
||||||
if (method.meta?.isReturnNativeResponse) {
|
if (method.meta?.isReturnNativeResponse) {
|
||||||
return res;
|
return res;
|
||||||
|
|||||||
@@ -59,12 +59,12 @@
|
|||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { reactive, ref } from 'vue';
|
import { reactive, ref } from 'vue';
|
||||||
import { useRoute, useRouter } from 'vue-router';
|
import { useRouter } from 'vue-router';
|
||||||
import { useUserStore } from '@/store/modules/user';
|
import { useUserStore } from '@/store/modules/user';
|
||||||
import { useMessage } from 'naive-ui';
|
import { useMessage } from 'naive-ui';
|
||||||
import { ResultEnum } from '@/enums/httpEnum';
|
import { ResultEnum } from '@/enums/httpEnum';
|
||||||
import { PersonOutline, LockClosedOutline, LogoGithub, LogoFacebook } from '@vicons/ionicons5';
|
|
||||||
import { PageEnum } from '@/enums/pageEnum';
|
import { PageEnum } from '@/enums/pageEnum';
|
||||||
|
import { PersonOutline, LockClosedOutline } from '@vicons/ionicons5';
|
||||||
import { websiteConfig } from '@/config/website.config';
|
import { websiteConfig } from '@/config/website.config';
|
||||||
interface FormState {
|
interface FormState {
|
||||||
username: string;
|
username: string;
|
||||||
@@ -75,11 +75,9 @@
|
|||||||
const message = useMessage();
|
const message = useMessage();
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
const autoLogin = ref(true);
|
const autoLogin = ref(true);
|
||||||
const LOGIN_NAME = PageEnum.BASE_LOGIN_NAME;
|
|
||||||
|
|
||||||
const formInline = reactive({
|
const formInline = reactive({
|
||||||
username: 'admin',
|
username: 'FinAdmin',
|
||||||
password: '123456',
|
password: 'Fin9527',
|
||||||
isCaptcha: true,
|
isCaptcha: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -91,7 +89,6 @@
|
|||||||
const userStore = useUserStore();
|
const userStore = useUserStore();
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const route = useRoute();
|
|
||||||
|
|
||||||
const handleSubmit = (e) => {
|
const handleSubmit = (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@@ -110,11 +107,8 @@
|
|||||||
const { code, message: msg } = await userStore.login(params);
|
const { code, message: msg } = await userStore.login(params);
|
||||||
message.destroyAll();
|
message.destroyAll();
|
||||||
if (code == ResultEnum.SUCCESS) {
|
if (code == ResultEnum.SUCCESS) {
|
||||||
const toPath = decodeURIComponent((route.query?.redirect || '/') as string);
|
|
||||||
message.success('登录成功,即将进入系统');
|
message.success('登录成功,即将进入系统');
|
||||||
if (route.name === LOGIN_NAME) {
|
router.replace(PageEnum.BASE_HOME);
|
||||||
router.replace('/');
|
|
||||||
} else router.replace(toPath);
|
|
||||||
} else {
|
} else {
|
||||||
message.info(msg || '登录失败');
|
message.info(msg || '登录失败');
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user