fix: 🐛 头像上传,文件上传问题

This commit is contained in:
H0nGzA1
2023-04-11 18:40:20 +08:00
parent 61ae6e8eb3
commit 0d556bfb2b
12 changed files with 348 additions and 86 deletions

View File

@@ -45,6 +45,7 @@
"ts-md5": "^1.3.1",
"vue": "^3.2.45",
"vue-clipboard3": "^2.0.0",
"vue-cropper": "^1.0.8",
"vue-grid-layout": "^3.0.0-beta1",
"vue-i18n": "^9.2.2",
"vue-router": "^4.1.6",

View File

@@ -0,0 +1,171 @@
<template>
<div class="user-info-head" @click="editCropper()">
<el-avatar :size="100" :src="options.img" />
<el-dialog :title="title" v-model="open" width="600px" append-to-body @opened="modalOpened" @close="closeDialog">
<el-row>
<el-col class="flex justify-center">
<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"
:centerBox="true"
v-if="visible"
class="cropper"
/>
</el-col>
</el-row>
<br />
<el-row class="flex justify-center">
<el-col :lg="2" :md="2">
<el-upload action="#" :http-request="requestUpload" :show-file-list="false" :before-upload="beforeUpload">
<el-button type="success">
选择
<el-icon class="el-icon--right"><Plus /></el-icon>
</el-button>
</el-upload>
</el-col>
<el-col :lg="{ span: 1, offset: 2 }" :md="2">
<el-button icon="RefreshLeft" @click="rotateLeft()"></el-button>
</el-col>
<el-col :lg="{ span: 1, offset: 2 }" :md="2">
<el-button icon="RefreshRight" @click="rotateRight()"></el-button>
</el-col>
<el-col :lg="{ span: 2, offset: 2 }" :md="2">
<el-button type="primary" @click="uploadImg()">更新头像</el-button>
</el-col>
</el-row>
</el-dialog>
</div>
</template>
<script setup>
import 'vue-cropper/dist/index.css';
import { VueCropper } from 'vue-cropper';
import { useUserInfo } from '/@/stores/userInfo';
import { getCurrentInstance, nextTick, reactive, ref } from 'vue';
import { base64ToFile } from '/@/utils/tools';
const userStore = useUserInfo();
const { proxy } = getCurrentInstance();
const open = ref(false);
const visible = ref(false);
const title = ref('修改头像');
const emit = defineEmits(['uploadImg']);
//图片裁剪数据
const options = reactive({
img: userStore.userInfos.avatar, // 裁剪图片的地址
autoCrop: true, // 是否默认生成截图框
autoCropWidth: 200, // 默认生成截图框宽度
autoCropHeight: 200, // 默认生成截图框高度
fixedBox: true, // 固定截图框大小 不允许改变
outputType: 'png', // 默认生成截图为PNG格式
});
/** 编辑头像 */
function editCropper() {
open.value = true;
}
/** 打开弹出层结束时的回调 */
function modalOpened() {
nextTick(() => {
visible.value = true;
});
}
/** 覆盖默认上传行为 */
function requestUpload() {}
/** 向左旋转 */
function rotateLeft() {
proxy.$refs.cropper.rotateLeft();
}
/** 向右旋转 */
function rotateRight() {
proxy.$refs.cropper.rotateRight();
}
/** 图片缩放 */
function changeScale(num) {
num = num || 1;
proxy.$refs.cropper.changeScale(num);
}
/** 上传预处理 */
function beforeUpload(file) {
if (file.type.indexOf('image/') == -1) {
proxy.$modal.msgError('文件格式错误,请上传图片类型,如JPGPNG后缀的文件。');
} else {
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = () => {
options.img = reader.result;
};
}
}
/** 上传图片 */
function uploadImg() {
// 获取截图的 base64 数据
proxy.$refs.cropper.getCropData((data) => {
let img = new Image();
img.src = data;
img.onload = async () => {
let _data = compress(img);
const imgFile = base64ToFile(_data, 'testname');
emit('uploadImg', imgFile);
};
});
}
// 压缩图片
function compress(img) {
let canvas = document.createElement('canvas');
let ctx = canvas.getContext('2d');
// let initSize = img.src.length;
let width = img.width;
let height = img.height;
canvas.width = width;
canvas.height = height;
// 铺底色
ctx.fillStyle = '#fff';
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(img, 0, 0, width, height);
// 进行压缩
let ndata = canvas.toDataURL('image/jpeg', 0.8);
return ndata;
}
/** 关闭窗口 */
function closeDialog() {
options.img = userStore.userInfos.avatar;
options.visible = false;
}
</script>
<style lang="scss" scoped>
.user-info-head {
position: relative;
display: inline-block;
height: 120px;
}
.user-info-head:hover:after {
content: '修改头像';
position: absolute;
text-align: center;
left: 0;
right: 0;
top: 0;
bottom: 0;
color: #000000;
font-size: 20px;
font-style: normal;
cursor: pointer;
line-height: 110px;
}
.cropper {
height: 400px;
width: 400px;
}
</style>

