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

View File

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

View File

@ -8,7 +8,7 @@ export const fetchGetEmailUsersList = (data: any) => {
/** 邮箱用户发送配置管理---获取所有邮箱配置用户 */ /** 邮箱用户发送配置管理---获取所有邮箱配置用户 */
export const fetchGetAllMailboxConfigurationUsers = () => { 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 = () => { export const fetchGetAllMediaTypes = () => {
return http.request<BaseResult<any>>('get', `files/getAllMediaTypes`); return http.request<BaseResult<any>>('get', `files/noManage/getAllMediaTypes`);
}; };
/** 系统文件管理---获取所有文件类型 */ /** 系统文件管理---获取所有文件存储基础路径 */
export const fetchGetAllFilesStoragePath = () => { export const fetchGetAllFilesStoragePath = () => {
return http.request<BaseResult<any>>('get', `files/getAllFilesStoragePath`); return http.request<BaseResult<any>>('get', `files/getAllFilesStoragePath`);
}; };

View File

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

View File

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

View File

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

View File

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

View File

@ -3,7 +3,7 @@ import type { BaseResult } from '@/api/service/types';
/** 系统管理-用户路由获取 */ /** 系统管理-用户路由获取 */
export const getRouterAsync = () => { 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 }); 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) => { export const fetchDeleteUserLoginLog = (data: any) => {
return http.request<BaseResult<object>>('delete', 'userLoginLog/deleteUserLoginLog', { data }); return http.request<BaseResult<object>>('delete', 'userLoginLog/deleteUserLoginLog', { data });

View File

@ -1,7 +1,7 @@
import reText from "./src/index.vue"; import reText from './src/index.vue';
import { withInstall } from "@pureadmin/utils"; import { withInstall } from '@pureadmin/utils';
/** 支持`Tooltip`提示的文本省略组件 */ /** 支持`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 LaySidebarTopCollapse from '../lay-sidebar/components/SidebarTopCollapse.vue';
import GlobalizationIcon from '@/assets/svg/globalization.svg?component'; 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 Setting from '@iconify-icons/ri/settings-3-line';
import Check from '@iconify-icons/ep/check'; import Check from '@iconify-icons/ep/check';
import { $t } from '@/plugins/i18n'; import { $t } from '@/plugins/i18n';
import { userI18nTypeStore } from '@/store/i18n/i18nType'; import { userI18nTypeStore } from '@/store/i18n/i18nType';
import { onMounted } from 'vue'; 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(); const { layout, device, logout, onPanel, pureApp, username, userAvatar, avatarsStyle, toggleSideBar, getDropdownItemStyle, getDropdownItemClass } = useNav();
@ -63,20 +63,8 @@ onMounted(() => {
<!-- 消息通知 --> <!-- 消息通知 -->
<LayNotice id="header-notice" /> <LayNotice id="header-notice" />
<!-- 退出登录 --> <!-- 退出登录 -->
<el-dropdown trigger="click"> <dropdown-menu />
<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>
<span :title="$t('buttons.pureOpenSystemSet')" class="set-icon navbar-bg-hover" @click="onPanel"> <span :title="$t('buttons.pureOpenSystemSet')" class="set-icon navbar-bg-hover" @click="onPanel">
<IconifyIconOffline :icon="Setting" /> <IconifyIconOffline :icon="Setting" />
</span> </span>
@ -104,26 +92,6 @@ onMounted(() => {
min-width: 280px; min-width: 280px;
height: 48px; height: 48px;
color: #000000d9; 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 { .breadcrumb-container {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,6 +1,6 @@
import { defineStore } from 'pinia'; import { defineStore } from 'pinia';
import { resetRouter, router, routerArrays, storageLocal, store, type userType } from '../utils'; 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 { useMultiTagsStoreHook } from '../multiTags';
import { type DataInfo, removeToken, setToken, userKey } from '@/utils/auth'; import { type DataInfo, removeToken, setToken, userKey } from '@/utils/auth';
import { message, storeMessage } from '@/utils/message'; import { message, storeMessage } from '@/utils/message';
@ -79,6 +79,15 @@ export const useUserStore = defineStore({
return false; return false;
}, },
/** 获取用户信息 */
async getUserinfo() {
const result = await fetchGetUserinfo();
if (result.code === 200) {
return result.data;
}
return {};
},
/** 根据用户id获取角色列表 */ /** 根据用户id获取角色列表 */
async getRoleListByUserId(data: any) { async getRoleListByUserId(data: any) {
const result = await fetchGetRoleListByUserId(data); 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 { return {
/** 当前网页是否为`dark`模式 */ /** 当前网页是否为`dark`模式 */
isDark, isDark,