mirror of
https://github.com/pure-admin/pure-admin-thin.git
synced 2025-04-24 23:47:17 +08:00
update
This commit is contained in:
parent
f0c4f1f39e
commit
a49f46a25e
@ -11,45 +11,45 @@ import {
|
||||
devDependencies
|
||||
} from "../package.json";
|
||||
|
||||
/** 启动`node`进程时所在工作目录的绝对路径 */
|
||||
/** Đường dẫn tuyệt đối của thư mục làm việc hiện tại khi khởi động tiến trình `node` */
|
||||
const root: string = process.cwd();
|
||||
|
||||
/**
|
||||
* @description 根据可选的路径片段生成一个新的绝对路径
|
||||
* @param dir 路径片段,默认`build`
|
||||
* @param metaUrl 模块的完整`url`,如果在`build`目录外调用必传`import.meta.url`
|
||||
* @description Tạo một đường dẫn tuyệt đối mới dựa trên đoạn đường dẫn tùy chọn
|
||||
* @param dir Đoạn đường dẫn, mặc định là `build`
|
||||
* @param metaUrl URL đầy đủ của mô-đun, cần truyền `import.meta.url` nếu gọi ngoài thư mục `build`
|
||||
*/
|
||||
const pathResolve = (dir = ".", metaUrl = import.meta.url) => {
|
||||
// 当前文件目录的绝对路径
|
||||
// Đường dẫn tuyệt đối của thư mục hiện tại
|
||||
const currentFileDir = dirname(fileURLToPath(metaUrl));
|
||||
// build 目录的绝对路径
|
||||
// Đường dẫn tuyệt đối của thư mục build
|
||||
const buildDir = resolve(currentFileDir, "build");
|
||||
// 解析的绝对路径
|
||||
// Đường dẫn tuyệt đối đã được giải quyết
|
||||
const resolvedPath = resolve(currentFileDir, dir);
|
||||
// 检查解析的绝对路径是否在 build 目录内
|
||||
// Kiểm tra xem đường dẫn tuyệt đối đã giải quyết có nằm trong thư mục build không
|
||||
if (resolvedPath.startsWith(buildDir)) {
|
||||
// 在 build 目录内,返回当前文件路径
|
||||
// Nếu trong thư mục build, trả về đường dẫn tệp hiện tại
|
||||
return fileURLToPath(metaUrl);
|
||||
}
|
||||
// 不在 build 目录内,返回解析后的绝对路径
|
||||
// Nếu không trong thư mục build, trả về đường dẫn tuyệt đối đã giải quyết
|
||||
return resolvedPath;
|
||||
};
|
||||
|
||||
/** 设置别名 */
|
||||
/** Đặt biệt danh */
|
||||
const alias: Record<string, string> = {
|
||||
"@": pathResolve("../src"),
|
||||
"@build": pathResolve()
|
||||
};
|
||||
|
||||
/** 平台的名称、版本、运行所需的`node`和`pnpm`版本、依赖、最后构建时间的类型提示 */
|
||||
/** Thông tin về tên nền tảng, phiên bản, phiên bản `node` và `pnpm` cần thiết để chạy, phụ thuộc, và thời gian xây dựng cuối cùng */
|
||||
const __APP_INFO__ = {
|
||||
pkg: { name, version, engines, dependencies, devDependencies },
|
||||
lastBuildTime: dayjs(new Date()).format("YYYY-MM-DD HH:mm:ss")
|
||||
};
|
||||
|
||||
/** 处理环境变量 */
|
||||
/** Xử lý biến môi trường */
|
||||
const wrapperEnv = (envConf: Recordable): ViteEnv => {
|
||||
// 默认值
|
||||
// Giá trị mặc định
|
||||
const ret: ViteEnv = {
|
||||
VITE_PORT: 8848,
|
||||
VITE_PUBLIC_PATH: "",
|
||||
@ -79,7 +79,7 @@ const wrapperEnv = (envConf: Recordable): ViteEnv => {
|
||||
|
||||
const fileListTotal: number[] = [];
|
||||
|
||||
/** 获取指定文件夹中所有文件的总大小 */
|
||||
/** Lấy tổng kích thước của tất cả các tệp trong thư mục chỉ định */
|
||||
const getPackageSize = options => {
|
||||
const { folder = "dist", callback, format = true } = options;
|
||||
readdir(folder, (err, files: string[]) => {
|
||||
|
@ -75,15 +75,16 @@
|
||||
"vue": "^3.4.31",
|
||||
"vue-i18n": "^9.13.1",
|
||||
"vue-router": "^4.4.0",
|
||||
"xlsx": "^0.18.5",
|
||||
"vue-tippy": "^6.4.4",
|
||||
"vue-types": "^5.1.2",
|
||||
"vue-virtual-scroller": "2.0.0-beta.8",
|
||||
"vue-waterfall-plugin-next": "^2.4.3",
|
||||
"vue3-danmaku": "^1.6.0",
|
||||
"vue3-oidc": "^0.1.15",
|
||||
"vue3-puzzle-vcode": "^1.1.7",
|
||||
"vuedraggable": "^4.1.0",
|
||||
"vxe-table": "4.6.17"
|
||||
"vxe-table": "4.6.17",
|
||||
"xlsx": "^0.18.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@commitlint/cli": "^19.3.0",
|
||||
|
39
pnpm-lock.yaml
generated
39
pnpm-lock.yaml
generated
@ -107,6 +107,9 @@ importers:
|
||||
vue3-danmaku:
|
||||
specifier: ^1.6.0
|
||||
version: 1.6.0(vue@3.4.31(typescript@5.5.3))
|
||||
vue3-oidc:
|
||||
specifier: ^0.1.15
|
||||
version: 0.1.15(vue@3.4.31(typescript@5.5.3))
|
||||
vue3-puzzle-vcode:
|
||||
specifier: ^1.1.7
|
||||
version: 1.1.7
|
||||
@ -1829,6 +1832,9 @@ packages:
|
||||
resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==}
|
||||
engines: {node: '>= 8'}
|
||||
|
||||
crypto-js@4.2.0:
|
||||
resolution: {integrity: sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==}
|
||||
|
||||
css-declaration-sorter@6.4.1:
|
||||
resolution: {integrity: sha512-rtdthzxKuyq6IzqX6jEcIzQF/YqccluefyCYheovBOLhFT/drQA9zj/UbRAa9J7C0o6EG6u3E6g+vKkay7/k3g==}
|
||||
engines: {node: ^10 || ^12 || >=14}
|
||||
@ -2679,6 +2685,9 @@ packages:
|
||||
resolution: {integrity: sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==}
|
||||
engines: {'0': node >= 0.2.0}
|
||||
|
||||
jwt-decode@3.1.2:
|
||||
resolution: {integrity: sha512-UfpWE/VZn0iP50d8cz9NrZLM9lSWhcJ+0Gt/nm4by88UL+J1SiKN8/5dkjMmbEzwL2CAe+67GsegCbIKtbp75A==}
|
||||
|
||||
keyv@4.5.4:
|
||||
resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==}
|
||||
|
||||
@ -2997,6 +3006,10 @@ packages:
|
||||
ohash@1.1.3:
|
||||
resolution: {integrity: sha512-zuHHiGTYTA1sYJ/wZN+t5HKZaH23i4yI1HMwbuXm24Nid7Dv0KcuRlKoNKS9UNfAVSBlnGLcuQrnOKWOZoEGaw==}
|
||||
|
||||
oidc-client-ts@2.4.0:
|
||||
resolution: {integrity: sha512-WijhkTrlXK2VvgGoakWJiBdfIsVGz6CFzgjNNqZU1hPKV2kyeEaJgLs7RwuiSp2WhLfWBQuLvr2SxVlZnk3N1w==}
|
||||
engines: {node: '>=12.13.0'}
|
||||
|
||||
onetime@5.1.2:
|
||||
resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==}
|
||||
engines: {node: '>=6'}
|
||||
@ -4269,6 +4282,14 @@ packages:
|
||||
peerDependencies:
|
||||
vue: ^3.0.0
|
||||
|
||||
vue3-oidc@0.1.15:
|
||||
resolution: {integrity: sha512-PltH0HVadyHbLPYAoxia3BuVozX87FrTVFcC5lTYJeRsKc0/Eh2x7jLggZNmpkRWJzKo0J1g/FhwybYuKtV7uw==}
|
||||
peerDependencies:
|
||||
vue: '>=3.0.0'
|
||||
peerDependenciesMeta:
|
||||
vue:
|
||||
optional: true
|
||||
|
||||
vue3-puzzle-vcode@1.1.7:
|
||||
resolution: {integrity: sha512-mW780dz7HKjrElnE60CeYSeHGidKBKHoMjTDYfqF21330rTkFOsfDK1FQKZ22MktgMtTEoS/imfpEDlM1cxY/g==}
|
||||
|
||||
@ -6041,6 +6062,8 @@ snapshots:
|
||||
shebang-command: 2.0.0
|
||||
which: 2.0.2
|
||||
|
||||
crypto-js@4.2.0: {}
|
||||
|
||||
css-declaration-sorter@6.4.1(postcss@8.4.39):
|
||||
dependencies:
|
||||
postcss: 8.4.39
|
||||
@ -7009,6 +7032,8 @@ snapshots:
|
||||
|
||||
jsonparse@1.3.1: {}
|
||||
|
||||
jwt-decode@3.1.2: {}
|
||||
|
||||
keyv@4.5.4:
|
||||
dependencies:
|
||||
json-buffer: 3.0.1
|
||||
@ -7292,6 +7317,11 @@ snapshots:
|
||||
ohash@1.1.3:
|
||||
optional: true
|
||||
|
||||
oidc-client-ts@2.4.0:
|
||||
dependencies:
|
||||
crypto-js: 4.2.0
|
||||
jwt-decode: 3.1.2
|
||||
|
||||
onetime@5.1.2:
|
||||
dependencies:
|
||||
mimic-fn: 2.1.0
|
||||
@ -8597,6 +8627,15 @@ snapshots:
|
||||
dependencies:
|
||||
vue: 3.4.31(typescript@5.5.3)
|
||||
|
||||
vue3-oidc@0.1.15(vue@3.4.31(typescript@5.5.3)):
|
||||
dependencies:
|
||||
'@vueuse/core': 10.11.0(vue@3.4.31(typescript@5.5.3))
|
||||
oidc-client-ts: 2.4.0
|
||||
optionalDependencies:
|
||||
vue: 3.4.31(typescript@5.5.3)
|
||||
transitivePeerDependencies:
|
||||
- '@vue/composition-api'
|
||||
|
||||
vue3-puzzle-vcode@1.1.7: {}
|
||||
|
||||
vue@3.4.31(typescript@5.5.3):
|
||||
|
@ -18,7 +18,7 @@
|
||||
"SidebarStatus": true,
|
||||
"EpThemeColor": "#409EFF",
|
||||
"ShowLogo": true,
|
||||
"ShowModel": "smart",
|
||||
"ShowModel": "chrome",
|
||||
"MenuArrowIconNoTransition": false,
|
||||
"CachingAsyncRoutes": false,
|
||||
"TooltipEffect": "light",
|
||||
|
11
public/silent-renew.html
Normal file
11
public/silent-renew.html
Normal file
@ -0,0 +1,11 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
</head>
|
||||
<script type="module" src="./silent-renew.ts"></script>
|
||||
|
||||
<body></body>
|
||||
|
||||
</html>
|
20
public/slient-renew.ts
Normal file
20
public/slient-renew.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import { useOidcStore } from "vue3-oidc";
|
||||
|
||||
const { state: oidcState } = useOidcStore();
|
||||
oidcState.value.userManager?.signinSilentCallback();
|
||||
|
||||
const LOGIN_REQUIRED = "login_required";
|
||||
const searchParams = new URLSearchParams(window.location.search);
|
||||
const error = searchParams.get("error");
|
||||
const state = searchParams.get("state");
|
||||
const isLocalService = location.origin.includes(
|
||||
"://localhost" || "0.0.0.0" || "127.0.0.0"
|
||||
);
|
||||
|
||||
console.error(`Error: \n error: ${error} \n state: ${state}`);
|
||||
|
||||
if (error === LOGIN_REQUIRED && import.meta.env && !isLocalService) {
|
||||
localStorage.clear();
|
||||
sessionStorage.clear();
|
||||
top?.location.reload();
|
||||
}
|
11
silent-renew.html
Normal file
11
silent-renew.html
Normal file
@ -0,0 +1,11 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
</head>
|
||||
<script type="module" src="./silent-renew.ts"></script>
|
||||
|
||||
<body></body>
|
||||
|
||||
</html>
|
20
slient-renew.ts
Normal file
20
slient-renew.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import { useOidcStore } from "vue3-oidc";
|
||||
|
||||
const { state: oidcState } = useOidcStore();
|
||||
oidcState.value.userManager?.signinSilentCallback();
|
||||
|
||||
const LOGIN_REQUIRED = "login_required";
|
||||
const searchParams = new URLSearchParams(window.location.search);
|
||||
const error = searchParams.get("error");
|
||||
const state = searchParams.get("state");
|
||||
const isLocalService = location.origin.includes(
|
||||
"://localhost" || "0.0.0.0" || "127.0.0.0"
|
||||
);
|
||||
|
||||
console.error(`Error: \n error: ${error} \n state: ${state}`);
|
||||
|
||||
if (error === LOGIN_REQUIRED && import.meta.env && !isLocalService) {
|
||||
localStorage.clear();
|
||||
sessionStorage.clear();
|
||||
top?.location.reload();
|
||||
}
|
31
src/Callback.vue
Normal file
31
src/Callback.vue
Normal file
@ -0,0 +1,31 @@
|
||||
<script lang="ts" setup>
|
||||
import { computed, onMounted, unref } from "vue";
|
||||
import { useRouter } from "vue-router";
|
||||
import { useOidcStore } from "vue3-oidc";
|
||||
const { state, actions } = useOidcStore();
|
||||
|
||||
const userManager = computed(() => state.value.userManager);
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
/**
|
||||
* manually handle login and will save user to state
|
||||
* Xử lý đăng nhập thủ công và lưu người dùng vào trạng thái
|
||||
*/
|
||||
onMounted(async () => {
|
||||
let user = (await unref(state).userManager?.getUser()) || unref(state).user;
|
||||
if (!user) {
|
||||
user = (await userManager.value?.signinRedirectCallback()) || null;
|
||||
}
|
||||
actions.value.setUser(user!);
|
||||
router.push("/welcome");
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<h1>OIDC-CALLBACK</h1>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped></style>
|
31
src/Signout.vue
Normal file
31
src/Signout.vue
Normal file
@ -0,0 +1,31 @@
|
||||
<script lang="ts" setup>
|
||||
import { computed } from "vue";
|
||||
import { useRouter } from "vue-router";
|
||||
import { useAuth, useOidcStore } from "vue3-oidc";
|
||||
const router = useRouter();
|
||||
|
||||
const { state } = useOidcStore();
|
||||
|
||||
const userManager = computed(() => state.value.userManager);
|
||||
|
||||
/**
|
||||
* manually login will be redirected to the callback route
|
||||
* Đăng nhập thủ công sẽ được chuyển hướng đến tuyến gọi lại
|
||||
*/
|
||||
const { signinRedirect } = useAuth();
|
||||
|
||||
const popup = () => {
|
||||
userManager.value?.signinPopup();
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<h1>Chưa đăng nhập</h1>
|
||||
<button @click="router.push('/Welcome')">Home</button>
|
||||
<button @click="signinRedirect!()">SignIn</button>
|
||||
<button @click="popup">Popup_SignIn</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped></style>
|
@ -40,7 +40,7 @@ export function useLayout() {
|
||||
hideTabs: $config?.HideTabs ?? false,
|
||||
hideFooter: $config.HideFooter ?? true,
|
||||
showLogo: $config?.ShowLogo ?? true,
|
||||
showModel: $config?.ShowModel ?? "smart",
|
||||
showModel: $config?.ShowModel ?? "chrome",
|
||||
multiTagsCache: $config?.MultiTagsCache ?? false,
|
||||
stretch: $config?.Stretch ?? false
|
||||
};
|
||||
|
@ -20,6 +20,7 @@ import "./style/tailwind.css";
|
||||
import "element-plus/dist/index.css"; // Import CSS của Element Plus
|
||||
import "./assets/iconfont/iconfont.js"; // Nhập icon font
|
||||
import "./assets/iconfont/iconfont.css"; // Import CSS của icon font
|
||||
import "./oidc/oidc";
|
||||
|
||||
const app = createApp(App); // Tạo một ứng dụng Vue mới với component App.vue
|
||||
|
||||
|
65
src/oidc/oidc.ts
Normal file
65
src/oidc/oidc.ts
Normal file
@ -0,0 +1,65 @@
|
||||
import { WebStorageStateStore } from "oidc-client-ts";
|
||||
import { unref } from "vue";
|
||||
import type { VueOidcSettings } from "vue3-oidc";
|
||||
import { createOidc, useOidcStore } from "vue3-oidc";
|
||||
import { setToken } from "@/utils/auth";
|
||||
import dayjs from "dayjs";
|
||||
import { message } from "@/utils/message";
|
||||
const { state } = useOidcStore();
|
||||
|
||||
const oidcSettings: VueOidcSettings = {
|
||||
authority: "https://identity.medlatec.vn",
|
||||
scope: "openid emp_id profile",
|
||||
client_id: "App_vuejs_4a29575e49a579b2f96722c38578dfc9",
|
||||
client_secret:
|
||||
"secret_a252294002546cc7cc455f6a8b0ccb6bd1e6ad0b50ceb60db902a08080918ebd",
|
||||
redirect_uri: origin + "/oidc-callback",
|
||||
post_logout_redirect_uri: origin + "/signout",
|
||||
response_type: "code",
|
||||
loadUserInfo: true,
|
||||
userStore: new WebStorageStateStore({
|
||||
prefix: "admin-pure-oidc",
|
||||
store: window.sessionStorage
|
||||
}),
|
||||
automaticSilentRenew: true,
|
||||
monitorSession: true,
|
||||
silent_redirect_uri: location.origin + "/silent-renew.html",
|
||||
onSigninRedirectCallback() {
|
||||
message("Login success", { type: "success" });
|
||||
location.href = unref(state).redirect_uri || "/welcome";
|
||||
}
|
||||
};
|
||||
|
||||
function runAuth() {
|
||||
createOidc({
|
||||
oidcSettings: oidcSettings, //your oidc settings
|
||||
auth: false, //if auth is true,will auto authenticate
|
||||
refreshToken: {
|
||||
enable: true,
|
||||
time: 10 * 1000
|
||||
},
|
||||
//your oidc events
|
||||
events: {
|
||||
addUserLoaded: user => {
|
||||
const avv = {
|
||||
/** Token truy cập */
|
||||
accessToken: user.access_token.toString(),
|
||||
/** Thời gian hết hạn của accessToken (dưới dạng timestamp) */
|
||||
expires: dayjs(user.expires_at).toDate(),
|
||||
/** Token dùng để làm mới accessToken */
|
||||
refreshToken: user.profile.emp_id.toString(),
|
||||
/** Ảnh đại diện */
|
||||
avatar: "https://api-hr.medlatec.vn/" + user.profile.avatar,
|
||||
/** Tên đăng nhập */
|
||||
username: user.profile.preferred_username.toString(),
|
||||
/** Biệt danh */
|
||||
nickname: user.profile.full_name.toString()
|
||||
};
|
||||
setToken(avv);
|
||||
},
|
||||
addUserSignedOut: () => {}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
runAuth();
|
@ -32,9 +32,9 @@ import {
|
||||
multipleTabsKey
|
||||
} from "@/utils/auth";
|
||||
|
||||
/** 自动导入全部静态路由,无需再手动引入!匹配 src/router/modules 目录(任何嵌套级别)中具有 .ts 扩展名的所有文件,除了 remaining.ts 文件
|
||||
* 如何匹配所有文件请看:https://github.com/mrmlnc/fast-glob#basic-syntax
|
||||
* 如何排除文件请看:https://cn.vitejs.dev/guide/features.html#negative-patterns
|
||||
/** Tự động nhập tất cả các tuyến tĩnh, không cần nhập thủ công nữa! Khớp với tất cả các tệp có phần mở rộng .ts trong thư mục src/router/modules (bất kỳ cấp độ lồng nhau nào), ngoại trừ tệp remaining.ts
|
||||
* Cách khớp tất cả các tệp xem tại: https://github.com/mrmlnc/fast-glob#basic-syntax
|
||||
* Cách loại trừ tệp xem tại: https://cn.vitejs.dev/guide/features.html#negative-patterns
|
||||
*/
|
||||
const modules: Record<string, any> = import.meta.glob(
|
||||
["./modules/**/*.ts", "!./modules/**/remaining.ts"],
|
||||
@ -43,29 +43,29 @@ const modules: Record<string, any> = import.meta.glob(
|
||||
}
|
||||
);
|
||||
|
||||
/** 原始静态路由(未做任何处理) */
|
||||
/** Các tuyến tĩnh gốc (chưa xử lý) */
|
||||
const routes = [];
|
||||
|
||||
Object.keys(modules).forEach(key => {
|
||||
routes.push(modules[key].default);
|
||||
});
|
||||
|
||||
/** 导出处理后的静态路由(三级及以上的路由全部拍成二级) */
|
||||
/** Xuất các tuyến tĩnh đã được xử lý (tất cả các tuyến trên cấp 3 đều phẳng thành cấp 2) */
|
||||
export const constantRoutes: Array<RouteRecordRaw> = formatTwoStageRoutes(
|
||||
formatFlatteningRoutes(buildHierarchyTree(ascending(routes.flat(Infinity))))
|
||||
);
|
||||
|
||||
/** 用于渲染菜单,保持原始层级 */
|
||||
/** Dùng để render menu, giữ nguyên cấp độ ban đầu */
|
||||
export const constantMenus: Array<RouteComponent> = ascending(
|
||||
routes.flat(Infinity)
|
||||
).concat(...remainingRouter);
|
||||
|
||||
/** 不参与菜单的路由 */
|
||||
/** Các tuyến không tham gia vào menu */
|
||||
export const remainingPaths = Object.keys(remainingRouter).map(v => {
|
||||
return remainingRouter[v].path;
|
||||
});
|
||||
|
||||
/** 创建路由实例 */
|
||||
/** Tạo thể hiện router */
|
||||
export const router: Router = createRouter({
|
||||
history: getHistoryMode(import.meta.env.VITE_ROUTER_HISTORY),
|
||||
routes: constantRoutes.concat(...(remainingRouter as any)),
|
||||
@ -85,7 +85,7 @@ export const router: Router = createRouter({
|
||||
}
|
||||
});
|
||||
|
||||
/** 重置路由 */
|
||||
/** Đặt lại router */
|
||||
export function resetRouter() {
|
||||
router.getRoutes().forEach(route => {
|
||||
const { name, meta } = route;
|
||||
@ -101,7 +101,7 @@ export function resetRouter() {
|
||||
usePermissionStoreHook().clearAllCachePage();
|
||||
}
|
||||
|
||||
/** 路由白名单 */
|
||||
/** Danh sách trắng của router */
|
||||
const whiteList = ["/login"];
|
||||
|
||||
const { VITE_HIDE_HOME } = import.meta.env;
|
||||
@ -109,7 +109,7 @@ const { VITE_HIDE_HOME } = import.meta.env;
|
||||
router.beforeEach((to: ToRouteType, _from, next) => {
|
||||
if (to.meta?.keepAlive) {
|
||||
handleAliveRoute(to, "add");
|
||||
// 页面整体刷新和点击标签页刷新
|
||||
// Làm mới trang toàn bộ và làm mới khi nhấn vào tab
|
||||
if (_from.name === undefined || _from.name === "Redirect") {
|
||||
handleAliveRoute(to);
|
||||
}
|
||||
@ -126,21 +126,21 @@ router.beforeEach((to: ToRouteType, _from, next) => {
|
||||
else document.title = transformI18n(item.meta.title);
|
||||
});
|
||||
}
|
||||
/** 如果已经登录并存在登录信息后不能跳转到路由白名单,而是继续保持在当前页面 */
|
||||
/** Nếu đã đăng nhập và có thông tin đăng nhập thì không thể chuyển đến danh sách trắng của router, mà tiếp tục giữ ở trang hiện tại */
|
||||
function toCorrectRoute() {
|
||||
whiteList.includes(to.fullPath) ? next(_from.fullPath) : next();
|
||||
}
|
||||
if (Cookies.get(multipleTabsKey) && userInfo) {
|
||||
// 无权限跳转403页面
|
||||
// Không có quyền chuyển đến trang 403
|
||||
if (to.meta?.roles && !isOneOfArray(to.meta?.roles, userInfo?.roles)) {
|
||||
next({ path: "/error/403" });
|
||||
}
|
||||
// 开启隐藏首页后在浏览器地址栏手动输入首页welcome路由则跳转到404页面
|
||||
// Khi bật ẩn trang chủ, nhập tay vào thanh địa chỉ của trình duyệt sẽ chuyển đến trang 404
|
||||
if (VITE_HIDE_HOME === "true" && to.fullPath === "/welcome") {
|
||||
next({ path: "/error/404" });
|
||||
}
|
||||
if (_from?.name) {
|
||||
// name为超链接
|
||||
// name là liên kết ngoài
|
||||
if (externalLink) {
|
||||
openLink(to?.name as string);
|
||||
NProgress.done();
|
||||
@ -148,7 +148,7 @@ router.beforeEach((to: ToRouteType, _from, next) => {
|
||||
toCorrectRoute();
|
||||
}
|
||||
} else {
|
||||
// 刷新
|
||||
// Làm mới
|
||||
if (
|
||||
usePermissionStoreHook().wholeMenus.length === 0 &&
|
||||
to.path !== "/login"
|
||||
@ -161,10 +161,10 @@ router.beforeEach((to: ToRouteType, _from, next) => {
|
||||
router.options.routes[0].children
|
||||
);
|
||||
getTopMenu(true);
|
||||
// query、params模式路由传参数的标签页不在此处处理
|
||||
// query, params: route truyền tham số không xử lý ở đây
|
||||
if (route && route.meta?.title) {
|
||||
if (isAllEmpty(route.parentId) && route.meta?.backstage) {
|
||||
// 此处为动态顶级路由(目录)
|
||||
// Đây là route cấp cao động (thư mục)
|
||||
const { path, name, meta } = route.children[0];
|
||||
useMultiTagsStoreHook().handleTags("push", {
|
||||
path,
|
||||
@ -181,7 +181,7 @@ router.beforeEach((to: ToRouteType, _from, next) => {
|
||||
}
|
||||
}
|
||||
}
|
||||
// 确保动态路由完全加入路由列表并且不影响静态路由(注意:动态路由刷新时router.beforeEach可能会触发两次,第一次触发动态路由还未完全添加,第二次动态路由才完全添加到路由列表,如果需要在router.beforeEach做一些判断可以在to.name存在的条件下去判断,这样就只会触发一次)
|
||||
// Đảm bảo các route động được thêm hoàn toàn vào danh sách route và không ảnh hưởng đến route tĩnh (Lưu ý: Làm mới các route động có thể kích hoạt router.beforeEach hai lần, lần đầu tiên các route động chưa hoàn toàn được thêm vào, lần thứ hai các route động đã hoàn toàn được thêm vào danh sách route, nếu cần kiểm tra trong router.beforeEach, có thể kiểm tra khi to.name tồn tại, như vậy sẽ chỉ kích hoạt một lần)
|
||||
if (isAllEmpty(to.name)) router.push(to.fullPath);
|
||||
});
|
||||
}
|
||||
|
@ -15,7 +15,8 @@ import {
|
||||
} from "@/api/user";
|
||||
import { useMultiTagsStoreHook } from "./multiTags";
|
||||
import { type DataInfo, setToken, removeToken, userKey } from "@/utils/auth";
|
||||
|
||||
import { useAuth } from "vue3-oidc";
|
||||
const { signoutRedirect } = useAuth();
|
||||
export const useUserStore = defineStore({
|
||||
id: "pure-user",
|
||||
state: (): userType => ({
|
||||
@ -77,6 +78,7 @@ export const useUserStore = defineStore({
|
||||
removeToken();
|
||||
useMultiTagsStoreHook().handleTags("equal", [...routerArrays]);
|
||||
resetRouter();
|
||||
signoutRedirect();
|
||||
router.push("/login");
|
||||
},
|
||||
/** 刷新`token` */
|
||||
|
@ -24,7 +24,7 @@ export interface DataInfo<T> {
|
||||
export const userKey = "user-info"; // Key cho localStorage lưu thông tin người dùng
|
||||
export const TokenKey = "authorized-token"; // Key cho cookie lưu token truy cập
|
||||
export const multipleTabsKey = "multiple-tabs"; // Key cho cookie kiểm tra nhiều tab mở
|
||||
|
||||
export const oidcKey = "admin-pure-oidcuser"; // Key cho localStorage lưu thông tin người dùng
|
||||
/**
|
||||
* Hàm lấy thông tin token từ cookie hoặc localStorage
|
||||
* Nếu không tìm thấy token trong cookie, thử lấy từ localStorage
|
||||
@ -85,7 +85,7 @@ export function setToken(data: DataInfo<Date>) {
|
||||
}
|
||||
|
||||
// Kiểm tra và thiết lập thông tin người dùng
|
||||
if (data.username && data.roles) {
|
||||
if (data.username) {
|
||||
const { username, roles } = data;
|
||||
setUserKey({
|
||||
avatar: data?.avatar ?? "",
|
||||
|
@ -3,32 +3,34 @@ import { useI18n } from "vue-i18n";
|
||||
import Motion from "./utils/motion";
|
||||
import { useRouter } from "vue-router";
|
||||
import { message } from "@/utils/message";
|
||||
import { loginRules } from "./utils/rule";
|
||||
import { useNav } from "@/layout/hooks/useNav";
|
||||
import type { FormInstance } from "element-plus";
|
||||
import { $t, transformI18n } from "@/plugins/i18n";
|
||||
import { useLayout } from "@/layout/hooks/useLayout";
|
||||
import { useUserStoreHook } from "@/store/modules/user";
|
||||
import { initRouter, getTopMenu } from "@/router/utils";
|
||||
import { bg, avatar, illustration } from "./utils/static";
|
||||
import { useRenderIcon } from "@/components/ReIcon/src/hooks";
|
||||
import { ref, reactive, toRaw, onMounted, onBeforeUnmount } from "vue";
|
||||
import { useTranslationLang } from "@/layout/hooks/useTranslationLang";
|
||||
import { useDataThemeChange } from "@/layout/hooks/useDataThemeChange";
|
||||
|
||||
import dayjs from "dayjs";
|
||||
import dayIcon from "@/assets/svg/day.svg?component";
|
||||
import darkIcon from "@/assets/svg/dark.svg?component";
|
||||
import globalization from "@/assets/svg/globalization.svg?component";
|
||||
import Lock from "@iconify-icons/ri/lock-fill";
|
||||
import Check from "@iconify-icons/ep/check";
|
||||
import User from "@iconify-icons/ri/user-3-fill";
|
||||
|
||||
import {
|
||||
computed,
|
||||
onMounted,
|
||||
ref,
|
||||
reactive,
|
||||
onBeforeUnmount,
|
||||
toRaw
|
||||
} from "vue";
|
||||
import { useAuth, useOidcStore } from "vue3-oidc";
|
||||
import { type DataInfo, setToken, removeToken, userKey } from "@/utils/auth";
|
||||
const { signinRedirect, signoutRedirect, autoAuthenticate } = useAuth();
|
||||
|
||||
defineOptions({
|
||||
name: "Login"
|
||||
});
|
||||
const router = useRouter();
|
||||
const loading = ref(false);
|
||||
const ruleFormRef = ref<FormInstance>();
|
||||
|
||||
const { initStorage } = useLayout();
|
||||
initStorage();
|
||||
@ -39,49 +41,54 @@ dataThemeChange(overallStyle.value);
|
||||
const { title, getDropdownItemStyle, getDropdownItemClass } = useNav();
|
||||
const { locale, translationCh, translationEn } = useTranslationLang();
|
||||
|
||||
const ruleForm = reactive({
|
||||
username: "admin",
|
||||
password: "admin123"
|
||||
});
|
||||
|
||||
const onLogin = async (formEl: FormInstance | undefined) => {
|
||||
if (!formEl) return;
|
||||
await formEl.validate((valid, fields) => {
|
||||
if (valid) {
|
||||
loading.value = true;
|
||||
useUserStoreHook()
|
||||
.loginByUsername({ username: ruleForm.username, password: "admin123" })
|
||||
.then(res => {
|
||||
if (res.success) {
|
||||
// Fetch backend routes
|
||||
return initRouter().then(() => {
|
||||
router.push(getTopMenu(true).path).then(() => {
|
||||
message(t("login.pureLoginSuccess"), { type: "success" });
|
||||
});
|
||||
});
|
||||
} else {
|
||||
message(t("login.pureLoginFail"), { type: "error" });
|
||||
}
|
||||
})
|
||||
.finally(() => (loading.value = false));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/** Use common function to prevent `removeEventListener` failure */
|
||||
function onkeypress({ code }: KeyboardEvent) {
|
||||
if (["Enter", "NumpadEnter"].includes(code)) {
|
||||
onLogin(ruleFormRef.value);
|
||||
signin();
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
window.document.addEventListener("keypress", onkeypress);
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
window.document.removeEventListener("keypress", onkeypress);
|
||||
});
|
||||
|
||||
const { state } = useOidcStore();
|
||||
|
||||
const user = computed(() => state.value.user?.profile);
|
||||
|
||||
const signin = () => {
|
||||
signinRedirect();
|
||||
setLogin();
|
||||
//The following operations are completed in the callback route
|
||||
};
|
||||
|
||||
const setLogin = () => {
|
||||
if (state.value != null) {
|
||||
router.push("/welcome").then(() => {
|
||||
const avv = {
|
||||
/** Token truy cập */
|
||||
accessToken: state.value.token.toString(),
|
||||
/** Thời gian hết hạn của accessToken (dưới dạng timestamp) */
|
||||
expires: dayjs(state?.value.user.expires_in).toDate(),
|
||||
/** Token dùng để làm mới accessToken */
|
||||
refreshToken: user.value.emp_id.toString(),
|
||||
/** Ảnh đại diện */
|
||||
avatar: "https://api-hr.medlatec.vn/" + user.value.avatar,
|
||||
/** Tên đăng nhập */
|
||||
username: user.value.preferred_username.toString(),
|
||||
/** Biệt danh */
|
||||
nickname: user.value.full_name.toString()
|
||||
};
|
||||
setToken(avv);
|
||||
message(t("login.pureLoginSuccess"), { type: "success" });
|
||||
window.location.href = "/welcome";
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
autoAuthenticate();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@ -140,51 +147,14 @@ onBeforeUnmount(() => {
|
||||
<h2 class="outline-none">{{ title }}</h2>
|
||||
</Motion>
|
||||
|
||||
<el-form
|
||||
ref="ruleFormRef"
|
||||
:model="ruleForm"
|
||||
:rules="loginRules"
|
||||
size="large"
|
||||
>
|
||||
<Motion :delay="100">
|
||||
<el-form-item
|
||||
:rules="[
|
||||
{
|
||||
required: true,
|
||||
message: transformI18n($t('login.pureUsernameReg')),
|
||||
trigger: 'blur'
|
||||
}
|
||||
]"
|
||||
prop="username"
|
||||
>
|
||||
<el-input
|
||||
v-model="ruleForm.username"
|
||||
clearable
|
||||
:placeholder="t('login.pureUsername')"
|
||||
:prefix-icon="useRenderIcon(User)"
|
||||
/>
|
||||
</el-form-item>
|
||||
</Motion>
|
||||
|
||||
<Motion :delay="150">
|
||||
<el-form-item prop="password">
|
||||
<el-input
|
||||
v-model="ruleForm.password"
|
||||
clearable
|
||||
show-password
|
||||
:placeholder="t('login.purePassword')"
|
||||
:prefix-icon="useRenderIcon(Lock)"
|
||||
/>
|
||||
</el-form-item>
|
||||
</Motion>
|
||||
|
||||
<el-form size="large">
|
||||
<Motion :delay="250">
|
||||
<el-button
|
||||
class="w-full mt-4"
|
||||
size="default"
|
||||
type="primary"
|
||||
:loading="loading"
|
||||
@click="onLogin(ruleFormRef)"
|
||||
@click="signin()"
|
||||
>
|
||||
{{ t("login.pureLogin") }}
|
||||
</el-button>
|
||||
|
Loading…
x
Reference in New Issue
Block a user