mirror of
https://github.com/fastapiadmin/FastapiAdmin.git
synced 2026-10-02 00:27:16 +00:00
feat: 新增用户头像字段并优化表单布局
refactor(前端): 统一表单搜索组件样式和布局 style: 优化表格和卡片样式,调整高度和间距 fix(上传): 修复文件上传组件name属性缺失问题 perf(IP定位): 优化IP定位逻辑,增加内网IP判断和重试机制 docs: 更新密码修改成功后的提示信息 chore: 清理无用代码和注释 ``` 这个提交消息包含了多个方面的改进: 1. 新增功能(feat):为用户表添加了avatar字段 2. 重构(refactor):统一了前端表单搜索组件的样式和布局 3. 样式优化(style):调整了表格和卡片的样式 4. 问题修复(fix):修复了文件上传组件name属性缺失的问题 5. 性能优化(perf):改进了IP定位服务的逻辑 6. 文档更新(docs):修改了密码修改成功后的提示信息 7. 杂项(chore):清理了无用的代码和注释 消息遵循了提交规范,每个部分都简明扼要地描述了变更内容。
This commit is contained in:
@@ -14,16 +14,19 @@
|
||||
</a-avatar>
|
||||
<div class="avatar-upload-overlay">
|
||||
<a-upload
|
||||
name="file"
|
||||
name="avatar"
|
||||
v-model:file-list="fileList"
|
||||
list-type="picture-card"
|
||||
:show-upload-list="false"
|
||||
:headers="uploadHeaders"
|
||||
action="/api/v1/system/user/current/avatar/upload"
|
||||
@change="avatarHandleChange"
|
||||
>
|
||||
<a-button type="link">
|
||||
<template #icon><UploadOutlined /></template>
|
||||
更换头像
|
||||
</a-button>
|
||||
:before-upload="beforeUpload"
|
||||
:custom-request="(options) => handleUpload(options)"
|
||||
>
|
||||
<div>
|
||||
<loading-outlined v-if="loading"></loading-outlined>
|
||||
<plus-outlined v-else></plus-outlined>
|
||||
<!-- <UploadOutlined /> -->
|
||||
<div style="margin-top: 8px">上传头像</div>
|
||||
</div>
|
||||
</a-upload>
|
||||
</div>
|
||||
</div>
|
||||
@@ -133,7 +136,6 @@
|
||||
<a-form-item
|
||||
label="性别"
|
||||
name="gender"
|
||||
:rules="[{ required: true, message: '请选择性别' }]"
|
||||
>
|
||||
<a-radio-group v-model:value="infoFormState.gender" class="gender-group">
|
||||
<a-radio v-for="item in DictDataStore['sys_user_sex']" :value="item.dict_value">
|
||||
@@ -284,8 +286,7 @@
|
||||
<script lang="ts" setup>
|
||||
import PageHeader from '@/components/PageHeader.vue';
|
||||
import { ref, reactive, onMounted, h, watch, computed } from 'vue';
|
||||
import { message } from 'ant-design-vue';
|
||||
import type { UploadChangeParam } from "ant-design-vue";
|
||||
import { message, UploadProps } from 'ant-design-vue';
|
||||
import {
|
||||
UserOutlined,
|
||||
LockOutlined,
|
||||
@@ -300,22 +301,28 @@ import {
|
||||
SaveOutlined,
|
||||
CheckCircleOutlined,
|
||||
CloseCircleOutlined,
|
||||
ClockCircleOutlined
|
||||
ClockCircleOutlined,
|
||||
PlusOutlined,
|
||||
LoadingOutlined
|
||||
} from '@ant-design/icons-vue';
|
||||
import { useUserStore } from "@/store/index";
|
||||
import storage from 'store';
|
||||
import { updateCurrentUserInfo, changeCurrentUserPassword, avatarHandleChange } from '@/api/system/user';
|
||||
import { updateCurrentUserInfo, changeCurrentUserPassword, uploadCurrentUserAvatar } from '@/api/system/user';
|
||||
import type { InfoFormState, PasswordFormState } from './types';
|
||||
import { useDictStore } from "@/store/index";
|
||||
import { logout } from '@/api/system/auth';
|
||||
import { useRouter } from "vue-router";
|
||||
|
||||
const loading = ref<boolean>(false);
|
||||
|
||||
const dictStore = useDictStore();
|
||||
|
||||
const DictDataStore = computed(() => {
|
||||
return dictStore.dictObj;
|
||||
return dictStore.dictObj;
|
||||
})
|
||||
|
||||
const getOptions = async () => {
|
||||
const dictOptions = await dictStore.setDict(['sys_user_sex'])
|
||||
const dictOptions = await dictStore.getDict(['sys_user_sex'])
|
||||
return dictOptions
|
||||
}
|
||||
|
||||
@@ -344,9 +351,6 @@ const passwordFormState = reactive<PasswordFormState>({
|
||||
repeatPassword: ''
|
||||
});
|
||||
|
||||
// 上传头像配置
|
||||
const token = storage.get('Access-Token');
|
||||
const uploadHeaders = token ? { Authorization: 'Bearer ' + token } : {};
|
||||
|
||||
// 监听selectedKeys变化
|
||||
watch(selectedKeys, (newVal) => {
|
||||
@@ -365,11 +369,7 @@ const onInfoFormFinish = async (values: any) => {
|
||||
infoSubmitting.value = true;
|
||||
values.avatar = infoFormState.avatar;
|
||||
const response = await updateCurrentUserInfo(values);
|
||||
message.success({
|
||||
content: response.data.msg,
|
||||
icon: h(CheckCircleOutlined, { style: "color: #52c41a" })
|
||||
});
|
||||
|
||||
message.success(response.data.msg);
|
||||
await userStore.getUserInfo;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
@@ -379,6 +379,9 @@ const onInfoFormFinish = async (values: any) => {
|
||||
}
|
||||
};
|
||||
|
||||
// 路由相关
|
||||
const router = useRouter();
|
||||
|
||||
// 密码表单提交
|
||||
const onPasswordFormFinish = async (values: any) => {
|
||||
try {
|
||||
@@ -397,45 +400,64 @@ const onPasswordFormFinish = async (values: any) => {
|
||||
};
|
||||
|
||||
const response = await changeCurrentUserPassword(data);
|
||||
message.success({
|
||||
content: response.data.msg,
|
||||
icon: h(CheckCircleOutlined, { style: "color: #52c41a" })
|
||||
});
|
||||
initPasswordForm();
|
||||
message.success(response.data.msg);
|
||||
|
||||
await logout({ token: storage.get('Access-Token') });
|
||||
|
||||
// 重置 store 状态
|
||||
userStore.$reset();
|
||||
|
||||
storage.remove('Access-Token');
|
||||
storage.remove('Refresh-Token');
|
||||
await userStore.clearUserInfo;
|
||||
// 强制刷新页面
|
||||
router.push('/login');
|
||||
} catch (error) {
|
||||
console.error('修改密码失败:', error);
|
||||
message.error({
|
||||
content: '修改密码失败',
|
||||
icon: h(CloseCircleOutlined, { style: "color: #ff4d4f" })
|
||||
});
|
||||
console.error('修改密码失败',error);
|
||||
} finally {
|
||||
passwordChanging.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 图片上传前的校验
|
||||
const beforeUpload = (file: File) => {
|
||||
const isImage = file.type.startsWith('image/');
|
||||
if (!isImage) {
|
||||
message.error('只能上传图片文件!');
|
||||
}
|
||||
return isImage;
|
||||
};
|
||||
|
||||
const fileList = ref<any[]>([]);
|
||||
// 头像上传处理
|
||||
const avatarHandleChange = (info: UploadChangeParam) => {
|
||||
if (info.file.status === 'done') {
|
||||
const response = info.file.response;
|
||||
const apiUrl = import.meta.env.VITE_API_BASE_URL + import.meta.env.VITE_APP_BASE_API
|
||||
const newAvatar = apiUrl + '/' + response.data.file_path;
|
||||
infoFormState.avatar = newAvatar;
|
||||
message.success({
|
||||
content: '上传成功',
|
||||
icon: h(CheckCircleOutlined, { style: "color: #52c41a" })
|
||||
});
|
||||
} else if (info.file.status === 'error') {
|
||||
message.error({
|
||||
content: '上传失败',
|
||||
icon: h(CloseCircleOutlined, { style: "color: #ff4d4f" })
|
||||
});
|
||||
const handleUpload = async (options: any) => {
|
||||
const { file, onSuccess, onError } = options;
|
||||
loading.value = true; // 开始上传,显示加载状态
|
||||
|
||||
try {
|
||||
const formData = new FormData();
|
||||
formData.append('file', file);
|
||||
|
||||
const response = await uploadCurrentUserAvatar(formData);
|
||||
const fileUrl = response.data.data.file_url;
|
||||
infoFormState.avatar = fileUrl;
|
||||
fileList.value = [{ url: fileUrl }];
|
||||
|
||||
message.success(response.data.msg);
|
||||
|
||||
onSuccess(response, file);
|
||||
} catch (error) {
|
||||
onError(error);
|
||||
console.error('上传失败:', error);
|
||||
} finally {
|
||||
loading.value = false; // 上传结束,隐藏加载状态
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// 初始化表单
|
||||
const initInfoForm = () => {
|
||||
const basicInfo = userStore.basicInfo;
|
||||
console.log("basicInfo", basicInfo);
|
||||
Object.assign(infoFormState, {
|
||||
name: basicInfo.name,
|
||||
gender: basicInfo.gender,
|
||||
|
||||
Reference in New Issue
Block a user