View File

@@ -5,11 +5,17 @@
// 用户信息
export interface UserInfosState {
authBtnList: string[];
photo: string;
roles: string[];
time: number;
avatar: string;
userName: string;
name: string;
email: string;
mobile: string;
gender: string;
dept_info: {
dept_id: number;
dept_name: string;
};
role_info: any[];
}
export interface UserInfosStates {
userInfos: UserInfosState;

View File

@@ -11,11 +11,22 @@ import { request } from '../utils/service';
export const useUserInfo = defineStore('userInfo', {
state: (): UserInfosStates => ({
userInfos: {
avatar: '',
userName: '',
photo: '',
time: 0,
roles: [],
authBtnList: [],
name: '',
email: '',
mobile: '',
gender: '',
dept_info: {
dept_id: 0,
dept_name: '',
},
role_info: [
{
id: 0,
name: '',
},
],
},
}),
actions: {
@@ -26,17 +37,22 @@ export const useUserInfo = defineStore('userInfo', {
} else {
let userInfos: any = await this.getApiUserInfo();
this.userInfos.userName = userInfos.data.name;
this.userInfos.photo = userInfos.data.avatar || 'https://img2.baidu.com/it/u=1978192862,2048448374&fm=253&fmt=auto&app=138&f=JPEG?w=504&h=500'
this.userInfos.time = new Date().getTime()
this.userInfos.roles = ['admin']
Session.set('userInfo', this.userInfos)
this.userInfos.avatar =
userInfos.data.avatar || 'https://img2.baidu.com/it/u=1978192862,2048448374&fm=253&fmt=auto&app=138&f=JPEG?w=504&h=500';
this.userInfos.name = userInfos.data.name;
this.userInfos.email = userInfos.data.email;
this.userInfos.mobile = userInfos.data.mobile;
this.userInfos.gender = userInfos.data.gender;
this.userInfos.dept_info = userInfos.data.dept_info;
this.userInfos.role_info = userInfos.data.role_info;
Session.set('userInfo', this.userInfos);
}
},
async getApiUserInfo() {
return request({
url: '/api/system/user/user_info/',
method: 'get',
})
}
});
},
},
});

View File

