feat: 🚀 修改用户信息查看用户登录日志完成
This commit is contained in:
parent
a9a2a140fb
commit
becdc20745
|
@ -69,6 +69,11 @@ 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 fetchUpdateAdminUserByLocalUser = (data: any) => {
|
||||||
|
return http.request<BaseResult<object>>('put', 'user/noManage/updateAdminUserByLocalUser', { 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 });
|
||||||
|
|
|
@ -6,12 +6,12 @@ export const fetchGetFilesList = (data: any) => {
|
||||||
return http.request<BaseResult<ResultTable>>('get', `files/getFilesList/${data.currentPage}/${data.pageSize}`, { params: data });
|
return http.request<BaseResult<ResultTable>>('get', `files/getFilesList/${data.currentPage}/${data.pageSize}`, { params: data });
|
||||||
};
|
};
|
||||||
|
|
||||||
/** 系统文件管理---下载系统文件 */
|
/** 系统文件管理---根据I下载系统文件d */
|
||||||
export const downloadFilesByFileId = (data: any) => {
|
export const downloadFilesByFileId = (data: any) => {
|
||||||
return http.request<any>('get', `files/downloadFilesByFileId/${data.id}`, { responseType: 'blob' });
|
return http.request<any>('get', `files/downloadFilesByFileId/${data.id}`, { responseType: 'blob' });
|
||||||
};
|
};
|
||||||
|
|
||||||
/** 系统文件管理---下载系统文件 */
|
/** 系统文件管理---批量下载系统文件 */
|
||||||
export const downloadFilesByFilepath = (data: any) => {
|
export const downloadFilesByFilepath = (data: any) => {
|
||||||
return http.request<any>('get', `files/downloadFilesByFilepath`, { params: data, responseType: 'blob' });
|
return http.request<any>('get', `files/downloadFilesByFilepath`, { params: data, responseType: 'blob' });
|
||||||
};
|
};
|
||||||
|
|
|
@ -9,14 +9,90 @@ const languageData = localStorage.getItem('i18nStore');
|
||||||
export const i18n = createI18n({
|
export const i18n = createI18n({
|
||||||
// 如果要支持 compositionAPI,此项必须设置为 false
|
// 如果要支持 compositionAPI,此项必须设置为 false
|
||||||
legacy: false,
|
legacy: false,
|
||||||
// locale: 'zh',
|
locale: 'zh',
|
||||||
fallbackLocale: 'en',
|
fallbackLocale: 'zh',
|
||||||
// ? 全局注册$t方法
|
// ? 全局注册$t方法
|
||||||
globalInjection: true,
|
globalInjection: true,
|
||||||
// 本地内容存在时,首次加载如果本地存储没有多语言需要再刷新
|
// 本地内容存在时,首次加载如果本地存储没有多语言需要再刷新
|
||||||
messages: languageData ? JSON.parse(languageData).i18n : {},
|
messages: languageData ? JSON.parse(languageData).i18n : {},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/*const siphonI18n = (function () {
|
||||||
|
// 仅初始化一次国际化配置
|
||||||
|
let cache = Object.fromEntries(
|
||||||
|
Object.entries(import.meta.glob('../../locales/!*.y(a)?ml', { eager: true })).map(([key, value]: any) => {
|
||||||
|
const matched = key.match(/([A-Za-z0-9-_]+)\./i)[1];
|
||||||
|
return [matched, value.default];
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
return (prefix = 'zh-CN') => {
|
||||||
|
return cache[prefix];
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
|
||||||
|
/!** 获取对象中所有嵌套对象的key键,并将它们用点号分割组成字符串 *!/
|
||||||
|
function getObjectKeys(obj) {
|
||||||
|
const stack = [];
|
||||||
|
const keys: Set<string> = new Set();
|
||||||
|
|
||||||
|
stack.push({ obj, key: '' });
|
||||||
|
|
||||||
|
while (stack.length > 0) {
|
||||||
|
const { obj, key } = stack.pop();
|
||||||
|
|
||||||
|
for (const k in obj) {
|
||||||
|
const newKey = key ? `${key}.${k}` : k;
|
||||||
|
|
||||||
|
if (obj[k] && isObject(obj[k])) {
|
||||||
|
stack.push({ obj: obj[k], key: newKey });
|
||||||
|
} else {
|
||||||
|
keys.add(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return keys;
|
||||||
|
}
|
||||||
|
|
||||||
|
/!** 将展开的key缓存 *!/
|
||||||
|
const keysCache: Map<string, Set<string>> = new Map();
|
||||||
|
const flatI18n = (prefix = 'zh') => {
|
||||||
|
let cache = keysCache.get(prefix);
|
||||||
|
if (!cache) {
|
||||||
|
cache = getObjectKeys(siphonI18n(prefix));
|
||||||
|
keysCache.set(prefix, cache);
|
||||||
|
}
|
||||||
|
return cache;
|
||||||
|
};
|
||||||
|
|
||||||
|
/!**
|
||||||
|
* 国际化转换工具函数(自动读取根目录locales文件夹下文件进行国际化匹配)
|
||||||
|
* @param message message
|
||||||
|
* @returns 转化后的message
|
||||||
|
*!/
|
||||||
|
export function transformI18n(message: any = '') {
|
||||||
|
if (!message) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理存储动态路由的title,格式 {zh:"",en:""}
|
||||||
|
if (typeof message === 'object') {
|
||||||
|
const locale: string | WritableComputedRef<string> | any = i18n.global.locale;
|
||||||
|
return message[locale?.value];
|
||||||
|
}
|
||||||
|
|
||||||
|
const key = message.match(/(\S*)\./)?.input;
|
||||||
|
|
||||||
|
if (key && flatI18n('zh-CN').has(key)) {
|
||||||
|
return i18n.global.t.call(i18n.global.locale, message);
|
||||||
|
} else if (!key && Object.hasOwn(siphonI18n('zh'), message)) {
|
||||||
|
// 兼容非嵌套形式的国际化写法
|
||||||
|
return i18n.global.t.call(i18n.global.locale, message);
|
||||||
|
} else {
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
}*/
|
||||||
|
|
||||||
export const $t: any = (i18n.global as any).t as any;
|
export const $t: any = (i18n.global as any).t as any;
|
||||||
|
|
||||||
export function useI18n(app: App) {
|
export function useI18n(app: App) {
|
||||||
|
|
|
@ -2,7 +2,16 @@ import { defineStore } from 'pinia';
|
||||||
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';
|
||||||
import { fetchAddAdminUser, fetchDeleteAdminUser, fetchGetAdminUserList, fetchQueryUser, fetchUpdateAdminUser, fetchUpdateUserPasswordByAdmin, fetchUpdateUserStatusByAdmin } from '@/api/v1/adminUser';
|
import {
|
||||||
|
fetchAddAdminUser,
|
||||||
|
fetchDeleteAdminUser,
|
||||||
|
fetchGetAdminUserList,
|
||||||
|
fetchQueryUser,
|
||||||
|
fetchUpdateAdminUser,
|
||||||
|
fetchUpdateAdminUserByLocalUser,
|
||||||
|
fetchUpdateUserPasswordByAdmin,
|
||||||
|
fetchUpdateUserStatusByAdmin,
|
||||||
|
} from '@/api/v1/adminUser';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 用户信息 Store
|
* 用户信息 Store
|
||||||
|
@ -60,6 +69,7 @@ export const useAdminUserStore = defineStore('adminUserStore', {
|
||||||
return pagination(result);
|
return pagination(result);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/** 查询用户 */
|
||||||
async queryUser(data: any) {
|
async queryUser(data: any) {
|
||||||
const result = await fetchQueryUser(data);
|
const result = await fetchQueryUser(data);
|
||||||
if (result.code !== 200) return [];
|
if (result.code !== 200) return [];
|
||||||
|
@ -72,6 +82,12 @@ export const useAdminUserStore = defineStore('adminUserStore', {
|
||||||
return storeMessage(result);
|
return storeMessage(result);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/** 修改本地用户信息 */
|
||||||
|
async updateAdminUserByLocalUser(data: any) {
|
||||||
|
const result = await fetchUpdateAdminUserByLocalUser(data);
|
||||||
|
return storeMessage(result);
|
||||||
|
},
|
||||||
|
|
||||||
/** 修改用户信息 */
|
/** 修改用户信息 */
|
||||||
async updateAdminUser(data: any) {
|
async updateAdminUser(data: any) {
|
||||||
const result = await fetchUpdateAdminUser(data);
|
const result = await fetchUpdateAdminUser(data);
|
||||||
|
|
|
@ -2,53 +2,19 @@
|
||||||
import { useRouter } from 'vue-router';
|
import { useRouter } from 'vue-router';
|
||||||
import { onBeforeMount, ref } from 'vue';
|
import { onBeforeMount, ref } from 'vue';
|
||||||
import { Text } from '@/components/Text';
|
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 { deviceDetection, useGlobal } from '@pureadmin/utils';
|
||||||
import AccountManagement from './account-management.vue';
|
|
||||||
import { useDataThemeChange } from '@/layout/hooks/useDataThemeChange';
|
import { useDataThemeChange } from '@/layout/hooks/useDataThemeChange';
|
||||||
import LaySidebarTopCollapse from '@/layout/components/lay-sidebar/components/SidebarTopCollapse.vue';
|
import LaySidebarTopCollapse from '@/layout/components/lay-sidebar/components/SidebarTopCollapse.vue';
|
||||||
import leftLine from '@iconify-icons/ri/arrow-left-s-line';
|
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 { $t } from '@/plugins/i18n';
|
||||||
import { userInfos } from '@/views/account-settings/utils/hooks';
|
import { userInfos } from '@/views/account-settings/utils/hooks';
|
||||||
|
import { panes } from '@/views/account-settings/utils/columns';
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const isOpen = ref(!deviceDetection());
|
const isOpen = ref(!deviceDetection());
|
||||||
const { $storage } = useGlobal<GlobalPropertiesApi>();
|
const { $storage } = useGlobal<GlobalPropertiesApi>();
|
||||||
const witchPane = ref('profile');
|
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(() => {
|
onBeforeMount(() => {
|
||||||
useDataThemeChange().dataThemeChange($storage.layout?.overallStyle);
|
useDataThemeChange().dataThemeChange($storage.layout?.overallStyle);
|
||||||
});
|
});
|
||||||
|
|
|
@ -3,110 +3,121 @@ import { onMounted, ref } from 'vue';
|
||||||
import { message } from '@/utils/message';
|
import { message } from '@/utils/message';
|
||||||
import type { FormInstance } from 'element-plus';
|
import type { FormInstance } from 'element-plus';
|
||||||
import ReCropperPreview from '@/components/CropperPreview';
|
import ReCropperPreview from '@/components/CropperPreview';
|
||||||
import { createFormData, deviceDetection } from '@pureadmin/utils';
|
import { deviceDetection } from '@pureadmin/utils';
|
||||||
import uploadLine from '@iconify-icons/ri/upload-line';
|
import uploadLine from '@iconify-icons/ri/upload-line';
|
||||||
import { useUserStore } from '@/store/system/user';
|
|
||||||
import { rules } from '@/views/account-settings/utils/columns';
|
import { rules } from '@/views/account-settings/utils/columns';
|
||||||
import { onSearch, userInfos } from '@/views/account-settings/utils/hooks';
|
import { cropperBlob, handleSubmitImage, isShow, onSearchByUserinfo, uploadAvatarSrc, userInfos } from '@/views/account-settings/utils/hooks';
|
||||||
|
import { $t } from '@/plugins/i18n';
|
||||||
|
import { sexConstant } from '@/enums/baseConstant';
|
||||||
|
import { useAdminUserStore } from '@/store/system/adminUser';
|
||||||
|
|
||||||
const imgSrc = ref('');
|
|
||||||
const cropperBlob = ref();
|
|
||||||
const cropRef = ref();
|
|
||||||
const uploadRef = ref();
|
|
||||||
const isShow = ref(false);
|
|
||||||
const userInfoFormRef = ref<FormInstance>();
|
const userInfoFormRef = ref<FormInstance>();
|
||||||
const userStore = useUserStore();
|
const uploadRef = ref();
|
||||||
|
const cropRef = ref();
|
||||||
|
// 剪裁完成后头像地址,base64内容
|
||||||
|
const imgBase64Src = ref('');
|
||||||
|
|
||||||
function queryEmail(queryString, callback) {
|
const adminUserStore = useAdminUserStore();
|
||||||
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 = () => {
|
const handleClose = () => {
|
||||||
cropRef.value.hidePopover();
|
cropRef.value.hidePopover();
|
||||||
uploadRef.value.clearFiles();
|
uploadRef.value.clearFiles();
|
||||||
isShow.value = false;
|
isShow.value = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** 剪裁头像 */
|
||||||
const onCropper = ({ blob }) => (cropperBlob.value = blob);
|
const onCropper = ({ blob }) => (cropperBlob.value = blob);
|
||||||
|
|
||||||
const handleSubmitImage = () => {
|
/** 头像修改内容 */
|
||||||
const formData = createFormData({
|
const onChange = (uploadFile: any) => {
|
||||||
files: new File([cropperBlob.value], 'avatar'),
|
const reader = new FileReader();
|
||||||
});
|
reader.onload = e => {
|
||||||
// formUpload(formData)
|
imgBase64Src.value = e.target.result as string;
|
||||||
// .then(({ success, data }) => {
|
isShow.value = true;
|
||||||
// if (success) {
|
console.log(imgBase64Src.value);
|
||||||
// message('更新头像成功', { type: 'success' });
|
};
|
||||||
// handleClose();
|
reader.readAsDataURL(uploadFile.raw);
|
||||||
// } else {
|
|
||||||
// message('更新头像失败');
|
|
||||||
// }
|
|
||||||
// })
|
|
||||||
// .catch(error => {
|
|
||||||
// message(`提交异常 ${error}`, { type: 'error' });
|
|
||||||
// });
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// 更新信息
|
/** 提交表单 */
|
||||||
const onSubmit = async (formEl: FormInstance) => {
|
const onSubmit = async (formEl: FormInstance) => {
|
||||||
await formEl.validate((valid, fields) => {
|
await formEl.validate(async valid => {
|
||||||
if (valid) {
|
if (valid) {
|
||||||
console.log(userInfos);
|
// 如果用户修改了头像,将上传的路径赋值进去
|
||||||
message('更新信息成功', { type: 'success' });
|
const avatar = uploadAvatarSrc.value;
|
||||||
|
if (avatar) userInfos.avatar = avatar;
|
||||||
|
|
||||||
|
await adminUserStore.updateAdminUserByLocalUser(userInfos);
|
||||||
|
await onSearchByUserinfo();
|
||||||
} else {
|
} else {
|
||||||
console.log('Error submit!', fields);
|
message($t('required_fields'), { type: 'warning' });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
onSearch();
|
onSearchByUserinfo();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div :class="['min-w-[180px]', deviceDetection() ? 'max-w-[100%]' : 'max-w-[70%]']">
|
<div :class="['min-w-[180px]', deviceDetection() ? 'max-w-[100%]' : 'max-w-[70%]']">
|
||||||
<h3 class="my-8">个人信息</h3>
|
<h3 class="my-8">个人信息</h3>
|
||||||
|
|
||||||
|
<!-- 头像 -->
|
||||||
<el-form ref="userInfoFormRef" :model="userInfos" :rules="rules" label-position="top">
|
<el-form ref="userInfoFormRef" :model="userInfos" :rules="rules" label-position="top">
|
||||||
<el-form-item label="头像">
|
<el-form-item :label="$t('avatar')">
|
||||||
<el-avatar :size="80" :src="userInfos.avatar" />
|
<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-upload ref="uploadRef" :auto-upload="false" :limit="1" :on-change="onChange" :show-file-list="false" accept="image/*">
|
||||||
<el-button class="ml-4" plain>
|
<el-button class="ml-4" plain>
|
||||||
<IconifyIconOffline :icon="uploadLine" />
|
<IconifyIconOffline :icon="uploadLine" />
|
||||||
<span class="ml-2">更新头像</span>
|
<span class="ml-2">{{ $t('upload_avatar') }}</span>
|
||||||
</el-button>
|
</el-button>
|
||||||
|
<template #tip>
|
||||||
|
<div class="el-upload__tip text-red-600">{{ $t('upload_user_avatar_tip') }}</div>
|
||||||
|
</template>
|
||||||
</el-upload>
|
</el-upload>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="昵称" prop="nickname">
|
|
||||||
<el-input v-model="userInfos.nickname" placeholder="请输入昵称" />
|
<!-- 用户名 -->
|
||||||
|
<el-form-item :label="$t('adminUser_username')" prop="username">
|
||||||
|
<el-input v-model="userInfos.username" :placeholder="$t('adminUser_username')" autocomplete="off" type="text" />
|
||||||
</el-form-item>
|
</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 :label="$t('adminUser_nickName')" prop="nickName">
|
||||||
|
<el-input v-model="userInfos.nickName" :placeholder="$t('adminUser_nickName')" autocomplete="off" type="text" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="联系电话">
|
|
||||||
<el-input v-model="userInfos.phone" clearable placeholder="请输入联系电话" />
|
<!-- 邮箱 -->
|
||||||
|
<el-form-item :label="$t('adminUser_email')" prop="email">
|
||||||
|
<el-input v-model="userInfos.email" :placeholder="$t('adminUser_email')" autocomplete="off" type="text" />
|
||||||
</el-form-item>
|
</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 :label="$t('adminUser_phone')" prop="phone">
|
||||||
|
<el-input v-model="userInfos.phone" :placeholder="$t('adminUser_phone')" autocomplete="off" type="text" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-button type="primary" @click="onSubmit(userInfoFormRef)"> 更新信息</el-button>
|
|
||||||
|
<!-- 性别 -->
|
||||||
|
<el-form-item :label="$t('adminUser_sex')" prop="sex">
|
||||||
|
<el-select v-model="userInfos.sex" :placeholder="$t('adminUser_sex')" clearable filterable>
|
||||||
|
<el-option v-for="(item, index) in sexConstant" :key="index" :label="item.label" :navigationBar="false" :value="item.value" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
|
<!-- 用户简介 -->
|
||||||
|
<el-form-item :label="$t('adminUser_summary')" prop="summary">
|
||||||
|
<el-input v-model="userInfos.summary" :autosize="{ minRows: 3, maxRows: 6 }" :placeholder="$t('adminUser_summary')" autocomplete="off" maxlength="200" show-word-limit type="textarea" />
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
|
<!-- 更新信息 -->
|
||||||
|
<el-button type="primary" @click="onSubmit(userInfoFormRef)"> {{ $t('update_information') }}</el-button>
|
||||||
</el-form>
|
</el-form>
|
||||||
|
|
||||||
<el-dialog v-model="isShow" :before-close="handleClose" :closeOnClickModal="false" :fullscreen="deviceDetection()" destroy-on-close title="编辑头像" width="40%">
|
<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" />
|
<ReCropperPreview ref="cropRef" :imgSrc="imgBase64Src" @cropper="onCropper" />
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<div class="dialog-footer">
|
<div class="dialog-footer">
|
||||||
<el-button bg text @click="handleClose">取消</el-button>
|
<el-button bg text @click="handleClose">取消</el-button>
|
||||||
|
|
|
@ -1,69 +0,0 @@
|
||||||
<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>
|
|
|
@ -1,41 +1,40 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { onMounted, reactive, ref } from 'vue';
|
import { onMounted, reactive } from 'vue';
|
||||||
import { deviceDetection } from '@pureadmin/utils';
|
import { deviceDetection } from '@pureadmin/utils';
|
||||||
import { PaginationProps, PureTable } from '@pureadmin/table';
|
import { PureTable } from '@pureadmin/table';
|
||||||
import { columns } from '@/views/account-settings/utils/columns';
|
import { columns } from '@/views/account-settings/utils/columns';
|
||||||
import { $t } from '@/plugins/i18n';
|
import { $t } from '@/plugins/i18n';
|
||||||
|
import { useUserLoginLogStore } from '@/store/monitor/userLoginLog';
|
||||||
|
|
||||||
const loading = ref(true);
|
const userLoginLogStore = useUserLoginLogStore();
|
||||||
const dataList = ref([]);
|
const userLoginLogs = reactive({
|
||||||
const pagination = reactive<PaginationProps>({
|
loading: false,
|
||||||
total: 0,
|
datalist: [],
|
||||||
pageSize: 10,
|
|
||||||
currentPage: 1,
|
currentPage: 1,
|
||||||
|
pageSize: 150,
|
||||||
|
total: 100,
|
||||||
background: true,
|
background: true,
|
||||||
layout: 'prev, pager, next',
|
layout: 'prev, pager, next',
|
||||||
});
|
});
|
||||||
|
|
||||||
async function onSearch() {
|
/** 获取用户登录日志内容 */
|
||||||
// loading.value = true;
|
const onSearchByLoginLog = async () => {
|
||||||
// const { data } = await getMineLogs();
|
userLoginLogs.loading = true;
|
||||||
// dataList.value = data.list;
|
|
||||||
// pagination.total = data.total;
|
const data = await userLoginLogStore.getUserLoginLogListByLocalUser(userLoginLogs);
|
||||||
// pagination.pageSize = data.pageSize;
|
userLoginLogs.datalist = data.list;
|
||||||
// pagination.currentPage = data.currentPage;
|
|
||||||
//
|
userLoginLogs.loading = false;
|
||||||
// setTimeout(() => {
|
};
|
||||||
// loading.value = false;
|
|
||||||
// }, 200);
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
onSearch();
|
onSearchByLoginLog();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div :class="['min-w-[180px]', deviceDetection() ? 'max-w-[100%]' : 'max-w-[70%]']">
|
<div :class="['min-w-[280px]', deviceDetection() ? 'max-w-[100%]' : 'max-w-[70%]']">
|
||||||
<h3 class="my-8">{{ $t('security_log') }}</h3>
|
<h3 class="my-8">{{ $t('security_log') }}</h3>
|
||||||
<pure-table :columns="columns" :data="dataList" :loading="loading" :pagination="pagination" row-key="id" table-layout="auto" />
|
<pure-table :columns="columns" :data="userLoginLogs.datalist" :loading="userLoginLogs.loading" :pagination="userLoginLogs" row-key="id" table-layout="auto" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -1,41 +1,77 @@
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import { reactive } from 'vue';
|
import { reactive } from 'vue';
|
||||||
import type { FormRules } from 'element-plus';
|
import type { FormRules } from 'element-plus';
|
||||||
|
import { $t } from '@/plugins/i18n';
|
||||||
|
import ProfileIcon from '@iconify-icons/ri/user-3-line';
|
||||||
|
import Profile from '@/views/account-settings/profile.vue';
|
||||||
|
import SecurityLogIcon from '@iconify-icons/ri/window-line';
|
||||||
|
import SecurityLog from '@/views/account-settings/security-log.vue';
|
||||||
|
import AccountManagementIcon from '@iconify-icons/ri/profile-line';
|
||||||
|
import AccountManagement from '@/views/account-settings/account-management.vue';
|
||||||
|
|
||||||
export const columns: TableColumnList = [
|
export const columns: TableColumnList = [
|
||||||
|
// { type: 'index', index: (index: number) => index + 1, label: '序号', width: 60 },
|
||||||
|
// // 用户名
|
||||||
|
// { label: $t('userLoginLog_username'), prop: 'username', width: 180 },
|
||||||
|
// // 登录Ip
|
||||||
|
// { label: $t('userLoginLog_ipAddress'), prop: 'ipAddress', width: 140 },
|
||||||
|
// 登录Ip归属地
|
||||||
|
{ label: $t('userLoginLog_ipRegion'), prop: 'ipRegion' },
|
||||||
|
// // 登录时代理
|
||||||
|
// { label: $t('userLoginLog_userAgent'), prop: 'userAgent', width: 200 },
|
||||||
|
// 操作类型
|
||||||
|
{ label: $t('userLoginLog_type'), prop: 'type' },
|
||||||
|
// // 标识客户端是否是通过Ajax发送请求的
|
||||||
|
// { label: $t('userLoginLog_xRequestedWith'), prop: 'xRequestedWith', width: 150 },
|
||||||
|
// 用户代理的品牌和版本
|
||||||
{
|
{
|
||||||
label: '详情',
|
label: $t('userLoginLog_secChUa'),
|
||||||
prop: 'summary',
|
prop: 'secChUa',
|
||||||
minWidth: 140,
|
formatter: ({ secChUa }) => {
|
||||||
|
const regex = /"([^"]+)";/;
|
||||||
|
const match = regex.exec(secChUa);
|
||||||
|
return match ? match[1] : secChUa;
|
||||||
},
|
},
|
||||||
{
|
|
||||||
label: 'IP 地址',
|
|
||||||
prop: 'ip',
|
|
||||||
minWidth: 100,
|
|
||||||
},
|
},
|
||||||
|
// // 用户代理是否在手机设备上运行
|
||||||
|
// { label: $t('userLoginLog_secChUaMobile'), prop: 'secChUaMobile', width: 130 },
|
||||||
|
// 用户代理的底层操作系统/平台
|
||||||
|
{ label: $t('userLoginLog_secChUaPlatform'), prop: 'secChUaPlatform' },
|
||||||
|
// 创建时间也就是操作时间
|
||||||
{
|
{
|
||||||
label: '地点',
|
label: $t('op_time'),
|
||||||
prop: 'address',
|
prop: 'op_time',
|
||||||
minWidth: 140,
|
sortable: true,
|
||||||
},
|
width: 160,
|
||||||
{
|
|
||||||
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'),
|
formatter: ({ operatingTime }) => dayjs(operatingTime).format('YYYY-MM-DD HH:mm:ss'),
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// 修改用户信息规则校验
|
||||||
export const rules = reactive<FormRules<any>>({
|
export const rules = reactive<FormRules<any>>({
|
||||||
nickname: [{ required: true, message: '昵称必填', trigger: 'blur' }],
|
username: [{ required: true, message: '昵称必填', trigger: 'blur' }],
|
||||||
|
nickName: [{ required: true, message: '昵称必填', trigger: 'blur' }],
|
||||||
|
email: [{ required: true, message: '昵称必填', trigger: 'blur' }],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// tab栏内容
|
||||||
|
export const panes = [
|
||||||
|
{
|
||||||
|
key: 'profile',
|
||||||
|
label: '个人信息',
|
||||||
|
icon: ProfileIcon,
|
||||||
|
component: Profile,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'securityLog',
|
||||||
|
label: '安全日志',
|
||||||
|
icon: SecurityLogIcon,
|
||||||
|
component: SecurityLog,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'accountManagement',
|
||||||
|
label: '账户管理',
|
||||||
|
icon: AccountManagementIcon,
|
||||||
|
component: AccountManagement,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
|
@ -1,18 +1,53 @@
|
||||||
import { useUserStore } from '@/store/system/user';
|
import { useUserStore } from '@/store/system/user';
|
||||||
import { reactive } from 'vue';
|
import { reactive, ref } from 'vue';
|
||||||
|
import { createFormData } from '@pureadmin/utils';
|
||||||
|
import { fetchUploadFile } from '@/api/v1/system';
|
||||||
|
import { message } from '@/utils/message';
|
||||||
|
import { $t } from '@/plugins/i18n';
|
||||||
|
|
||||||
|
// 剪裁完成后头像数据
|
||||||
|
export const cropperBlob = ref();
|
||||||
|
// 上传地址路径
|
||||||
|
export const uploadAvatarSrc = ref();
|
||||||
|
// 剪裁头像是否显示
|
||||||
|
export const isShow = ref(false);
|
||||||
const userStore = useUserStore();
|
const userStore = useUserStore();
|
||||||
|
|
||||||
|
// 用户信息内容
|
||||||
export const userInfos = reactive({
|
export const userInfos = reactive({
|
||||||
avatar: '',
|
avatar: '',
|
||||||
username: '',
|
username: '',
|
||||||
nickname: '',
|
nickname: '',
|
||||||
email: '',
|
email: '',
|
||||||
phone: '',
|
phone: '',
|
||||||
description: '',
|
summary: '',
|
||||||
|
nickName: '',
|
||||||
|
password: '',
|
||||||
|
sex: '',
|
||||||
});
|
});
|
||||||
|
|
||||||
export const onSearch = async () => {
|
/** 获取用户信息内容 */
|
||||||
|
export const onSearchByUserinfo = async () => {
|
||||||
const data = await userStore.getUserinfo();
|
const data = await userStore.getUserinfo();
|
||||||
Object.assign(userInfos, data);
|
Object.assign(userInfos, data);
|
||||||
userInfos.description = data.personDescription;
|
};
|
||||||
|
|
||||||
|
/** 修改头像 */
|
||||||
|
export const handleSubmitImage = async () => {
|
||||||
|
// 上传头像表单
|
||||||
|
const formData = createFormData({
|
||||||
|
files: new File([cropperBlob.value], 'avatar'),
|
||||||
|
type: 'avatar',
|
||||||
|
});
|
||||||
|
|
||||||
|
// 上传头像
|
||||||
|
const result = await fetchUploadFile(formData);
|
||||||
|
|
||||||
|
// 上传成功后关闭弹窗
|
||||||
|
if (result.code === 200) {
|
||||||
|
uploadAvatarSrc.value = result.data.filepath;
|
||||||
|
userInfos.avatar = result.data.url;
|
||||||
|
message($t('upload_success'), { type: 'success' });
|
||||||
|
isShow.value = false;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in New Issue