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 });
|
||||
};
|
||||
|
||||
/** 用户信息---更新本地用户信息 */
|
||||
export const fetchUpdateAdminUserByLocalUser = (data: any) => {
|
||||
return http.request<BaseResult<object>>('put', 'user/noManage/updateAdminUserByLocalUser', { data });
|
||||
};
|
||||
|
||||
/** 用户信息---更新用户信息 */
|
||||
export const fetchUpdateAdminUser = (data: any) => {
|
||||
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 });
|
||||
};
|
||||
|
||||
/** 系统文件管理---下载系统文件 */
|
||||
/** 系统文件管理---根据I下载系统文件d */
|
||||
export const downloadFilesByFileId = (data: any) => {
|
||||
return http.request<any>('get', `files/downloadFilesByFileId/${data.id}`, { responseType: 'blob' });
|
||||
};
|
||||
|
||||
/** 系统文件管理---下载系统文件 */
|
||||
/** 系统文件管理---批量下载系统文件 */
|
||||
export const downloadFilesByFilepath = (data: any) => {
|
||||
return http.request<any>('get', `files/downloadFilesByFilepath`, { params: data, responseType: 'blob' });
|
||||
};
|
||||
|
|
|
@ -9,14 +9,90 @@ const languageData = localStorage.getItem('i18nStore');
|
|||
export const i18n = createI18n({
|
||||
// 如果要支持 compositionAPI,此项必须设置为 false
|
||||
legacy: false,
|
||||
// locale: 'zh',
|
||||
fallbackLocale: 'en',
|
||||
locale: 'zh',
|
||||
fallbackLocale: 'zh',
|
||||
// ? 全局注册$t方法
|
||||
globalInjection: true,
|
||||
// 本地内容存在时,首次加载如果本地存储没有多语言需要再刷新
|
||||
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 function useI18n(app: App) {
|
||||
|
|
|
@ -2,7 +2,16 @@ import { defineStore } from 'pinia';
|
|||
import { pageSizes } from '@/enums/baseConstant';
|
||||
import { storeMessage } from '@/utils/message';
|
||||
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
|
||||
|
@ -60,6 +69,7 @@ export const useAdminUserStore = defineStore('adminUserStore', {
|
|||
return pagination(result);
|
||||
},
|
||||
|
||||
/** 查询用户 */
|
||||
async queryUser(data: any) {
|
||||
const result = await fetchQueryUser(data);
|
||||
if (result.code !== 200) return [];
|
||||
|
@ -72,6 +82,12 @@ export const useAdminUserStore = defineStore('adminUserStore', {
|
|||
return storeMessage(result);
|
||||
},
|
||||
|
||||
/** 修改本地用户信息 */
|
||||
async updateAdminUserByLocalUser(data: any) {
|
||||
const result = await fetchUpdateAdminUserByLocalUser(data);
|
||||
return storeMessage(result);
|
||||
},
|
||||
|
||||
/** 修改用户信息 */
|
||||
async updateAdminUser(data: any) {
|
||||
const result = await fetchUpdateAdminUser(data);
|
||||
|
|
|
@ -2,53 +2,19 @@
|
|||
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';
|
||||
import { panes } from '@/views/account-settings/utils/columns';
|
||||
|
||||
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);
|
||||
});
|
||||
|
|
|
@ -3,110 +3,121 @@ 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 { 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';
|
||||
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 userStore = useUserStore();
|
||||
const uploadRef = ref();
|
||||
const cropRef = ref();
|
||||
// 剪裁完成后头像地址,base64内容
|
||||
const imgBase64Src = ref('');
|
||||
|
||||
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 adminUserStore = useAdminUserStore();
|
||||
|
||||
/** 关闭弹窗 */
|
||||
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 onChange = (uploadFile: any) => {
|
||||
const reader = new FileReader();
|
||||
reader.onload = e => {
|
||||
imgBase64Src.value = e.target.result as string;
|
||||
isShow.value = true;
|
||||
console.log(imgBase64Src.value);
|
||||
};
|
||||
reader.readAsDataURL(uploadFile.raw);
|
||||
};
|
||||
|
||||
// 更新信息
|
||||
/** 提交表单 */
|
||||
const onSubmit = async (formEl: FormInstance) => {
|
||||
await formEl.validate((valid, fields) => {
|
||||
await formEl.validate(async 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 {
|
||||
console.log('Error submit!', fields);
|
||||
message($t('required_fields'), { type: 'warning' });
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
onSearch();
|
||||
onSearchByUserinfo();
|
||||
});
|
||||
</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-form-item :label="$t('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>
|
||||
<IconifyIconOffline :icon="uploadLine" />
|
||||
<span class="ml-2">更新头像</span>
|
||||
<span class="ml-2">{{ $t('upload_avatar') }}</span>
|
||||
</el-button>
|
||||
<template #tip>
|
||||
<div class="el-upload__tip text-red-600">{{ $t('upload_user_avatar_tip') }}</div>
|
||||
</template>
|
||||
</el-upload>
|
||||
</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 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 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 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-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-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>
|
||||
<div class="dialog-footer">
|
||||
<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>
|
||||
import { onMounted, reactive, ref } from 'vue';
|
||||
import { onMounted, reactive } from 'vue';
|
||||
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 { $t } from '@/plugins/i18n';
|
||||
import { useUserLoginLogStore } from '@/store/monitor/userLoginLog';
|
||||
|
||||
const loading = ref(true);
|
||||
const dataList = ref([]);
|
||||
const pagination = reactive<PaginationProps>({
|
||||
total: 0,
|
||||
pageSize: 10,
|
||||
const userLoginLogStore = useUserLoginLogStore();
|
||||
const userLoginLogs = reactive({
|
||||
loading: false,
|
||||
datalist: [],
|
||||
currentPage: 1,
|
||||
pageSize: 150,
|
||||
total: 100,
|
||||
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);
|
||||
}
|
||||
/** 获取用户登录日志内容 */
|
||||
const onSearchByLoginLog = async () => {
|
||||
userLoginLogs.loading = true;
|
||||
|
||||
const data = await userLoginLogStore.getUserLoginLogListByLocalUser(userLoginLogs);
|
||||
userLoginLogs.datalist = data.list;
|
||||
|
||||
userLoginLogs.loading = false;
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
onSearch();
|
||||
onSearchByLoginLog();
|
||||
});
|
||||
</script>
|
||||
|
||||
<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>
|
||||
<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>
|
||||
</template>
|
||||
|
|
|
@ -1,41 +1,77 @@
|
|||
import dayjs from 'dayjs';
|
||||
import { reactive } from 'vue';
|
||||
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 = [
|
||||
// { 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: '详情',
|
||||
prop: 'summary',
|
||||
minWidth: 140,
|
||||
label: $t('userLoginLog_secChUa'),
|
||||
prop: 'secChUa',
|
||||
formatter: ({ secChUa }) => {
|
||||
const regex = /"([^"]+)";/;
|
||||
const match = regex.exec(secChUa);
|
||||
return match ? match[1] : secChUa;
|
||||
},
|
||||
},
|
||||
// // 用户代理是否在手机设备上运行
|
||||
// { label: $t('userLoginLog_secChUaMobile'), prop: 'secChUaMobile', width: 130 },
|
||||
// 用户代理的底层操作系统/平台
|
||||
{ label: $t('userLoginLog_secChUaPlatform'), prop: 'secChUaPlatform' },
|
||||
// 创建时间也就是操作时间
|
||||
{
|
||||
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,
|
||||
label: $t('op_time'),
|
||||
prop: 'op_time',
|
||||
sortable: true,
|
||||
width: 160,
|
||||
formatter: ({ operatingTime }) => dayjs(operatingTime).format('YYYY-MM-DD HH:mm:ss'),
|
||||
},
|
||||
];
|
||||
|
||||
// 修改用户信息规则校验
|
||||
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 { 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();
|
||||
|
||||
// 用户信息内容
|
||||
export const userInfos = reactive({
|
||||
avatar: '',
|
||||
username: '',
|
||||
nickname: '',
|
||||
email: '',
|
||||
phone: '',
|
||||
description: '',
|
||||
summary: '',
|
||||
nickName: '',
|
||||
password: '',
|
||||
sex: '',
|
||||
});
|
||||
|
||||
export const onSearch = async () => {
|
||||
/** 获取用户信息内容 */
|
||||
export const onSearchByUserinfo = async () => {
|
||||
const data = await userStore.getUserinfo();
|
||||
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