perf: 系统管理

This commit is contained in:
pan 2024-03-18 16:25:02 +08:00
parent 9bac6333fb
commit 0838e69d23
8 changed files with 128 additions and 18 deletions

View File

@ -1,5 +1,6 @@
buttons: buttons:
hsLoginOut: LoginOut hsLoginOut: LoginOut
userInfo: user info
hsfullscreen: FullScreen hsfullscreen: FullScreen
hsexitfullscreen: ExitFullscreen hsexitfullscreen: ExitFullscreen
hsrefreshRoute: RefreshRoute hsrefreshRoute: RefreshRoute

View File

@ -1,5 +1,6 @@
buttons: buttons:
hsLoginOut: 退出系统 hsLoginOut: 退出系统
userInfo: 个人信息
hsfullscreen: 全屏 hsfullscreen: 全屏
hsexitfullscreen: 退出全屏 hsexitfullscreen: 退出全屏
hsrefreshRoute: 刷新路由 hsrefreshRoute: 刷新路由

View File

@ -7,9 +7,11 @@ import Breadcrumb from "./sidebar/breadCrumb.vue";
import topCollapse from "./sidebar/topCollapse.vue"; import topCollapse from "./sidebar/topCollapse.vue";
import { useTranslationLang } from "../hooks/useTranslationLang"; import { useTranslationLang } from "../hooks/useTranslationLang";
import globalization from "@/assets/svg/globalization.svg?component"; import globalization from "@/assets/svg/globalization.svg?component";
import { useDetail } from "@/router/utils";
import LogoutCircleRLine from "@iconify-icons/ri/logout-circle-r-line"; import LogoutCircleRLine from "@iconify-icons/ri/logout-circle-r-line";
import Setting from "@iconify-icons/ri/settings-3-line"; import Setting from "@iconify-icons/ri/settings-3-line";
import Check from "@iconify-icons/ep/check"; import Check from "@iconify-icons/ep/check";
import Avatar from "@iconify-icons/ep/avatar";
const { const {
layout, layout,
@ -24,6 +26,7 @@ const {
getDropdownItemStyle, getDropdownItemStyle,
getDropdownItemClass getDropdownItemClass
} = useNav(); } = useNav();
const { router } = useDetail();
const { t, locale, translationCh, translationEn } = useTranslationLang(); const { t, locale, translationCh, translationEn } = useTranslationLang();
</script> </script>
@ -89,6 +92,16 @@ const { t, locale, translationCh, translationEn } = useTranslationLang();
</span> </span>
<template #dropdown> <template #dropdown>
<el-dropdown-menu class="logout"> <el-dropdown-menu class="logout">
<el-dropdown-item
@click="
router.push({
name: 'UserInfo'
})
"
>
<IconifyIconOffline :icon="Avatar" style="margin: 5px" />
个人中心
</el-dropdown-item>
<el-dropdown-item @click="logout"> <el-dropdown-item @click="logout">
<IconifyIconOffline <IconifyIconOffline
:icon="LogoutCircleRLine" :icon="LogoutCircleRLine"

View File

@ -0,0 +1,26 @@
import { $t } from "@/plugins/i18n";
import { home } from "@/router/enums";
const Layout = () => import("@/layout/index.vue");
export default {
path: "/system/user/info",
name: "UserInfo",
component: Layout,
redirect: "/system/user/info",
meta: {
icon: "homeFilled",
title: $t("buttons.userInfo"),
rank: home
},
children: [
{
path: "/system/user/info",
name: "UserInfo",
component: () => import("@/views/system/user/info.vue"),
meta: {
title: $t("buttons.userInfo"),
showLink: false
}
}
]
} satisfies RouteConfigsTable;

View File

@ -3,12 +3,17 @@ import {
type RouteRecordRaw, type RouteRecordRaw,
type RouteComponent, type RouteComponent,
createWebHistory, createWebHistory,
createWebHashHistory createWebHashHistory,
useRouter,
useRoute,
type LocationQueryRaw,
type RouteParamsRaw
} from "vue-router"; } from "vue-router";
import { router } from "./index"; import { router } from "./index";
import { isProxy, toRaw } from "vue"; import { isProxy, toRaw } from "vue";
import { useTimeoutFn } from "@vueuse/core"; import { useTimeoutFn } from "@vueuse/core";
import { import {
isEmpty,
isString, isString,
cloneDeep, cloneDeep,
isAllEmpty, isAllEmpty,
@ -366,6 +371,67 @@ function getTopMenu(tag = false): menuType {
return topMenu; return topMenu;
} }
function useDetail() {
const route = useRoute();
const router = useRouter();
const getParameter = isEmpty(route.params) ? route.query : route.params;
function toDetail(
parameter: LocationQueryRaw | RouteParamsRaw,
model: "query" | "params"
) {
// ⚠️ 这里要特别注意下因为vue-router在解析路由参数的时候会自动转化成字符串类型比如在使用useRoute().route.query或useRoute().route.params时得到的参数都是字符串类型
// 所以在传参的时候,如果参数是数字类型,就需要在此处 toString() 一下,保证传参跟路由参数类型一致都是字符串,这是必不可少的环节!!!
Object.keys(parameter).forEach(param => {
if (!isString(parameter[param])) {
parameter[param] = parameter[param].toString();
}
});
if (model === "query") {
// 保存信息到标签页
useMultiTagsStoreHook().handleTags("push", {
path: `/tabs/query-detail`,
name: "TabQueryDetail",
query: parameter,
meta: {
title: {
zh: `No.${parameter.id} - 详情信息`,
en: `No.${parameter.id} - DetailInfo`
},
// 如果使用的是非国际化精简版title可以像下面这么写
// title: `No.${index} - 详情信息`,
// 最大打开标签数
dynamicLevel: 3
}
});
// 路由跳转
router.push({ name: "TabQueryDetail", query: parameter });
} else if (model === "params") {
useMultiTagsStoreHook().handleTags("push", {
path: `/tabs/params-detail/:id`,
name: "TabParamsDetail",
params: parameter,
meta: {
title: {
zh: `No.${parameter.id} - 详情信息`,
en: `No.${parameter.id} - DetailInfo`
}
// 如果使用的是非国际化精简版title可以像下面这么写
// title: `No.${index} - 详情信息`,
}
});
router.push({ name: "TabParamsDetail", params: parameter });
}
}
// 用于页面刷新,重新获取浏览器地址栏参数并保存到标签页
const initToDetail = (model: "query" | "params") => {
if (getParameter) toDetail(getParameter, model);
};
return { toDetail, initToDetail, getParameter, router };
}
export { export {
hasAuth, hasAuth,
getAuths, getAuths,
@ -382,5 +448,6 @@ export {
handleAliveRoute, handleAliveRoute,
formatTwoStageRoutes, formatTwoStageRoutes,
formatFlatteningRoutes, formatFlatteningRoutes,
filterNoPermissionTree filterNoPermissionTree,
useDetail
}; };

View File

@ -65,7 +65,8 @@ export function setToken(data: DataInfo<Date>) {
function setUserKey( function setUserKey(
accessToken: string, accessToken: string,
username: string, username: string,
roles: Array<string> roles: Array<string>,
user?: any
) { ) {
useUserStoreHook().SET_USERNAME(username); useUserStoreHook().SET_USERNAME(username);
useUserStoreHook().SET_ROLES(roles); useUserStoreHook().SET_ROLES(roles);
@ -73,13 +74,14 @@ export function setToken(data: DataInfo<Date>) {
accessToken, accessToken,
expires, expires,
username, username,
roles roles,
user
}); });
} }
if (data.username && data.roles) { if (data.username && data.roles) {
const { username, roles } = data; const { username, roles, user } = data;
setUserKey(accessToken, username, roles); setUserKey(accessToken, username, roles, user);
} else { } else {
const username = const username =
storageLocal().getItem<DataInfo<number>>(userKey)?.username ?? ""; storageLocal().getItem<DataInfo<number>>(userKey)?.username ?? "";

View File

@ -30,7 +30,7 @@ const {
submitEditUser submitEditUser
} = useUser(tableRef, treeRef); } = useUser(tableRef, treeRef);
const user = reactive(userInfo.value.user); const user = reactive(userInfo.value);
defineOptions({ defineOptions({
name: "UserInfo" name: "UserInfo"
}); });
@ -51,9 +51,9 @@ defineOptions({
<el-avatar <el-avatar
:size="80" :size="80"
src="https://empty" src="https://empty"
@click="handleUpload(userInfo.user)" @click="handleUpload(userInfo)"
> >
<img :src="'/avatar/' + userInfo.user.avatarName" /> <img :src="'/avatar/' + userInfo.avatarName" />
</el-avatar> </el-avatar>
</div> </div>
@ -62,28 +62,28 @@ defineOptions({
<div style="height: 100%"> <div style="height: 100%">
<IconifyIconOffline class="check-zh" :icon="SignIn" /> <IconifyIconOffline class="check-zh" :icon="SignIn" />
登录账号 登录账号
<div class="user-right">{{ userInfo.user.username }}</div> <div class="user-right">{{ userInfo.username }}</div>
</div> </div>
</li> </li>
<li> <li>
<IconifyIconOffline class="check-zh" :icon="Check" /> <IconifyIconOffline class="check-zh" :icon="Check" />
用户昵称 用户昵称
<div class="user-right">{{ userInfo.user.nickName }}</div> <div class="user-right">{{ userInfo.nickName }}</div>
</li> </li>
<li> <li>
<IconifyIconOffline class="check-zh" :icon="NodeTree" /> <IconifyIconOffline class="check-zh" :icon="NodeTree" />
所属部门 所属部门
<div class="user-right">{{ userInfo.user.dept.name }}</div> <div class="user-right">{{ userInfo.dept.name }}</div>
</li> </li>
<li> <li>
<IconifyIconOffline class="check-zh" :icon="Phone" /> <IconifyIconOffline class="check-zh" :icon="Phone" />
手机号码 手机号码
<div class="user-right">{{ userInfo.user.phone }}</div> <div class="user-right">{{ userInfo.phone }}</div>
</li> </li>
<li> <li>
<IconifyIconOffline class="check-zh" :icon="Mail" /> <IconifyIconOffline class="check-zh" :icon="Mail" />
用户邮箱 用户邮箱
<div class="user-right">{{ userInfo.user.email }}</div> <div class="user-right">{{ userInfo.email }}</div>
</li> </li>
<li> <li>
<IconifyIconOffline class="check-zh" :icon="Secure" /> <IconifyIconOffline class="check-zh" :icon="Secure" />
@ -135,7 +135,7 @@ defineOptions({
clearable clearable
style="width: 35%" style="width: 35%"
/> />
<span style=" margin-left: 10px;color: #e6a23c" <span style="margin-left: 10px; color: #e6a23c"
>用户昵称不作为登录使用</span >用户昵称不作为登录使用</span
> >
</el-form-item> </el-form-item>
@ -164,7 +164,7 @@ defineOptions({
]" ]"
> >
<el-input v-model="user.phone" clearable style="width: 35%" /> <el-input v-model="user.phone" clearable style="width: 35%" />
<span style=" margin-left: 10px;color: #e6a23c" <span style="margin-left: 10px; color: #e6a23c"
>手机号码不能重复</span >手机号码不能重复</span
> >
</el-form-item> </el-form-item>

View File

@ -4,7 +4,7 @@ import { baseUrlAvatar } from "@/api/utils";
import { zxcvbn } from "@zxcvbn-ts/core"; import { zxcvbn } from "@zxcvbn-ts/core";
import { isAllEmpty, isNull, isEmail } from "@pureadmin/utils"; import { isAllEmpty, isNull, isEmail } from "@pureadmin/utils";
import { addDialog } from "@/components/ReDialog"; import { addDialog } from "@/components/ReDialog";
import Cookies from "js-cookie"; import { storageLocal } from "@pureadmin/utils";
import * as User from "@/api/system/user"; import * as User from "@/api/system/user";
import { import {
ElForm, ElForm,
@ -55,7 +55,7 @@ export function useUser(tableRef: Ref, treeRef: Ref) {
}); });
/** 用户信息 */ /** 用户信息 */
const userInfo = computed(() => { const userInfo = computed(() => {
return JSON.parse(Cookies.get("userInfo")); return storageLocal().getItem("user-info").user;
}); });
/** 获取邮箱验证码 */ /** 获取邮箱验证码 */
const getEmailCode = email => { const getEmailCode = email => {