This commit is contained in:
Truong Nguyen 2024-07-11 15:32:25 +07:00
parent f0c4f1f39e
commit a49f46a25e
17 changed files with 327 additions and 125 deletions

View File

@ -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 `build`
* @param metaUrl URL đy đ của -đ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[]) => {

View File

@ -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
View File

@ -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):

View File

@ -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
View 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
View 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
View 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
View 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
View 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ử đăng nhập thủ công 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
View 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>

View File

@ -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
};

View File

@ -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
View 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();

View File

@ -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 nhp tt c các tuyến tĩnh, không cn nhp th công na! Khp vi tt c các tp có phn m rng .ts trong thư mc src/router/modules (bt k cp đ lng nhau nào), ngoi tr tp 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);
});
}

View File

@ -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` */

View File

@ -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 ?? "",

View File

@ -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>