page: 📄 用户修改账户信息页面
This commit is contained in:
parent
6ef4e73842
commit
a9a2a140fb
|
@ -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 });
|
||||
};
|
||||
|
|
|
@ -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 });
|
||||
};
|
||||
|
|
|
@ -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');
|
||||
};
|
||||
|
||||
/** 邮箱用户发送配置管理---添加邮箱用户发送配置管理 */
|
||||
|
|
|
@ -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`);
|
||||
};
|
||||
|
|
|
@ -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 });
|
||||
};
|
||||
|
|
|
@ -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 });
|
||||
};
|
||||
|
|
|
@ -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`);
|
||||
};
|
||||
|
|
|
@ -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 });
|
||||
};
|
||||
|
|
|
@ -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视图 */
|
||||
|
|
|
@ -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');
|
||||
};
|
||||
|
||||
/** 上传文件 */
|
||||
|
|
|
@ -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 });
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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>
|
|
@ -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 {
|
||||
|
|
|
@ -64,7 +64,7 @@ const getThemeColorStyle = computed(() => {
|
|||
/** 当网页整体为暗色风格时不显示亮白色主题配色切换选项 */
|
||||
const showThemeColors = computed(() => {
|
||||
return themeColor => {
|
||||
return themeColor === 'light' && isDark.value ? false : true;
|
||||
return !(themeColor === 'light' && isDark.value);
|
||||
};
|
||||
});
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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为超链接
|
||||
|
|
|
@ -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'),
|
||||
},
|
||||
|
|
|
@ -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>;
|
||||
|
|
|
@ -107,7 +107,7 @@ function addPathMatch() {
|
|||
router.addRoute({
|
||||
path: '/:pathMatch(.*)',
|
||||
name: 'pathMatch',
|
||||
redirect: '/error/404',
|
||||
redirect: '/Error/404',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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' }],
|
||||
});
|
|
@ -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;
|
||||
};
|
|
@ -27,7 +27,6 @@ export function usePublicHooks() {
|
|||
};
|
||||
};
|
||||
});
|
||||
|
||||
return {
|
||||
/** 当前网页是否为`dark`模式 */
|
||||
isDark,
|
||||
|
|
Loading…
Reference in New Issue