修改头像-功能完善

main
ysn 3 days ago
parent 47b5af3d98
commit ea2c988d3d
  1. 5
      src/api/system/user.js
  2. 15
      src/layout/components/Navbar.vue
  3. 1
      src/store/getters.js
  4. 11
      src/store/modules/user.js
  5. 254
      src/views/system/user/profile/userAvatar.vue

@ -97,11 +97,10 @@ export function postUserOpsPwd(data) {
} }
// 用户头像上传 // 用户头像上传
export function uploadAvatar(data) { export function postUserOpsAvatarUpdate(data) {
return request({ return request({
url: '/system/user/profile/avatar', url: '/users/ops/avatar/update',
method: 'post', method: 'post',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
data: data data: data
}) })
} }

@ -50,20 +50,24 @@
trigger="hover" trigger="hover"
> >
<div class="avatar-wrapper"> <div class="avatar-wrapper">
<img :src="avatar" class="user-avatar" /> <img :src="config.MINIO_ENDPOINT_HTTPS +userInfo.avatar" class="user-avatar" />
<span class="user-nickname"> <span class="user-nickname">
{{ userInfo.name }} {{ userInfo.name }}
</span> </span>
<span class="user-nickname"> <span class="user-nickname">
<el-link <el-link
type="primary" type="primary"
icon="el-icon-circle-check"
:underline="false" :underline="false"
> >
{{ userInfo.status }} {{
userInfo.status
? userStateList.find((item) => item.id == userInfo.status)
.state
: ""
}}
</el-link> </el-link>
</span> </span>
<span class="user-nickname"> <span class="user-nickname" v-if="userInfo.group">
所属单位 所属单位
<el-link type="primary" :underline="false"> <el-link type="primary" :underline="false">
{{ userInfo.group }} {{ userInfo.group }}
@ -132,7 +136,6 @@ import SystemSettingDialog from "./SystemSettingDialog";
import AboutDialog from "./AboutDialog"; import AboutDialog from "./AboutDialog";
export default { export default {
dicts: ["sys_normal_disable"],
components: { components: {
Breadcrumb, Breadcrumb,
Logo, Logo,
@ -167,7 +170,7 @@ export default {
}, },
}, },
computed: { computed: {
...mapGetters(["sidebar", "avatar", "device", "userInfo"]), ...mapGetters(["sidebar", "avatar", "device", "config", "userStateList", "userInfo"]),
setting: { setting: {
get() { get() {
return this.$store.state.settings.showSettings; return this.$store.state.settings.showSettings;

@ -8,6 +8,7 @@ const getters = {
visitedViews: state => state.tagsView.visitedViews, visitedViews: state => state.tagsView.visitedViews,
cachedViews: state => state.tagsView.cachedViews, cachedViews: state => state.tagsView.cachedViews,
loginInfo: state => state.user.loginInfo, loginInfo: state => state.user.loginInfo,
userStateList: state => state.user.userStateList,
token: state => state.user.token, token: state => state.user.token,
userInfo: state => state.user.userInfo, userInfo: state => state.user.userInfo,
introduction: state => state.user.introduction, introduction: state => state.user.introduction,

@ -2,7 +2,7 @@ import store from '@/store'
import router from '@/router' import router from '@/router'
import cache from '@/plugins/cache' import cache from '@/plugins/cache'
import { MessageBox } from 'element-ui' import { MessageBox } from 'element-ui'
import { login, logout, getInfo, getCommonConfigOptions } from '@/api/login' import { login, logout, getInfo, getCommonConfigOptions, postUserStateList } from '@/api/login'
import { getToken, getLoginInfo, setToken, setLoginInfo, removeToken, removeLoginInfo } from '@/utils/auth' import { getToken, getLoginInfo, setToken, setLoginInfo, removeToken, removeLoginInfo } from '@/utils/auth'
import { isHttp, isEmpty } from "@/utils/validate" import { isHttp, isEmpty } from "@/utils/validate"
import defAva from '@/assets/images/profile.jpg' import defAva from '@/assets/images/profile.jpg'
@ -11,11 +11,12 @@ import { initMinioClient, parseMinioFilePath } from '@/utils/requestMinio'
const user = { const user = {
state: { state: {
loginInfo: getLoginInfo(), loginInfo: getLoginInfo(),
userStateList: [],
userInfo: {}, userInfo: {},
token: getToken(), token: getToken(),
roles: [], roles: [],
permissions: [], permissions: [],
netConfig: {} netConfig: {},
}, },
mutations: { mutations: {
@ -37,6 +38,9 @@ const user = {
SET_NET_CONFIG: (state, netConfig) => { SET_NET_CONFIG: (state, netConfig) => {
state.netConfig = netConfig state.netConfig = netConfig
}, },
SET_USER_STATE_LIST: (state, userStateList) => {
state.userStateList = userStateList
},
}, },
actions: { actions: {
// 登录 // 登录
@ -60,6 +64,9 @@ const user = {
GetInfo({ commit, state }) { GetInfo({ commit, state }) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
getInfo().then(res => { getInfo().then(res => {
postUserStateList().then(res => {
commit('SET_USER_STATE_LIST', res.data.list)
})
const user = res.data const user = res.data
let avatar = user.avatar || "" let avatar = user.avatar || ""
if (!isHttp(avatar)) { if (!isHttp(avatar)) {

@ -1,162 +1,133 @@
<template> <template>
<div> <div>
<div class="user-info-head" @click="editCropper()"><img v-bind:src="options.img" title="点击上传头像" class="img-circle img-lg" /></div> <div class="user-info-head" @click="editCropper()">
<el-dialog :title="title" :visible.sync="open" width="800px" append-to-body @opened="modalOpened" @close="closeDialog"> <img
<el-row> v-bind:src="optionsImg"
<el-col :xs="24" :md="12" :style="{height: '350px'}"> title="点击上传头像"
<vue-cropper class="img-circle img-lg"
ref="cropper"
:img="options.img"
:info="true"
:autoCrop="options.autoCrop"
:autoCropWidth="options.autoCropWidth"
:autoCropHeight="options.autoCropHeight"
:fixedBox="options.fixedBox"
:outputType="options.outputType"
@realTime="realTime"
v-if="visible"
/> />
</el-col>
<el-col :xs="24" :md="12" :style="{height: '350px'}">
<div class="avatar-upload-preview">
<img :src="previews.url" :style="previews.img" />
</div> </div>
</el-col> <!-- 文件选择隐藏输入 -->
</el-row> <input
<br /> ref="fileInput"
<el-row> type="file"
<el-col :lg="2" :sm="3" :xs="3"> accept="image/*"
<el-upload action="#" :http-request="requestUpload" :show-file-list="false" :before-upload="beforeUpload"> style="display: none"
<el-button size="small"> @change="handleFileSelect"
选择 />
<i class="el-icon-upload el-icon--right"></i>
</el-button>
</el-upload>
</el-col>
<el-col :lg="{span: 1, offset: 2}" :sm="2" :xs="2">
<el-button icon="el-icon-plus" size="small" @click="changeScale(1)"></el-button>
</el-col>
<el-col :lg="{span: 1, offset: 1}" :sm="2" :xs="2">
<el-button icon="el-icon-minus" size="small" @click="changeScale(-1)"></el-button>
</el-col>
<el-col :lg="{span: 1, offset: 1}" :sm="2" :xs="2">
<el-button icon="el-icon-refresh-left" size="small" @click="rotateLeft()"></el-button>
</el-col>
<el-col :lg="{span: 1, offset: 1}" :sm="2" :xs="2">
<el-button icon="el-icon-refresh-right" size="small" @click="rotateRight()"></el-button>
</el-col>
<el-col :lg="{span: 2, offset: 6}" :sm="2" :xs="2">
<el-button type="primary" size="small" @click="uploadImg()"> </el-button>
</el-col>
</el-row>
</el-dialog>
</div> </div>
</template> </template>
<script> <script>
import store from "@/store" import store from "@/store";
import { VueCropper } from "vue-cropper" import { postUserOpsAvatarUpdate } from "@/api/system/user";
import { uploadAvatar } from "@/api/system/user" import { uploadFile } from "@/utils/requestMinio";
import { debounce } from '@/utils'
export default { export default {
components: { VueCropper },
data() { data() {
return { return {
//
open: false,
// cropper
visible: false,
//
title: "修改头像", title: "修改头像",
options: { optionsImg:
img: store.getters.avatar, // store.getters.config.MINIO_ENDPOINT_HTTPS +
autoCrop: true, // store.getters.userInfo.avatar,
autoCropWidth: 200, // // 500KB
autoCropHeight: 200, // MAX_FILE_SIZE: 500 * 1024,
fixedBox: true, // };
outputType:"png", // PNG
filename: 'avatar' //
},
previews: {},
resizeHandler: null
}
}, },
methods: { methods: {
// //
editCropper() { editCropper() {
this.open = true this.$refs.fileInput.click();
}, },
// //
modalOpened() { async handleFileSelect(event) {
this.visible = true const file = event.target.files[0];
if (!this.resizeHandler) { if (!file) return;
this.resizeHandler = debounce(() => {
this.refresh() // 1.
}, 100) if (!file.type.startsWith("image/")) {
this.$modal.msgError("只能上传图片文件");
event.target.value = "";
return;
}
// 2500KB
if (file.size > this.MAX_FILE_SIZE) {
this.$modal.msgError(
`文件大小不能超过 500KB(当前:${this.formatFileSize(file.size)}`
);
event.target.value = "";
return;
}
try {
await this.uploadAvatar(file);
} catch (error) {
console.error("头像上传失败:", error);
this.$modal.msgError("上传失败: " + error.message);
} finally {
//
event.target.value = "";
} }
window.addEventListener("resize", this.resizeHandler)
},
//
refresh() {
this.$refs.cropper.refresh()
},
//
requestUpload() {
},
//
rotateLeft() {
this.$refs.cropper.rotateLeft()
},
//
rotateRight() {
this.$refs.cropper.rotateRight()
},
//
changeScale(num) {
num = num || 1
this.$refs.cropper.changeScale(num)
}, },
// // MinIO
beforeUpload(file) { async uploadAvatar(file) {
if (file.type.indexOf("image/") == -1) { try {
this.$modal.msgError("文件格式错误,请上传图片类型,如:JPG,PNG后缀的文件。") // MinIO
} else { await this.ensureMinioInitialized();
const reader = new FileReader()
reader.readAsDataURL(file) const config = this.$store.getters.config;
reader.onload = () => { const bucket = config.MINIO_BUCKET_AVATAR || "remote-avatar-test";
this.options.img = reader.result const timestamp = Date.now();
this.options.filename = file.name const ext = file.name.split(".").pop() || "png";
const fileName = `${timestamp}.${ext}`;
const objectName = `head_portrait/${fileName}`;
console.log(
`Uploading avatar to bucket: ${bucket}, object: ${objectName}`
);
// 使 MinIO
const result = await uploadFile(
bucket,
objectName,
file,
{},
(percent) => {}
);
console.log("MinIO 上传成功:", result);
let avatar = config.MINIO_BUCKET_AVATAR + "/" + result.objectName;
//
await postUserOpsAvatarUpdate({
avatar: avatar,
});
//
const avatarUrl = config.MINIO_ENDPOINT_HTTPS + avatar;
this.optionsImg = avatarUrl;
store.commit("SET_AVATAR", avatar);
} catch (error) {
throw error;
} }
},
// MinIO
async ensureMinioInitialized() {
const config = this.$store.getters.config;
if (!config || !config.MINIO_ENDPOINT) {
console.log("MinIO config not loaded, fetching...");
await this.$store.dispatch("GetNetConfig");
} }
}, },
// //
uploadImg() { formatFileSize(bytes) {
this.$refs.cropper.getCropBlob(data => { if (bytes < 1024) return bytes + " B";
let formData = new FormData() if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(2) + " KB";
formData.append("avatarfile", data, this.options.filename) return (bytes / (1024 * 1024)).toFixed(2) + " MB";
uploadAvatar(formData).then(response => {
this.open = false
this.options.img = process.env.VUE_APP_BASE_API + response.imgUrl
store.commit('SET_AVATAR', this.options.img)
this.$modal.msgSuccess("修改成功")
this.visible = false
})
})
}, },
//
realTime(data) {
this.previews = data
}, },
// };
closeDialog() {
this.options.img = store.getters.avatar
this.visible = false
window.removeEventListener("resize", this.resizeHandler)
}
}
}
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
.user-info-head { .user-info-head {
position: relative; position: relative;
@ -165,7 +136,7 @@ export default {
} }
.user-info-head:hover:after { .user-info-head:hover:after {
content: '+'; content: "+";
position: absolute; position: absolute;
left: 0; left: 0;
right: 0; right: 0;
@ -181,4 +152,15 @@ export default {
line-height: 110px; line-height: 110px;
border-radius: 50%; border-radius: 50%;
} }
.upload-progress-container {
padding: 20px;
}
.upload-progress-info {
margin-bottom: 15px;
text-align: center;
font-size: 14px;
color: #666;
}
</style> </style>
Loading…
Cancel
Save