page: 📄 用户修改账户信息页面

This commit is contained in:
bunny 2024-10-22 13:34:01 +08:00
parent 6ef4e73842
commit a9a2a140fb
33 changed files with 788 additions and 380 deletions

View File

@ -29,17 +29,12 @@ export interface RefreshTokenResult {
expires: Date;
}
/**
*
*/
/** 登录 */
export const fetchLogin = (data?: object) => {
return http.request<BaseResult<UserResult>>('post', '/login', { data });
};
/**
* *
* @param data
*/
/** 发送邮件 */
export const fetchPostEmailCode = (data: any) => {
return http.request<BaseResult<any>>('post', '/user/noAuth/sendLoginEmail', { data }, { headers: { 'Content-Type': 'multipart/form-data' } });
};
@ -49,90 +44,67 @@ export const refreshTokenApi = (data?: object) => {
return http.request<BaseResult<RefreshTokenResult>>('post', 'user/noAuth/refreshToken', { data });
};
/**
* * 退
* @param data
*/
/** 退出账户 */
export const fetchLogout = (data?: object) => {
return http.request<BaseResult<any>>('post', 'user/logout', { data });
return http.request<BaseResult<any>>('post', 'user/noManage/logout', { data });
};
/**
* ---
*/
/** 获取用户信息,根据当前token获取 */
export const fetchGetUserinfo = () => {
return http.request<BaseResult<any>>('get', 'user/noManage/getUserinfo');
};
/** 用户信息---获取用户信息列表 */
export const fetchGetAdminUserList = (data: any) => {
return http.request<BaseResult<ResultTable>>('get', `user/getAdminUserList/${data.currentPage}/${data.pageSize}`, { params: data });
};
/**
*
* @param data
*/
/** 用户信息---查询用户 */
export const fetchQueryUser = (data: any) => {
return http.request<BaseResult<object>>('get', 'user/queryUser', { params: data });
return http.request<BaseResult<object>>('get', 'user/noManage/queryUser', { params: data });
};
/**
* ---
*/
/** 用户信息---添加用户信息 */
export const fetchAddAdminUser = (data: any) => {
return http.request<BaseResult<object>>('post', 'user/addAdminUser', { data });
};
/**
* ---
*/
/** 用户信息---更新用户信息 */
export const fetchUpdateAdminUser = (data: any) => {
return http.request<BaseResult<object>>('put', 'user/updateAdminUser', { data });
};
/**
* ---
*/
/** 用户信息---删除用户信息 */
export const fetchDeleteAdminUser = (data: any) => {
return http.request<BaseResult<object>>('delete', 'user/deleteAdminUser', { data });
};
/**
*
*/
/** 用户管理---获取用户信息 */
export const fetchGetUserinfoById = (data?: object) => {
return http.request<BaseResult<UserResult>>('get', 'user/getUserinfoById', { params: data });
};
/** 修改用户状态 */
/** 用户管理---修改用户状态 */
export const fetchUpdateUserStatusByAdmin = (data?: object) => {
return http.request<BaseResult<UserResult>>('put', 'user/updateUserStatusByAdmin', { data });
};
/**
*
* @param data
*/
/** 用户管理---管理员修改管理员用户密码 */
export const fetchUpdateUserPasswordByAdmin = (data: any) => {
return http.request<BaseResult<UserResult>>('put', 'user/updateUserPasswordByAdmin', { data });
};
/**
*
* @param data
*/
/** 用户管理---管理员修改管理员用户头像 */
export const fetchUploadAvatarByAdmin = (data: any) => {
return http.request<BaseResult<UserResult>>('put', 'user/uploadAvatarByAdmin', { data }, { headers: { 'Content-Type': 'multipart/form-data' } });
};
/**
* 线
* @param data
*/
/** 用户管理---强制用户下线 */
export const fetchForcedOffline = (data: any) => {
return http.request<BaseResult<UserResult>>('put', 'user/forcedOffline', { data });
};
/**
*
* @param data
*/
/** 用户管理---为用户分配角色 */
export const fetchAssignRolesToUsers = (data: object) => {
return http.request<BaseResult<any>>('post', 'userRole/assignRolesToUsers', { data });
};

View File

@ -1,37 +1,27 @@
import { http } from '@/api/service/request';
import type { BaseResult, ResultTable } from '@/api/service/types';
/**
* ---
*/
/** 部门管理---获取部门管理列表 */
export const fetchGetDeptList = (data: any) => {
return http.request<BaseResult<ResultTable>>('get', `dept/getDeptList/${data.currentPage}/${data.pageSize}`, { params: data });
};
/**
* ---
*/
/** 部门管理---获取所有部门管理列表 */
export const fetchGetAllDeptList = () => {
return http.request<BaseResult<object>>('get', 'dept/getAllDeptList');
};
/**
* ---
*/
/** 部门管理---添加部门管理 */
export const fetchAddDept = (data: any) => {
return http.request<BaseResult<object>>('post', 'dept/addDept', { data });
};
/**
* ---
*/
/** 部门管理---更新部门管理 */
export const fetchUpdateDept = (data: any) => {
return http.request<BaseResult<object>>('put', 'dept/updateDept', { data });
};
/**
* ---
*/
/** 部门管理---删除部门管理 */
export const fetchDeleteDept = (data: any) => {
return http.request<BaseResult<object>>('delete', 'dept/deleteDept', { data });
};

View File