@@ -3,16 +3,16 @@
* @param {String} jsonString 需要解析的 json 字符串
* @param {String} defaultValue 默认值
*/
import { uiContext } from "@fast-crud/fast-crud";
import { uiContext } from '@fast-crud/fast-crud';
export function parse(jsonString = "{}", defaultValue = {}) {
let result = defaultValue;
try {
result = JSON.parse(jsonString);
} catch (error) {
console.log(error);
}
return result;
export function parse(jsonString = '{}', defaultValue = {}) {
let result = defaultValue;
try {
result = JSON.parse(jsonString);
} catch (error) {
console.log(error);
}
return result;
}
/**
@@ -21,8 +21,8 @@ export function parse(jsonString = "{}", defaultValue = {}) {
* @param {String} msg 状态信息
* @param {Number} code 状态码
*/
export function response(data = {}, msg = "", code = 0) {
return [200, { code, msg, data }];
export function response(data = {}, msg = '', code = 0) {
return [200, { code, msg, data }];
}
/**
@@ -30,8 +30,8 @@ export function response(data = {}, msg = "", code = 0) {
* @param {Any} data 返回值
* @param {String} msg 状态信息
*/
export function responseSuccess(data = {}, msg = "成功") {
return response(data, msg);
export function responseSuccess(data = {}, msg = '成功') {
return response(data, msg);
}
/**
@@ -40,30 +40,57 @@ export function responseSuccess(data = {}, msg = "成功") {
* @param {String} msg 状态信息
* @param {Number} code 状态码
*/
export function responseError(data = {}, msg = "请求失败", code = 500) {
return response(data, msg, code);
export function responseError(data = {}, msg = '请求失败', code = 500) {
return response(data, msg, code);
}
/**
* @description 记录和显示错误
* @param {Error} error 错误对象
*/
export function errorLog(error:any,notification=true) {
// 打印到控制台
console.error(error);
// 显示提示
if(notification){
uiContext.get().notification.error({ message: error.message });
}
export function errorLog(error: any, notification = true) {
// 打印到控制台
console.error(error);
// 显示提示
if (notification) {
uiContext.get().notification.error({ message: error.message });
}
}
/**
* @description 创建一个错误
* @param {String} msg 错误信息
*/
export function errorCreate(msg:any,notification=true) {
const error = new Error(msg);
errorLog(error,notification);
// throw error;
export function errorCreate(msg: any, notification = true) {
const error = new Error(msg);
errorLog(error, notification);
// throw error;
}
export function base64ToFile(base64: any, fileName: string) {
// 将base64按照 , 进行分割 将前缀 与后续内容分隔开
let data = base64.split(',');
// 利用正则表达式 从前缀中获取图片的类型信息image/png、image/jpeg、image/webp等
let type = data[0].match(/:(.*?);/)[1];
// 从图片的类型信息中 获取具体的文件格式后缀png、jpeg、webp
let suffix = type.split('/')[1];
// 使用atob()对base64数据进行解码 结果是一个文件数据流 以字符串的格式输出
const bstr = window.atob(data[1]);
// 获取解码结果字符串的长度
let n = bstr.length;
// 根据解码结果字符串的长度创建一个等长的整形数字数组
// 但在创建时 所有元素初始值都为 0
const u8arr = new Uint8Array(n);
// 将整形数组的每个元素填充为解码结果字符串对应位置字符的UTF-16 编码单元
while (n--) {
// charCodeAt():获取给定索引处字符对应的 UTF-16 代码单元
u8arr[n] = bstr.charCodeAt(n);
}
// 利用构造函数创建File文件对象
// new File(bits, name, options)
const file = new File([u8arr], `${fileName}.${suffix}`, {
type: type,
});
// 将File文件对象返回给方法的调用者
return file;
}

View File

@@ -89,8 +89,8 @@ export default defineComponent({
const state = reactive({
isShowPassword: false,
ruleForm: {
username: 'superadmin',
password: 'admin123456',
username: '',
password: '',
captcha: '',
captchaKey: '',
captchaImgBase: '',

View File

@@ -1,40 +1,38 @@
import { request } from '/@/utils/service';
import { PageQuery, AddReq, DelReq, EditReq, InfoReq } from '@fast-crud/fast-crud';
import {apiPrefix} from "/@/views/system/messageCenter/api";
import { apiPrefix } from '/@/views/system/messageCenter/api';
export function GetUserInfo(query: PageQuery) {
return request({
url: '/api/system/user/user_info/',
method: 'get',
params: query
});
return request({
url: '/api/system/user/user_info/',
method: 'get',
params: query,
});
}
/**
* 更新用户信息
* @param data
*/
export function updateUserInfo(data: AddReq) {
return request({
url: '/api/system/user/update_user_info/',
method: 'put',
data: data
})
return request({
url: '/api/system/user/update_user_info/',
method: 'put',
data: data,
});
}
/**
* 获取自己接收的消息
* @param query
* @returns {*}
* @constructor
*/
export function GetSelfReceive (query:PageQuery) {
return request({
url: '/api/system/message_center/get_self_receive/',
method: 'get',
params: query
})
export function GetSelfReceive(query: PageQuery) {
return request({
url: '/api/system/message_center/get_self_receive/',
method: 'get',
params: query,
});
}
/***
@@ -42,9 +40,24 @@ export function GetSelfReceive (query:PageQuery) {
* @param data
*/
export function UpdatePassword(data: EditReq) {
return request({
url: '/api/system/user/change_password/',
method: 'put',
data: data
})
return request({
url: '/api/system/user/change_password/',
method: 'put',
data: data,
});
}
/***
* 上传头像
* @param data
*/
export function uploadAvatar(data: AddReq) {
return request({
url: 'api/system/file/',
method: 'post',
data: data,
headers: {
'Content-Type': 'multipart/form-data',
},
});
}

View File

@@ -6,10 +6,9 @@
<el-card shadow="hover" header="个人信息">
<div class="personal-user">
<div class="personal-user-left">
<el-upload class="h100 personal-user-left-upload" :action="uploadAvatar.action" :headers="uploadAvatar.headers" multiple :limit="1">
<img v-if="state.personalForm.avatar" :src="state.personalForm.avatar" />
<img v-else src="https://img2.baidu.com/it/u=1978192862,2048448374&fm=253&fmt=auto&app=138&f=JPEG?w=504&h=500" />
</el-upload>
<!-- <el-avatar :size="100" v-if="state.personalForm.avatar" :src="state.personalForm.avatar" /> -->
<!-- <el-avatar :size="100" v-else src="https://cube.elemecdn.com/0/88/03b0d39583f48206768a7534e55bcpng.png" /> -->
<avatarSelector @uploadImg="uploadImg"></avatarSelector>
</div>
<div class="personal-user-right">
<el-row>
@@ -174,13 +173,18 @@
</template>
<script setup lang="ts" name="personal">
import { reactive, computed, onMounted, ref } from 'vue';
import { reactive, computed, onMounted, ref, defineAsyncComponent } from 'vue';
import { formatAxis } from '/@/utils/formatTime';
import * as api from './api';
import { ElMessage } from 'element-plus';
import { getBaseURL } from '/@/utils/baseUrl';
import { Session } from '/@/utils/storage';
import { useRouter } from 'vue-router';
import { useUserInfo } from '/@/stores/userInfo';
const userStore = useUserInfo();
// 头像裁剪组件
const avatarSelector = defineAsyncComponent(() => import('/@/components/avatarSelector/index.vue'));
// 当前时间提示语
const currentTime = computed(() => {
@@ -339,6 +343,17 @@ const settingPassword = () => {
}
});
};
const uploadImg = (data: any) => {
console.log(data);
let formdata = new FormData();
formdata.append('key', 'test_file');
formdata.append('file', data);
api.uploadAvatar(formdata).then((res: any) => {
// userStore.setUserInfos()
console.log(res);
});
};
</script>
<style scoped lang="scss">

View File

@@ -5045,6 +5045,11 @@ vue-clipboard3@^2.0.0:
dependencies:
clipboard "^2.0.6"
vue-cropper@^1.0.8:
version "1.0.8"
resolved "https://registry.npmjs.org/vue-cropper/-/vue-cropper-1.0.8.tgz#05853bb7702557d05a4784a8d0cd072b57dd8e4f"
integrity sha512-EX9XoT5a/PQ62J6KDZz8hhaFi9ME1k2yBZlRfYCm8iySzTcjw0nDBq8Y65HtyHaH2jJwUKgYfD6mdFCE0GhUzA==
vue-cropperjs@^5.0.0:
version "5.0.0"
resolved "https://registry.npmjs.org/vue-cropperjs/-/vue-cropperjs-5.0.0.tgz#7f8cbc460737af3831b4ded634c95905198e329e"