修改头像-功能完善

main
ysn 2 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({
url: '/system/user/profile/avatar',
url: '/users/ops/avatar/update',
method: 'post',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
data: data
})
}

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

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

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

@ -1,162 +1,133 @@
<template>
<div>
<div class="user-info-head" @click="editCropper()"><img v-bind:src="options.img" title="点击上传头像" class="img-circle img-lg" /></div>
<el-dialog :title="title" :visible.sync="open" width="800px" append-to-body @opened="modalOpened" @close="closeDialog">
<el-row>
<el-col :xs="24" :md="12" :style="{height: '350px'}">
<vue-cropper
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"
<div class="user-info-head" @click="editCropper()">
<img
v-bind:src="optionsImg"
title="点击上传头像"
class="img-circle img-lg"
/>
</el-col>
<el-col :xs="24" :md="12" :style="{height: '350px'}">
<div class="avatar-upload-preview">
<img :src="previews.url" :style="previews.img" />
</div>
</el-col>
</el-row>
<br />
<el-row>
<el-col :lg="2" :sm="3" :xs="3">
<el-upload action="#" :http-request="requestUpload" :show-file-list="false" :before-upload="beforeUpload">
<el-button size="small">
选择
<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>
<!-- 文件选择隐藏输入 -->
<input
ref="fileInput"
type="file"
accept="image/*"
style="display: none"
@change="handleFileSelect"
/>
</div>
</template>
<script>
import store from "@/store"
import { VueCropper } from "vue-cropper"
import { uploadAvatar } from "@/api/system/user"
import { debounce } from '@/utils'
import store from "@/store";
import { postUserOpsAvatarUpdate } from "@/api/system/user";
import { uploadFile } from "@/utils/requestMinio";
export default {
components: { VueCropper },
data() {
return {
//
open: false,
// cropper
visible: false,
//
title: "修改头像",
options: {
img: store.getters.avatar, //
autoCrop: true, //
autoCropWidth: 200, //
autoCropHeight: 200, //
fixedBox: true, //
outputType:"png", // PNG
filename: 'avatar' //
},
previews: {},
resizeHandler: null
}
optionsImg:
store.getters.config.MINIO_ENDPOINT_HTTPS +
store.getters.userInfo.avatar,
// 500KB
MAX_FILE_SIZE: 500 * 1024,
};
},
methods: {
//
//
editCropper() {
this.open = true
this.$refs.fileInput.click();
},
//
modalOpened() {
this.visible = true
if (!this.resizeHandler) {
this.resizeHandler = debounce(() => {
this.refresh()
}, 100)
//
async handleFileSelect(event) {
const file = event.target.files[0];
if (!file) return;
// 1.
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)
},
//
beforeUpload(file) {
if (file.type.indexOf("image/") == -1) {
this.$modal.msgError("文件格式错误,请上传图片类型,如:JPG,PNG后缀的文件。")
} else {
const reader = new FileReader()
reader.readAsDataURL(file)
reader.onload = () => {
this.options.img = reader.result
this.options.filename = file.name
// MinIO
async uploadAvatar(file) {
try {
// MinIO
await this.ensureMinioInitialized();
const config = this.$store.getters.config;
const bucket = config.MINIO_BUCKET_AVATAR || "remote-avatar-test";
const timestamp = Date.now();
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() {
this.$refs.cropper.getCropBlob(data => {
let formData = new FormData()
formData.append("avatarfile", data, this.options.filename)
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
})
})
//
formatFileSize(bytes) {
if (bytes < 1024) return bytes + " B";
if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(2) + " KB";
return (bytes / (1024 * 1024)).toFixed(2) + " MB";
},
//
realTime(data) {
this.previews = data
},
//
closeDialog() {
this.options.img = store.getters.avatar
this.visible = false
window.removeEventListener("resize", this.resizeHandler)
}
}
}
};
</script>
<style scoped lang="scss">
.user-info-head {
position: relative;
@ -165,7 +136,7 @@ export default {
}
.user-info-head:hover:after {
content: '+';
content: "+";
position: absolute;
left: 0;
right: 0;
@ -181,4 +152,15 @@ export default {
line-height: 110px;
border-radius: 50%;
}
.upload-progress-container {
padding: 20px;
}
.upload-progress-info {
margin-bottom: 15px;
text-align: center;
font-size: 14px;
color: #666;
}
</style>
Loading…
Cancel
Save