feat: 接入真实登录鉴权流程

This commit is contained in:
湛兮
2026-05-26 14:45:15 +08:00
parent a6c9f5dee3
commit 5003628017
21 changed files with 572 additions and 305 deletions
+4 -4
View File
@@ -25,12 +25,12 @@ export const usePermissionStore = defineStore("pure-permission", {
actions: {
/** 组装整体路由生成的菜单 */
handleWholeMenus(routes: any[]) {
this.wholeMenus = filterNoPermissionTree(
const menus = filterNoPermissionTree(
filterTree(ascending(this.constantMenus.concat(routes)))
);
this.flatteningRoutes = formatFlatteningRoutes(
this.constantMenus.concat(routes) as any
);
this.wholeMenus = menus;
this.flatteningRoutes = formatFlatteningRoutes(menus as any);
},
/** 监听缓存页面是否存在于标签页,不存在则删除 */
clearCache() {
+162 -55
View File
@@ -4,42 +4,110 @@ import {
store,
router,
resetRouter,
routerArrays,
storageLocal
} from "../utils";
import {
type UserResult,
type RefreshTokenResult,
getLogin,
refreshTokenApi
type AuthUser,
type LoginInput,
type LoginResult,
type PermissionMenu,
getCurrentPermissions,
getCurrentUser,
loginAdmin
} from "@/api/user";
import { usePermissionStoreHook } from "./permission";
import { useMultiTagsStoreHook } from "./multiTags";
import { type DataInfo, setToken, removeToken, userKey } from "@/utils/auth";
import {
type DataInfo,
setToken,
getToken,
removeToken,
userKey
} from "@/utils/auth";
const savedUser = storageLocal().getItem<DataInfo<number>>(userKey);
function resolveExpiresAt(expiresIn: string) {
const matched = /^(\d+)([smhd])$/.exec(expiresIn);
const amount = matched ? Number(matched[1]) : 2;
const unit = matched?.[2] ?? "h";
const unitMap = {
s: 1000,
m: 60 * 1000,
h: 60 * 60 * 1000,
d: 24 * 60 * 60 * 1000
};
return Date.now() + amount * unitMap[unit];
}
function toUserContext(
user: AuthUser,
token: Pick<DataInfo<number>, "accessToken" | "expires">,
permissionMenus: PermissionMenu[],
permissions: string[]
): DataInfo<number> {
return {
...token,
username: user.username,
nickname: user.displayName,
accountType: user.accountType,
storeId: user.storeId,
storeName: user.storeName,
roles: user.roles.map(role => role.code),
permissions,
permissionMenus,
canManage: user.canManage
};
}
function collectMenuTags(menus: any[]) {
const result = [];
menus.forEach(item => {
if (item?.children?.length) {
result.push(...collectMenuTags(item.children));
return;
}
if (item?.meta?.showLink === false || item?.meta?.hiddenTag) return;
result.push(item);
});
return result;
}
export const useUserStore = defineStore("pure-user", {
state: (): userType => ({
// 头像
avatar: storageLocal().getItem<DataInfo<number>>(userKey)?.avatar ?? "",
// 用户名
username:
storageLocal().getItem<DataInfo<number>>(userKey)?.username ?? "admin",
// 昵称
nickname:
storageLocal().getItem<DataInfo<number>>(userKey)?.nickname ??
"系统管理员",
// 页面级别权限
roles: storageLocal().getItem<DataInfo<number>>(userKey)?.roles ?? [
"admin"
],
// 按钮级别权限
permissions:
storageLocal().getItem<DataInfo<number>>(userKey)?.permissions ?? [],
avatar: "",
username: savedUser?.username ?? "",
nickname: savedUser?.nickname ?? "",
accountType: savedUser?.accountType ?? "",
storeId: savedUser?.storeId,
storeName: savedUser?.storeName,
roles: savedUser?.roles ?? [],
permissions: savedUser?.permissions ?? [],
permissionMenus: savedUser?.permissionMenus ?? [],
canManage: savedUser?.canManage ?? false,
authLoaded: false,
// 是否勾选了登录页的免登录
isRemembered: false,
// 登录页的免登录存储几天,默认7天
loginDay: 7
}),
actions: {
/** 写入后端认证上下文,并同步 pure-admin 仍在读取的用户字段。 */
SET_USER_CONTEXT(data: Partial<DataInfo<number>>) {
this.username = data.username ?? "";
this.nickname = data.nickname ?? "";
this.accountType = data.accountType ?? "";
this.storeId = data.storeId;
this.storeName = data.storeName;
this.roles = data.roles ?? [];
this.permissions = data.permissions ?? [];
this.permissionMenus = data.permissionMenus ?? [];
this.canManage = data.canManage ?? false;
},
/** 存储头像 */
SET_AVATAR(avatar: string) {
this.avatar = avatar;
@@ -68,44 +136,83 @@ export const useUserStore = defineStore("pure-user", {
SET_LOGINDAY(value: number) {
this.loginDay = Number(value);
},
/** 登入 */
async loginByUsername(data) {
return new Promise<UserResult>((resolve, reject) => {
getLogin(data)
.then(data => {
if (data?.success) setToken(data.data);
resolve(data);
})
.catch(error => {
reject(error);
});
});
syncPermissionMenus() {
const permissionStore = usePermissionStoreHook();
permissionStore.handleWholeMenus([]);
if (!useMultiTagsStoreHook().getMultiTagsCache) {
useMultiTagsStoreHook().handleTags(
"equal",
collectMenuTags(permissionStore.wholeMenus)
);
}
},
/** 前端登出(不调用接口) */
/** 登入:按 API.md 推荐流程完成 login -> me -> permissions/me。 */
async loginByUsername(data: LoginInput) {
const loginResult: LoginResult = await loginAdmin(data);
if (loginResult?.success) {
const context = toUserContext(
loginResult.data.user,
{
accessToken: loginResult.data.token,
expires: resolveExpiresAt(loginResult.data.expiresIn)
},
[],
loginResult.data.user.permissions
);
setToken(context);
this.SET_USER_CONTEXT(context);
await this.loadAuthContext(true);
}
return loginResult;
},
/** 页面刷新或重新进入受保护页面时,向后端校验 token 并恢复权限上下文。 */
async loadAuthContext(force = false) {
const token = getToken();
if (!token?.accessToken) {
throw new Error("请先登录");
}
if (this.authLoaded && !force) {
this.syncPermissionMenus();
return;
}
const [userResult, permissionResult] = await Promise.all([
getCurrentUser(),
getCurrentPermissions()
]);
const context = toUserContext(
userResult.data,
token,
permissionResult.data.menus,
permissionResult.data.permissions
);
setToken(context);
this.SET_USER_CONTEXT(context);
this.authLoaded = true;
this.syncPermissionMenus();
},
/** 前端登出(后端当前无登出接口) */
logOut() {
this.username = "admin";
this.nickname = "系统管理员";
this.roles = ["admin"];
this.username = "";
this.nickname = "";
this.accountType = "";
this.storeId = undefined;
this.storeName = undefined;
this.roles = [];
this.permissions = [];
this.permissionMenus = [];
this.canManage = false;
this.authLoaded = false;
removeToken();
useMultiTagsStoreHook().handleTags("equal", [...routerArrays]);
useMultiTagsStoreHook().handleTags("equal", []);
resetRouter();
router.push("/employees");
},
/** 刷新`token` */
async handRefreshToken(data) {
return new Promise<RefreshTokenResult>((resolve, reject) => {
refreshTokenApi(data)
.then(data => {
if (data) {
setToken(data.data);
resolve(data);
}
})
.catch(error => {
reject(error);
});
});
router.push("/login");
}
}
});