@ -8,7 +8,7 @@ export const fetchGetEmailUsersList = (data: any) => {
/** 邮箱用户发送配置管理---获取所有邮箱配置用户 */
export const fetchGetAllMailboxConfigurationUsers = () => {
return http.request<BaseResult<any>>('get', 'emailUsers/getAllMailboxConfigurationUsers');
return http.request<BaseResult<any>>('get', 'emailUsers/noManage/getAllMailboxConfigurationUsers');
};
/** 邮箱用户发送配置管理---添加邮箱用户发送配置管理 */

View File

@ -18,10 +18,10 @@ export const downloadFilesByFilepath = (data: any) => {
/** 系统文件管理---获取所有文件类型 */
export const fetchGetAllMediaTypes = () => {
return http.request<BaseResult<any>>('get', `files/getAllMediaTypes`);
return http.request<BaseResult<any>>('get', `files/noManage/getAllMediaTypes`);
};
/** 系统文件管理---获取所有文件类型 */
/** 系统文件管理---获取所有文件存储基础路径 */
export const fetchGetAllFilesStoragePath = () => {
return http.request<BaseResult<any>>('get', `files/getAllFilesStoragePath`);
};

View File

@ -1,65 +1,47 @@
import { http } from '@/api/service/request';
import type { BaseResult, ResultTable } from '@/api/service/types';
/**
* *
*/
/** 多语言类型管理---获取多语言内容 */
export const fetchGetI18n = () => {
return http.request<BaseResult<object>>('get', 'i18n/getI18n');
};
/**
* ---
*/
/** 多语言类型管理---获取多语言列表 */
export const fetchGetI18nList = (data: any) => {
return http.request<BaseResult<ResultTable>>('get', `i18n/getI18nList/${data.currentPage}/${data.pageSize}`, { params: data });
};
/**
* ---
*/
/** 多语言类型管理---添加多语言 */
export const fetchAddI18n = (data: any) => {
return http.request<BaseResult<object>>('post', 'i18n/addI18n', { data });
};
/**
* ---
*/
/** 多语言类型管理---更新多语言 */
export const fetchUpdateI18n = (data: any) => {
return http.request<BaseResult<object>>('put', 'i18n/updateI18n', { data });
};
/**
* ---
*/
/** 多语言类型管理---删除多语言 */
export const fetchDeleteI18n = (data: any) => {
return http.request<BaseResult<object>>('delete', 'i18n/deleteI18n', { data });
};
/**
* ---
*/
/** 多语言类型管理---获取多语言类型列表 */
export const fetchGetI18nTypeList = () => {
return http.request<BaseResult<ResultTable>>('get', 'i18nType/noAuth/getI18nTypeList');
};
/**
* ---
*/
/** 多语言类型管理---添加多语言类型 */
export const fetchAddI18nType = (data: any) => {
return http.request<BaseResult<object>>('post', 'i18nType/addI18nType', { data });
};
/**
* ---
*/
/** 多语言类型管理---更新多语言类型 */
export const fetchUpdateI18nType = (data: any) => {
return http.request<BaseResult<object>>('put', 'i18nType/updateI18nType', { data });
};
/**
* ---
*/
/** 多语言类型管理---删除多语言类型 */
export const fetchDeleteI18nType = (data: any) => {
return http.request<BaseResult<object>>('delete', 'i18nType/deleteI18nType', { data });
};

View File

@ -1,38 +1,27 @@
import { http } from '@/api/service/request';
import type { BaseResult, ResultTable } from '@/api/service/types';
/**
* ---
*/
/** 系统菜单图标---获取多语言列表 */
export const fetchGetMenuIconList = (data: any) => {
return http.request<BaseResult<ResultTable>>('get', `menuIcon/getMenuIconList/${data.currentPage}/${data.pageSize}`, { params: data });
};
/**
* ---
*/
/** 系统菜单图标---添加多语言 */
export const fetchAddMenuIcon = (data: any) => {
return http.request<BaseResult<object>>('post', 'menuIcon/addMenuIcon', { data });
};
/**
* ---
*/
/** 系统菜单图标---更新多语言 */
export const fetchUpdateMenuIcon = (data: any) => {
return http.request<BaseResult<object>>('put', 'menuIcon/updateMenuIcon', { data });
};
/**
* ---
*/
/** 系统菜单图标---删除多语言 */
export const fetchDeleteMenuIcon = (data: any) => {
return http.request<BaseResult<object>>('delete', 'menuIcon/deleteMenuIcon', { data });
};
/**
* ---iconName搜索menuIc
* @param data
*/
/** 系统菜单图标---根据iconName搜索menuIc */
export const fetchGetIconNameList = (data: any) => {
return http.request<BaseResult<object>>('get', 'menuIcon/getIconNameList', { params: data });
return http.request<BaseResult<object>>('get', 'menuIcon/noManage/getIconNameList', { params: data });
};

View File

@ -8,10 +8,10 @@ export const fetchGetPowerList = (data: any) => {
/** 权限---根据角色id获取权限内容 */
export const fetchGetPowerListByRoleId = (data: any) => {
return http.request<BaseResult<object>>('get', 'rolePower/getPowerListByRoleId', { params: data });
return http.request<BaseResult<object>>('get', 'rolePower/noManage/getPowerListByRoleId', { params: data });
};
/** 获取所有权限 */
/** 权限---获取所有权限 */
export const fetchGetAllPowers = () => {
return http.request<BaseResult<any>>('get', `power/getAllPowers`);
};

View File

@ -1,51 +1,37 @@
import { http } from '@/api/service/request';
import type { BaseResult, ResultTable } from '@/api/service/types';
/**
* ---
*/
/** 角色---获取角色列表 */
export const fetchGetRoleList = (data: any) => {
return http.request<BaseResult<ResultTable>>('get', `role/getRoleList/${data.currentPage}/${data.pageSize}`, { params: data });
};
/**
*
*/
/** 角色---获取所有角色 */
export const fetchGetAllRoles = () => {
return http.request<BaseResult<any>>('get', `role/getAllRoles`);
return http.request<BaseResult<any>>('get', `role/noManage/getAllRoles`);
};
/**
* id获取所有角色
*/
/** 角色---根据用户id获取所有角色 */
export const fetchGetRoleListByUserId = data => {
return http.request<BaseResult<any>>('get', `userRole/getRoleListByUserId`, { params: data });
};
/**
* ---
*/
/** 角色---添加角色 */
export const fetchAddRole = (data: any) => {
return http.request<BaseResult<object>>('post', 'role/addRole', { data });
};
/**
* ---
*/
/** 角色---为角色分配权限 */
export const fetchAssignPowersToRole = (data: any) => {
return http.request<BaseResult<object>>('post', 'rolePower/assignPowersToRole', { data });
};
/**
* ---
*/
/** 角色---更新角色 */
export const fetchUpdateRole = (data: any) => {
return http.request<BaseResult<object>>('put', 'role/updateRole', { data });
};
/**
* ---
*/
/** 角色---删除角色 */
export const fetchDeleteRole = (data: any) => {
return http.request<BaseResult<object>>('delete', 'role/deleteRole', { data });
};

View File

@ -8,7 +8,7 @@ export const fetchGetSchedulersList = (data: any) => {
/** Schedulers视图---获取所有可用调度任务 */
export const fetchGetAllScheduleJobList = () => {
return http.request<BaseResult<ResultTable>>('get', 'schedulers/getAllScheduleJobList');
return http.request<BaseResult<ResultTable>>('get', 'schedulers/noManage/getAllScheduleJobList');
};
/** Schedulers视图---添加Schedulers视图 */

View File

@ -3,7 +3,7 @@ import type { BaseResult } from '@/api/service/types';
/** 系统管理-用户路由获取 */
export const getRouterAsync = () => {
return http.request<BaseResult<any>>('get', 'router/getRouterAsync');
return http.request<BaseResult<any>>('get', 'router/noManage/getRouterAsync');
};
/** 上传文件 */

View File

@ -6,6 +6,11 @@ export const fetchGetUserLoginLogList = (data: any) => {
return http.request<BaseResult<ResultTable>>('get', `userLoginLog/getUserLoginLogList/${data.currentPage}/${data.pageSize}`, { params: data });
};
/** 用户登录日志---获取用户登录日志列表 */
export const fetchGetUserLoginLogListByLocalUser = (data: any) => {
return http.request<BaseResult<ResultTable>>('get', `userLoginLog/noManage/getUserLoginLogListByLocalUser/${data.currentPage}/${data.pageSize}`);
};
/** 用户登录日志---删除用户登录日志 */
export const fetchDeleteUserLoginLog = (data: any) => {
return http.request<BaseResult<object>>('delete', 'userLoginLog/deleteUserLoginLog', { data });

View File

@ -1,7 +1,7 @@
import reText from "./src/index.vue";
import { withInstall } from "@pureadmin/utils";
import reText from './src/index.vue';
import { withInstall } from '@pureadmin/utils';
/** 支持`Tooltip`提示的文本省略组件 */
export const ReText = withInstall(reText);
export const Text = withInstall(reText);
export default ReText;
export default Text;

View File

@ -0,0 +1,53 @@
<script lang="ts" setup>
import { useNav } from '@/layout/hooks/useNav';
import LogoutCircleRLine from '@iconify-icons/ri/logout-circle-r-line';
import { $t } from '@/plugins/i18n';
import { useRouter } from 'vue-router';
const { logout, username, userAvatar, avatarsStyle } = useNav();
const router = useRouter();
</script>
<template>
<el-dropdown trigger="click">
<span class="el-dropdown-link navbar-bg-hover select-none">
<img :src="userAvatar" :style="avatarsStyle" alt="" />
<p v-if="username" class="dark:text-white">{{ username }}</p>
</span>
<template #dropdown>
<el-dropdown-item @click="router.push({ name: 'AccountSettings' })">
<IconifyIconOffline :icon="LogoutCircleRLine" style="margin: 5px" />
账户设置
<!--{{ $t('buttons.pureLoginOut') }}-->
</el-dropdown-item>
<el-dropdown-menu class="logout">
<el-dropdown-item @click="logout">
<IconifyIconOffline :icon="LogoutCircleRLine" style="margin: 5px" />
{{ $t('buttons.pureLoginOut') }}
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</template>
<style lang="scss" scoped>
.el-dropdown-link {
display: flex;
align-items: center;
justify-content: space-around;
height: 48px;
padding: 10px;
color: #000000d9;
cursor: pointer;
p {
font-size: 14px;
}
img {
width: 22px;
height: 22px;
border-radius: 50%;
}
}
</style>

View File

@ -9,12 +9,12 @@ import LaySidebarBreadCrumb from '../lay-sidebar/components/SidebarBreadCrumb.vu
import LaySidebarTopCollapse from '../lay-sidebar/components/SidebarTopCollapse.vue';
import GlobalizationIcon from '@/assets/svg/globalization.svg?component';
import LogoutCircleRLine from '@iconify-icons/ri/logout-circle-r-line';
import Setting from '@iconify-icons/ri/settings-3-line';
import Check from '@iconify-icons/ep/check';
import { $t } from '@/plugins/i18n';
import { userI18nTypeStore } from '@/store/i18n/i18nType';
import { onMounted } from 'vue';
import DropdownMenu from '@/layout/components/lay-navbar/dropdown-menu.vue';
const { layout, device, logout, onPanel, pureApp, username, userAvatar, avatarsStyle, toggleSideBar, getDropdownItemStyle, getDropdownItemClass } = useNav();
@ -63,20 +63,8 @@ onMounted(() => {
<!-- 消息通知 -->
<LayNotice id="header-notice" />
<!-- 退出登录 -->
<el-dropdown trigger="click">
<span class="el-dropdown-link navbar-bg-hover select-none">
<img :src="userAvatar" :style="avatarsStyle" alt="" />
<p v-if="username" class="dark:text-white">{{ username }}</p>
</span>
<template #dropdown>
<el-dropdown-menu class="logout">
<el-dropdown-item @click="logout">
<IconifyIconOffline :icon="LogoutCircleRLine" style="margin: 5px" />
{{ $t('buttons.pureLoginOut') }}
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
<dropdown-menu />
<!-- 打开设置 -->
<span :title="$t('buttons.pureOpenSystemSet')" class="set-icon navbar-bg-hover" @click="onPanel">
<IconifyIconOffline :icon="Setting" />
</span>
@ -104,26 +92,6 @@ onMounted(() => {
min-width: 280px;
height: 48px;
color: #000000d9;
.el-dropdown-link {
display: flex;
align-items: center;
justify-content: space-around;
height: 48px;
padding: 10px;
color: #000000d9;
cursor: pointer;
p {
font-size: 14px;
}
img {
width: 22px;
height: 22px;
border-radius: 50%;
}
}
}
.breadcrumb-container {

View File

@ -64,7 +64,7 @@ const getThemeColorStyle = computed(() => {
/** 当网页整体为暗色风格时不显示亮白色主题配色切换选项 */
const showThemeColors = computed(() => {
return themeColor => {
return themeColor === 'light' && isDark.value ? false : true;
return !(themeColor === 'light' && isDark.value);
};
});

View File

@ -1,73 +1,61 @@
<script lang="ts" setup>
import { useRenderIcon } from "@/components/CommonIcon/src/hooks";
import { ReText } from "@/components/Text";
import { getConfig } from "@/config";
import { useNav } from "@/layout/hooks/useNav";
import { menuType } from "@/layout/types";
import path from "path";
import {
computed,
type CSSProperties,
type PropType,
ref,
toRaw,
useAttrs
} from "vue";
import SidebarExtraIcon from "./SidebarExtraIcon.vue";
import SidebarLinkItem from "./SidebarLinkItem.vue";
import { useRenderIcon } from '@/components/CommonIcon/src/hooks';
import { Text } from '@/components/Text';
import { getConfig } from '@/config';
import { useNav } from '@/layout/hooks/useNav';
import { menuType } from '@/layout/types';
import path from 'path';
import { computed, type CSSProperties, type PropType, ref, toRaw, useAttrs } from 'vue';
import SidebarExtraIcon from './SidebarExtraIcon.vue';
import SidebarLinkItem from './SidebarLinkItem.vue';
import { $t } from "@/plugins/i18n";
import EpArrowDown from "@iconify-icons/ep/arrow-down-bold";
import ArrowLeft from "@iconify-icons/ep/arrow-left-bold";
import ArrowRight from "@iconify-icons/ep/arrow-right-bold";
import ArrowUp from "@iconify-icons/ep/arrow-up-bold";
import { $t } from '@/plugins/i18n';
import EpArrowDown from '@iconify-icons/ep/arrow-down-bold';
import ArrowLeft from '@iconify-icons/ep/arrow-left-bold';
import ArrowRight from '@iconify-icons/ep/arrow-right-bold';
import ArrowUp from '@iconify-icons/ep/arrow-up-bold';
const attrs = useAttrs();
const { layout, isCollapse, tooltipEffect, getDivStyle } = useNav();
const props = defineProps({
item: {
type: Object as PropType<menuType>
type: Object as PropType<menuType>,
},
isNest: {
type: Boolean,
default: false
default: false,
},
basePath: {
type: String,
default: ""
}
default: '',
},
});
const getNoDropdownStyle = computed((): CSSProperties => {
return {
width: "100%",
display: "flex",
alignItems: "center"
width: '100%',
display: 'flex',
alignItems: 'center',
};
});
const getSubMenuIconStyle = computed((): CSSProperties => {
return {
display: "flex",
justifyContent: "center",
alignItems: "center",
margin:
layout.value === "horizontal"
? "0 5px 0 0"
: isCollapse.value
? "0 auto"
: "0 5px 0 0"
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
margin: layout.value === 'horizontal' ? '0 5px 0 0' : isCollapse.value ? '0 auto' : '0 5px 0 0',
};
});
const expandCloseIcon = computed(() => {
if (!getConfig()?.MenuArrowIconNoTransition) return "";
if (!getConfig()?.MenuArrowIconNoTransition) return '';
return {
"expand-close-icon": useRenderIcon(EpArrowDown),
"expand-open-icon": useRenderIcon(ArrowUp),
"collapse-close-icon": useRenderIcon(ArrowRight),
"collapse-open-icon": useRenderIcon(ArrowLeft)
'expand-close-icon': useRenderIcon(EpArrowDown),
'expand-open-icon': useRenderIcon(ArrowUp),
'collapse-close-icon': useRenderIcon(ArrowRight),
'collapse-open-icon': useRenderIcon(ArrowLeft),
};
});
@ -88,7 +76,7 @@ function hasOneShowingChild(children: menuType[] = [], parent: menuType) {
}
if (showingChildren.length === 0) {
onlyOneChild.value = { ...parent, path: "", noShowingChildren: true };
onlyOneChild.value = { ...parent, path: '', noShowingChildren: true };
return true;
}
return false;
@ -106,44 +94,13 @@ function resolvePath(routePath) {
</script>
<template>
<SidebarLinkItem
v-if="
hasOneShowingChild(item.children, item) &&
(!onlyOneChild.children || onlyOneChild.noShowingChildren)
"
:to="item"
>
<el-menu-item
:class="{ 'submenu-title-noDropdown': !isNest }"
:index="resolvePath(onlyOneChild.path)"
:style="getNoDropdownStyle"
v-bind="attrs"
>
<div
v-if="toRaw(item.meta.icon)"
:style="getSubMenuIconStyle"
class="sub-menu-icon"
>
<component
:is="
useRenderIcon(
toRaw(onlyOneChild.meta.icon) ||
(item.meta && toRaw(item.meta.icon))
)
"
/>
<SidebarLinkItem v-if="hasOneShowingChild(item.children, item) && (!onlyOneChild.children || onlyOneChild.noShowingChildren)" :to="item">
<el-menu-item :class="{ 'submenu-title-noDropdown': !isNest }" :index="resolvePath(onlyOneChild.path)" :style="getNoDropdownStyle" v-bind="attrs">
<div v-if="toRaw(item.meta.icon)" :style="getSubMenuIconStyle" class="sub-menu-icon">
<component :is="useRenderIcon(toRaw(onlyOneChild.meta.icon) || (item.meta && toRaw(item.meta.icon)))" />
</div>
<el-text
v-if="
(!item?.meta.icon &&
isCollapse &&
layout === 'vertical' &&
item?.pathList?.length === 1) ||
(!onlyOneChild.meta.icon &&
isCollapse &&
layout === 'mix' &&
item?.pathList?.length === 2)
"
v-if="(!item?.meta.icon && isCollapse && layout === 'vertical' && item?.pathList?.length === 1) || (!onlyOneChild.meta.icon && isCollapse && layout === 'mix' && item?.pathList?.length === 2)"
class="!w-full !pl-4 !text-inherit"
truncated
>
@ -152,72 +109,42 @@ function resolvePath(routePath) {
<template #title>
<div :style="getDivStyle">
<ReText
<Text
:tippyProps="{
offset: [0, -10],
theme: tooltipEffect
theme: tooltipEffect,
}"
class="!w-full !text-inherit"
>
{{ $t(onlyOneChild.meta.title) }}
</ReText>
</Text>
<SidebarExtraIcon :extraIcon="onlyOneChild.meta.extraIcon" />
</div>
</template>
</el-menu-item>
</SidebarLinkItem>
<el-sub-menu
v-else
ref="subMenu"
:index="resolvePath(item.path)"
teleported
v-bind="expandCloseIcon"
>
<el-sub-menu v-else ref="subMenu" :index="resolvePath(item.path)" teleported v-bind="expandCloseIcon">
<template #title>
<div
v-if="toRaw(item.meta.icon)"
:style="getSubMenuIconStyle"
class="sub-menu-icon"
>
<div v-if="toRaw(item.meta.icon)" :style="getSubMenuIconStyle" class="sub-menu-icon">
<component :is="useRenderIcon(item.meta && toRaw(item.meta.icon))" />
</div>
<ReText
v-if="
layout === 'mix' && toRaw(item.meta.icon)
? !isCollapse || item?.pathList?.length !== 2
: !(
layout === 'vertical' &&
isCollapse &&
toRaw(item.meta.icon) &&
item.parentId === null
)
"
<Text
v-if="layout === 'mix' && toRaw(item.meta.icon) ? !isCollapse || item?.pathList?.length !== 2 : !(layout === 'vertical' && isCollapse && toRaw(item.meta.icon) && item.parentId === null)"
:class="{
'!w-full': true,
'!text-inherit': true,
'!pl-4':
layout !== 'horizontal' &&
isCollapse &&
!toRaw(item.meta.icon) &&
item.parentId === null
'!pl-4': layout !== 'horizontal' && isCollapse && !toRaw(item.meta.icon) && item.parentId === null,
}"
:tippyProps="{
offset: [0, -10],
theme: tooltipEffect
theme: tooltipEffect,
}"
>
{{ $t(item.meta.title) }}
</ReText>
</Text>
<SidebarExtraIcon v-if="!isCollapse" :extraIcon="item.meta.extraIcon" />
</template>
<sidebar-item
v-for="child in item.children"
:key="child.path"
:base-path="resolvePath(child.path)"
:is-nest="true"
:item="child"
class="nest-menu"
/>
<sidebar-item v-for="child in item.children" :key="child.path" :base-path="resolvePath(child.path)" :is-nest="true" :item="child" class="nest-menu" />
</el-sub-menu>
</template>

View File

@ -102,11 +102,11 @@ router.beforeEach((to: ToRouteType, _from, next) => {
if (Cookies.get(multipleTabsKey) && userInfo) {
// 无权限跳转403页面
if (to.meta?.roles && !isOneOfArray(to.meta?.roles, userInfo?.roles)) {
next({ path: '/error/403' });
next({ path: '/Error/403' });
}
// 开启隐藏首页后在浏览器地址栏手动输入首页welcome路由则跳转到404页面
if (VITE_HIDE_HOME === 'true' && to.fullPath === '/welcome') {
next({ path: '/error/404' });
next({ path: '/Error/404' });
}
if (_from?.name) {
// name为超链接

View File

@ -1,8 +1,8 @@
import { $t } from '@/plugins/i18n';
export default {
path: '/error',
redirect: '/error/403',
path: '/Error',
redirect: '/Error/403',
meta: {
icon: 'ri:information-line',
showLink: false,
@ -11,25 +11,25 @@ export default {
},
children: [
{
path: '/error/403',
path: '/Error/403',
name: '403',
component: () => import('@/components/error/403.vue'),
component: () => import('@/components/Error/403.vue'),
meta: {
title: $t('menus.pureFourZeroOne'),
},
},
{
path: '/error/404',
path: '/Error/404',
name: '404',
component: () => import('@/components/error/404.vue'),
component: () => import('@/components/Error/404.vue'),
meta: {
title: $t('menus.pureFourZeroFour'),
},
},
{
path: '/error/500',
path: '/Error/500',
name: '500',
component: () => import('@/components/error/500.vue'),
component: () => import('@/components/Error/500.vue'),
meta: {
title: $t('menus.pureFive'),
},

View File

@ -27,4 +27,14 @@ export default [
},
],
},
{
path: '/account-settings',
name: 'AccountSettings',
component: () => import('@/views/account-settings/index.vue'),
meta: {
title: 'buttons.accountSettings',
showLink: false,
rank: 104,
},
},
] satisfies Array<RouteConfigsTable>;

View File

@ -107,7 +107,7 @@ function addPathMatch() {
router.addRoute({
path: '/:pathMatch(.*)',
name: 'pathMatch',
redirect: '/error/404',
redirect: '/Error/404',
});
}
}

View File

@ -1,5 +1,5 @@
import { defineStore } from 'pinia';
import { fetchDeleteUserLoginLog, fetchGetUserLoginLogList } from '@/api/v1/userLoginLog';
import { fetchDeleteUserLoginLog, fetchGetUserLoginLogList, fetchGetUserLoginLogListByLocalUser } from '@/api/v1/userLoginLog';
import { pageSizes } from '@/enums/baseConstant';
import { storeMessage } from '@/utils/message';
import { storePagination } from '@/store/useStorePagination';
@ -66,6 +66,14 @@ export const useUserLoginLogStore = defineStore('userLoginLogStore', {
return pagination(result);
},
/** 分页查询根据用户Id用户登录日志内容 */
async getUserLoginLogListByLocalUser(data: any) {
const baseResult = await fetchGetUserLoginLogListByLocalUser(data);
if (baseResult.code === 200) {
return baseResult.data;
}
},
/** 删除用户登录日志 */
async deleteUserLoginLog(data: any) {
const result = await fetchDeleteUserLoginLog(data);

View File

@ -1,6 +1,6 @@
import { defineStore } from 'pinia';
import { resetRouter, router, routerArrays, storageLocal, store, type userType } from '../utils';
import { fetchAssignRolesToUsers, fetchLogin, fetchLogout, fetchPostEmailCode, refreshTokenApi } from '@/api/v1/adminUser';
import { fetchAssignRolesToUsers, fetchGetUserinfo, fetchLogin, fetchLogout, fetchPostEmailCode, refreshTokenApi } from '@/api/v1/adminUser';
import { useMultiTagsStoreHook } from '../multiTags';
import { type DataInfo, removeToken, setToken, userKey } from '@/utils/auth';
import { message, storeMessage } from '@/utils/message';
@ -79,6 +79,15 @@ export const useUserStore = defineStore({
return false;
},
/** 获取用户信息 */
async getUserinfo() {
const result = await fetchGetUserinfo();
if (result.code === 200) {
return result.data;
}
return {};
},
/** 根据用户id获取角色列表 */
async getRoleListByUserId(data: any) {
const result = await fetchGetRoleListByUserId(data);

View File

@ -0,0 +1,61 @@
<script lang="ts" setup>
import { ref } from 'vue';
import { message } from '@/utils/message';
import { deviceDetection } from '@pureadmin/utils';
defineOptions({
name: 'AccountManagement',
});
const list = ref([
{
title: '账户密码',
illustrate: '当前密码强度:强',
button: '修改',
},
{
title: '密保手机',
illustrate: '已经绑定手机158****6789',
button: '修改',
},
{
title: '密保问题',
illustrate: '未设置密保问题,密保问题可有效保护账户安全',
button: '修改',
},
{
title: '备用邮箱',
illustrate: '已绑定邮箱pure***@163.com',
button: '修改',
},
]);
function onClick(item) {
console.log('onClick', item.title);
message('请根据具体业务自行实现', { type: 'success' });
}
</script>
<template>
<div :class="['min-w-[180px]', deviceDetection() ? 'max-w-[100%]' : 'max-w-[70%]']">
<h3 class="my-8">账户管理</h3>
<div v-for="(item, index) in list" :key="index">
<div class="flex items-center">
<div class="flex-1">
<p>{{ item.title }}</p>
<el-text class="mx-1" type="info">{{ item.illustrate }}</el-text>
</div>
<el-button text type="primary" @click="onClick(item)">
{{ item.button }}
</el-button>
</div>
<el-divider />
</div>
</div>
</template>
<style lang="scss" scoped>
.el-divider--horizontal {
border-top: 0.1px var(--el-border-color) var(--el-border-style);
}
</style>

View File

@ -0,0 +1,162 @@
<script lang="ts" setup>
import { useRouter } from 'vue-router';
import { onBeforeMount, ref } from 'vue';
import { Text } from '@/components/Text';
import Profile from './profile.vue';
import Preferences from './references.vue';
import SecurityLog from './security-log.vue';
import { deviceDetection, useGlobal } from '@pureadmin/utils';
import AccountManagement from './account-management.vue';
import { useDataThemeChange } from '@/layout/hooks/useDataThemeChange';
import LaySidebarTopCollapse from '@/layout/components/lay-sidebar/components/SidebarTopCollapse.vue';
import leftLine from '@iconify-icons/ri/arrow-left-s-line';
import ProfileIcon from '@iconify-icons/ri/user-3-line';
import PreferencesIcon from '@iconify-icons/ri/settings-3-line';
import SecurityLogIcon from '@iconify-icons/ri/window-line';
import AccountManagementIcon from '@iconify-icons/ri/profile-line';
import { $t } from '@/plugins/i18n';
import { userInfos } from '@/views/account-settings/utils/hooks';
const router = useRouter();
const isOpen = ref(!deviceDetection());
const { $storage } = useGlobal<GlobalPropertiesApi>();
const witchPane = ref('profile');
const panes = [
{
key: 'profile',
label: '个人信息',
icon: ProfileIcon,
component: Profile,
},
{
key: 'preferences',
label: '偏好设置',
icon: PreferencesIcon,
component: Preferences,
},
{
key: 'securityLog',
label: '安全日志',
icon: SecurityLogIcon,
component: SecurityLog,
},
{
key: 'accountManagement',
label: '账户管理',
icon: AccountManagementIcon,
component: AccountManagement,
},
];
onBeforeMount(() => {
useDataThemeChange().dataThemeChange($storage.layout?.overallStyle);
});
</script>
<template>
<el-container class="h-full">
<el-aside
v-if="isOpen"
:width="deviceDetection() ? '180px' : '240px'"
class="pure-account-settings overflow-hidden px-2 dark:!bg-[var(--el-bg-color)] border-r-[1px] border-[var(--pure-border-color)]"
>
<el-menu :default-active="witchPane" class="pure-account-settings-menu">
<el-menu-item class="hover:!transition-all hover:!duration-200 hover:!text-base !h-[50px]" @click="router.go(-1)">
<div class="flex items-center">
<IconifyIconOffline :icon="leftLine" />
<span class="ml-2">{{ $t('back') }}</span>
</div>
</el-menu-item>
<div class="flex items-center ml-8 mt-4 mb-4">
<el-avatar :size="48" :src="userInfos.avatar" />
<div class="ml-4 flex flex-col max-w-[130px]">
<Text class="font-bold !self-baseline">
{{ userInfos.nickname }}
</Text>
<Text class="!self-baseline" type="info">
{{ userInfos.username }}
</Text>
</div>
</div>
<el-menu-item
v-for="item in panes"
:key="item.key"
:index="item.key"
@click="
() => {
witchPane = item.key;
if (deviceDetection()) {
isOpen = !isOpen;
}
}
"
>
<div class="flex items-center z-10">
<el-icon>
<IconifyIconOffline :icon="item.icon" />
</el-icon>
<span>{{ item.label }}</span>
</div>
</el-menu-item>
</el-menu>
</el-aside>
<el-main>
<LaySidebarTopCollapse v-if="deviceDetection()" :is-active="isOpen" class="px-0" @toggleClick="isOpen = !isOpen" />
<component :is="panes.find(item => item.key === witchPane).component" :class="[!deviceDetection() && 'ml-[120px]']" />
</el-main>
</el-container>
</template>
<style lang="scss">
.pure-account-settings {
background: $menuBg;
}
.pure-account-settings-menu {
background-color: transparent;
border: none;
.el-menu-item {
height: 48px !important;
color: $menuText;
background-color: transparent !important;
transition: color 0.2s;
&:hover {
color: $menuTitleHover !important;
}
&.is-active {
color: #fff !important;
&:hover {
color: #fff !important;
}
&::before {
position: absolute;
inset: 0 8px;
margin: 4px 0;
clear: both;
content: '';
background: var(--el-color-primary);
border-radius: 3px;
}
}
}
}
</style>
<style lang="scss" scoped>
body[layout] {
.el-menu--vertical .is-active {
color: #fff !important;
transition: color 0.2s;
&:hover {
color: #fff !important;
}
}
}
</style>

View File

@ -0,0 +1,118 @@
<script lang="ts" setup>
import { onMounted, ref } from 'vue';
import { message } from '@/utils/message';
import type { FormInstance } from 'element-plus';
import ReCropperPreview from '@/components/CropperPreview';
import { createFormData, deviceDetection } from '@pureadmin/utils';
import uploadLine from '@iconify-icons/ri/upload-line';
import { useUserStore } from '@/store/system/user';
import { rules } from '@/views/account-settings/utils/columns';
import { onSearch, userInfos } from '@/views/account-settings/utils/hooks';
const imgSrc = ref('');
const cropperBlob = ref();
const cropRef = ref();
const uploadRef = ref();
const isShow = ref(false);
const userInfoFormRef = ref<FormInstance>();
const userStore = useUserStore();
function queryEmail(queryString, callback) {
const emailList = [{ value: '@qq.com' }, { value: '@126.com' }, { value: '@163.com' }];
let results = [];
let queryList = [];
emailList.map(item => queryList.push({ value: queryString.split('@')[0] + item.value }));
results = queryString ? queryList.filter(item => item.value.toLowerCase().indexOf(queryString.toLowerCase()) === 0) : queryList;
callback(results);
}
const onChange = uploadFile => {
const reader = new FileReader();
reader.onload = e => {
imgSrc.value = e.target.result as string;
isShow.value = true;
};
reader.readAsDataURL(uploadFile.raw);
};
const handleClose = () => {
cropRef.value.hidePopover();
uploadRef.value.clearFiles();
isShow.value = false;
};
const onCropper = ({ blob }) => (cropperBlob.value = blob);
const handleSubmitImage = () => {
const formData = createFormData({
files: new File([cropperBlob.value], 'avatar'),
});
// formUpload(formData)
// .then(({ success, data }) => {
// if (success) {
// message('', { type: 'success' });
// handleClose();
// } else {
// message('');
// }
// })
// .catch(error => {
// message(` ${error}`, { type: 'error' });
// });
};
//
const onSubmit = async (formEl: FormInstance) => {
await formEl.validate((valid, fields) => {
if (valid) {
console.log(userInfos);
message('更新信息成功', { type: 'success' });
} else {
console.log('Error submit!', fields);
}
});
};
onMounted(() => {
onSearch();
});
</script>
<template>
<div :class="['min-w-[180px]', deviceDetection() ? 'max-w-[100%]' : 'max-w-[70%]']">
<h3 class="my-8">个人信息</h3>
<el-form ref="userInfoFormRef" :model="userInfos" :rules="rules" label-position="top">
<el-form-item label="头像">
<el-avatar :size="80" :src="userInfos.avatar" />
<el-upload ref="uploadRef" :auto-upload="false" :limit="1" :on-change="onChange" :show-file-list="false" accept="image/*" action="#">
<el-button class="ml-4" plain>
<IconifyIconOffline :icon="uploadLine" />
<span class="ml-2">更新头像</span>
</el-button>
</el-upload>
</el-form-item>
<el-form-item label="昵称" prop="nickname">
<el-input v-model="userInfos.nickname" placeholder="请输入昵称" />
</el-form-item>
<el-form-item label="邮箱" prop="email">
<el-autocomplete v-model="userInfos.email" :fetch-suggestions="queryEmail" :trigger-on-focus="false" class="w-full" clearable placeholder="请输入邮箱" />
</el-form-item>
<el-form-item label="联系电话">
<el-input v-model="userInfos.phone" clearable placeholder="请输入联系电话" />
</el-form-item>
<el-form-item label="简介">
<el-input v-model="userInfos.description" :autosize="{ minRows: 6, maxRows: 8 }" maxlength="56" placeholder="请输入简介" show-word-limit type="textarea" />
</el-form-item>
<el-button type="primary" @click="onSubmit(userInfoFormRef)"> 更新信息</el-button>
</el-form>
<el-dialog v-model="isShow" :before-close="handleClose" :closeOnClickModal="false" :fullscreen="deviceDetection()" destroy-on-close title="编辑头像" width="40%">
<ReCropperPreview ref="cropRef" :imgSrc="imgSrc" @cropper="onCropper" />
<template #footer>
<div class="dialog-footer">
<el-button bg text @click="handleClose">取消</el-button>
<el-button bg text type="primary" @click="handleSubmitImage"> 确定</el-button>
</div>
</template>
</el-dialog>
</div>
</template>

View File

@ -0,0 +1,69 @@
<script setup lang="ts">
import { ref } from "vue";
import { message } from "@/utils/message";
import { deviceDetection } from "@pureadmin/utils";
defineOptions({
name: "Preferences"
});
const list = ref([
{
title: "账户密码",
illustrate: "其他用户的消息将以站内信的形式通知",
checked: true
},
{
title: "系统消息",
illustrate: "系统消息将以站内信的形式通知",
checked: true
},
{
title: "待办任务",
illustrate: "待办任务将以站内信的形式通知",
checked: true
}
]);
function onChange(val, item) {
console.log("onChange", val);
message(`${item.title}设置成功`, { type: "success" });
}
</script>
<template>
<div
:class="[
'min-w-[180px]',
deviceDetection() ? 'max-w-[100%]' : 'max-w-[70%]'
]"
>
<h3 class="my-8">偏好设置</h3>
<div v-for="(item, index) in list" :key="index">
<div class="flex items-center">
<div class="flex-1">
<p>{{ item.title }}</p>
<p class="wp-4">
<el-text class="mx-1" type="info">
{{ item.illustrate }}
</el-text>
</p>
</div>
<el-switch
v-model="item.checked"
inline-prompt
active-text="是"
inactive-text="否"
@change="val => onChange(val, item)"
/>
</div>
<el-divider />
</div>
</div>
</template>
<style lang="scss" scoped>
.el-divider--horizontal {
border-top: 0.1px var(--el-border-color) var(--el-border-style);
}
</style>

View File

@ -0,0 +1,41 @@
<script lang="ts" setup>
import { onMounted, reactive, ref } from 'vue';
import { deviceDetection } from '@pureadmin/utils';
import { PaginationProps, PureTable } from '@pureadmin/table';
import { columns } from '@/views/account-settings/utils/columns';
import { $t } from '@/plugins/i18n';
const loading = ref(true);
const dataList = ref([]);
const pagination = reactive<PaginationProps>({
total: 0,
pageSize: 10,
currentPage: 1,
background: true,
layout: 'prev, pager, next',
});
async function onSearch() {
// loading.value = true;
// const { data } = await getMineLogs();
// dataList.value = data.list;
// pagination.total = data.total;
// pagination.pageSize = data.pageSize;
// pagination.currentPage = data.currentPage;
//
// setTimeout(() => {
// loading.value = false;
// }, 200);
}
onMounted(() => {
onSearch();
});
</script>
<template>
<div :class="['min-w-[180px]', deviceDetection() ? 'max-w-[100%]' : 'max-w-[70%]']">
<h3 class="my-8">{{ $t('security_log') }}</h3>
<pure-table :columns="columns" :data="dataList" :loading="loading" :pagination="pagination" row-key="id" table-layout="auto" />
</div>
</template>

View File

@ -0,0 +1,41 @@
import dayjs from 'dayjs';
import { reactive } from 'vue';
import type { FormRules } from 'element-plus';
export const columns: TableColumnList = [
{
label: '详情',
prop: 'summary',
minWidth: 140,
},
{
label: 'IP 地址',
prop: 'ip',
minWidth: 100,
},
{
label: '地点',
prop: 'address',
minWidth: 140,
},
{
label: '操作系统',
prop: 'system',
minWidth: 100,
},
{
label: '浏览器类型',
prop: 'browser',
minWidth: 100,
},
{
label: '时间',
prop: 'operatingTime',
minWidth: 180,
formatter: ({ operatingTime }) => dayjs(operatingTime).format('YYYY-MM-DD HH:mm:ss'),
},
];
export const rules = reactive<FormRules<any>>({
nickname: [{ required: true, message: '昵称必填', trigger: 'blur' }],
});

View File

@ -0,0 +1,18 @@
import { useUserStore } from '@/store/system/user';
import { reactive } from 'vue';
const userStore = useUserStore();
export const userInfos = reactive({
avatar: '',
username: '',
nickname: '',
email: '',
phone: '',
description: '',
});
export const onSearch = async () => {
const data = await userStore.getUserinfo();
Object.assign(userInfos, data);
userInfos.description = data.personDescription;
};

View File

@ -27,7 +27,6 @@ export function usePublicHooks() {
};
};
});
return {
/** 当前网页是否为`dark`模式 */
isDark,