Compare commits

...

2 Commits

Author SHA1 Message Date
yangws 2a9b39f5e9 Merge pull request 'fix:修改头像上传;' (#84) from yangws into main
Reviewed-on: #84
2024-07-30 16:02:39 +08:00
yangws 1e79807a4b fix:修改头像上传; 2024-07-30 16:02:01 +08:00
7 changed files with 303 additions and 159 deletions

View File

@ -21,10 +21,11 @@
"dependencies": {
"@electron-toolkit/preload": "^3.0.1",
"@electron-toolkit/utils": "^3.0.0",
"@element-plus/icons-vue": "^2.3.1",
"@electron/remote": "^2.1.2",
"@element-plus/icons-vue": "^2.3.1",
"@vitejs/plugin-vue-jsx": "^4.0.0",
"@vueuse/core": "^10.11.0",
"cropperjs": "^1.6.2",
"crypto-js": "^4.2.0",
"electron-dl-manager": "^3.0.0",
"electron-log": "^5.1.7",
@ -34,12 +35,11 @@
"js-cookie": "^3.0.5",
"jsencrypt": "^3.3.2",
"jsondiffpatch": "0.6.0",
"pdfjs-dist": "4.4.168",
"lodash": "^4.17.21",
"pdfjs-dist": "4.4.168",
"pinia": "^2.1.7",
"pinia-plugin-persistedstate": "^3.2.1",
"spark-md5": "^3.0.2",
"vue-cropper": "^1.1.4",
"vue-router": "^4.4.0",
"xlsx": "^0.18.5"
},

View File

@ -19,7 +19,7 @@
</template>
<script setup>
import {ref,defineProps,defineEmits,onMounted} from "vue";
import {ref,defineProps,defineEmits} from "vue";
const activeIndex = ref('0')
const props = defineProps({
classList:{
@ -43,9 +43,7 @@ const handleSelect = (itemDom,pathKey) => {
// })
emits('handleSelect',{index,id})
}
onMounted(() => {
console.log(props.classList,'props.classList')
})
</script>
<style scoped>

View File

@ -36,7 +36,6 @@
})
const isDelClass = delClassDemo()
const userStore = useUserStore().user
console.log(userStore,'userStore')
//
const deleteClassRoom = () => {
ElMessageBox.alert('确认删除该班级?', {
@ -64,7 +63,6 @@
classInfo.student = res.rows.filter(item => item.inrole === 'student')
})
})
console.log(classInfo,'classInfo')
}
}
onMounted(() => {

View File

@ -152,7 +152,6 @@
// }
// })
classList.value = [...response.rows]
console.log(classList.value,'classList')
if(classList.value.length > 0){
classId.value = classList.value[0].id
currentIndex.value = 0

View File

@ -162,7 +162,7 @@
],
birthday: [
{ required: true, message: '请选择出生日期', trigger: 'blur' }
]
],
})
//
@ -225,6 +225,10 @@
studentForm.id = 0
studentForm.entpid = 0
studentForm.name = ''
studentForm.address = ''
studentForm.birthday = ''
studentForm.parentname = ''
studentForm.parentmobile = ''
studentVisible.value = true
}else{
getStudentmain(id).then((response) => {
@ -249,7 +253,7 @@
confirmButtonText: '确认',
type: 'warning',
}).then(() => {
leaveClass({ classid: props.classId, studentid: studentForm.id, inrole: 'student' }).then((response) => {
leaveClass({ classid: props.classId, studentid: studentForm.id, inrole: 'student' }).then(() => {
ElMessage({
type: 'info',
message: '移出成功',

View File

@ -0,0 +1,266 @@
<!-- 裁剪头像弹窗 -->
<template>
<div class="personal-authentica">
<div class="context">
<div class="text-box" v-loading="upLoading">
<!-- 关闭按钮 -->
<!-- 内容 -->
<div id="Box">
<h3>头像设置</h3>
<!-- 裁剪+效果 -->
<div class="box">
<div class="box_1">
<img :src="filePath" ref="image" />
</div>
<!--裁剪完的图片-->
<div class="box_2">
<div class="before"></div>
</div>
</div>
<!-- 裁剪按钮-->
<div class="btn">
<el-button style="margin-right: 20px">选择</el-button>
<input
class="upload"
type="file"
accept=".png, .jpg, .jpeg"
@change="uploadImg"
/>
<el-button @click="cancle">取消</el-button>
<el-button @click="sureSava">提交</el-button>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import { onMounted, reactive, toRefs, ref } from "vue";
//
import Cropper from "cropperjs";
import "cropperjs/dist/cropper.css";
import { ElMessage } from "element-plus";
export default {
props: {
filePath: {
type: String,
},
aspectRatio: { //
type: Number,
default: 1,
},
userId: {
type: String,
},
},
setup(props, context) {
//
const uploadImg = (e) => {
if (e.target.files && e.target.files[0]) {
const file = e.target.files[0];
dataFile.image.src = URL.createObjectURL(file);
if (dataFile.myCropper) {
dataFile.myCropper.destroy();
}
copper();
}
};
const dataFile = reactive({
//
afterImg: "",
//
image: null,
//
myCropper: null,
});
//
const copper = () => {
dataFile.myCropper = new Cropper(dataFile.image, {
/*
* viewMode 视图控制
- 0 无限制
- 1 限制裁剪框不能超出图片的范围
- 2 限制裁剪框不能超出图片的范围 且图片填充模式为 cover 最长边填充
- 3 限制裁剪框不能超出图片的范围 且图片填充模式为 contain 最短边填充
* */
viewMode: 1,
//
/*
* dragMode 拖拽图片模式
- crop 形成新的裁剪框
- move 图片可移动
- none 什么也没有
* */
dragMode: "move",
// ,true
background: true,
//
preview: ".before",
// 0-1 0.8 80%
autoCropArea: 0.8,
//
zoomOnWheel: true,
// +
center: true,
//
aspectRatio: props.aspectRatio,
});
};
//
const sureSava = () => {
if (dataFile.myCropper) {
//
dataFile.afterImg = dataFile.myCropper
.getCroppedCanvas({
imageSmoothingQuality: "high",
})
.toDataURL("image/jpeg"); //
context.emit("sureSava",convertDataUrlToBlob(dataFile.afterImg))
} else {
ElMessage({
message: "请先选择图片",
type: "warning",
});
}
};
//
const upLoading = ref(false);
const convertDataUrlToBlob = (dataUrl) => {
const parts = dataUrl.split(';base64,');
const contentType = parts[0].split(':')[1];
const byteCharacters = atob(parts[1]);
const byteArrays = [];
for (let offset = 0; offset < byteCharacters.length; offset += 512) {
const slice = byteCharacters.slice(offset, offset + 512);
const byteNumbers = new Array(slice.length);
for (let i = 0; i < slice.length; i++) {
byteNumbers[i] = slice.charCodeAt(i);
}
const byteArray = new Uint8Array(byteNumbers);
byteArrays.push(byteArray);
}
return new Blob(byteArrays, { type: contentType });
};
const cancle = () => {
context.emit("cancle");
}
//
onMounted(() => {
if (props.filePath) {
dataFile.image.src = props.filePath;
copper();
}
});
return {
...toRefs(dataFile),
sureSava,
// boxClose,
uploadImg,
upLoading,
cancle,
};
},
};
</script>
<style lang="scss" scoped>
#Box {
padding: 20px;
margin-top: 20px;
border-radius: 5px;
height: 100%;
> h3 {
}
> .box {
display: flex;
align-items: center;
margin-top: 20px;
> div {
width: 400px;
height: 200px;
> img {
display: block;
}
}
> .box_2 {
> .before {
width: 200px;
height: 200px;
position: relative;
left: 150px;
overflow: hidden;
border-radius: 50%;
}
}
}
> .btn {
position: relative;
display: flex;
> .upload {
display: block;
width: 60px;
height: 32px;
position: absolute;
top: 0;
left: 0;
opacity: 0;
}
margin-top: 30px;
}
}
.personal-authentica {
position: fixed;
top: 0;
bottom: 0;
left: 0;
right: 0;
z-index: 400;
font-size: 12px;
color: #ccc;
> .context {
position: absolute;
z-index: 400;
left: 50%;
top: 30%;
transform: translate(-50%, -50%);
> .text-box {
position: relative;
// margin: 120px auto 0;
width: 720px;
height: 350px;
background-color: #fff;
> i {
position: absolute;
color: #aaa;
right: 20px;
top: 8px;
font-size: 35px;
cursor: pointer;
}
}
}
> .mark {
position: fixed;
width: 100vw;
height: 100vh;
background-color: rgba(43, 43, 43, 0.7215686275);
}
}
</style>

View File

@ -2,112 +2,45 @@
<div class="user-info-head" @click="editCropper()">
<img :src="options.img" title="点击上传头像" class="img-circle img-lg" />
<el-dialog
:title="title"
v-model="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"
/>
</el-col>
<el-col :xs="24" :md="12" :style="{ height: '350px' }">
<div class="avatar-upload-preview">
<div v-show="show" class="large" :style="[{backgroundImage:`url(${options.img})`},largePosition]"></div>
</div>
</el-col>
</el-row>
<br />
<el-row>
<el-col :lg="2" :md="2">
<el-upload
action="#"
:http-request="requestUpload"
:show-file-list="false"
:before-upload="beforeUpload"
>
<el-button>
选择
<el-icon class="el-icon--right"><Upload /></el-icon>
</el-button>
</el-upload>
</el-col>
<el-col :lg="{ span: 1, offset: 2 }" :md="2">
<el-button :icon="Plus" @click="changeScale(1)"></el-button>
</el-col>
<el-col :lg="{ span: 1, offset: 1 }" :md="2">
<el-button :icon="Minus" @click="changeScale(-1)"></el-button>
</el-col>
<el-col :lg="{ span: 1, offset: 1 }" :md="2">
<el-button :icon="RefreshLeft" @click="rotateLeft()"></el-button>
</el-col>
<el-col :lg="{ span: 1, offset: 1 }" :md="2">
<el-button :icon="RefreshRight" @click="rotateRight()"></el-button>
</el-col>
<el-col :lg="{ span: 2, offset: 6 }" :md="2">
<el-button type="primary" @click="uploadImg()"> </el-button>
</el-col>
</el-row>
<CropperImage
ref="cropper"
:filePath="options.img"
@sureSava="uploadImg"
@cancle="cancle"
></CropperImage>
</el-dialog>
</div>
</template>
<script setup>
import { Upload, Plus, Minus, RefreshLeft, RefreshRight } from '@element-plus/icons-vue'
import {ref, reactive, getCurrentInstance, watch} from 'vue'
import 'vue-cropper/dist/index.css'
import { VueCropper } from 'vue-cropper'
import {ref, reactive} from 'vue'
import { uploadAvatar } from '@/api/system/user'
import useUserStore from '@/store/modules/user'
import { ElMessage } from 'element-plus'
import { useMouseInElement } from '@vueuse/core'
import CropperImage from './cropperImage.vue'
const userStore = useUserStore()
const { proxy } = getCurrentInstance()
const open = ref(false)
const visible = ref(false)
const title = ref('修改头像')
//
const options = reactive({
img: userStore.user.avatar, //
autoCrop: true, //
autoCropWidth: 200, //
autoCropHeight: 200, //
autoCropWidth: 400, //
autoCropHeight: 400, //
fixedBox: true, //
outputType: 'png', // PNG
previews: {} //
})
//
const show = ref(false)
// ()
const layerPosition = reactive({
left: 0,
top: 0
})
// ()
const largePosition = reactive({
backgroundPositionX: 0,
backgroundPositionY: 0
})
const cropper = ref(null)
const { elementX, elementY } = useMouseInElement(cropper)
//
const transform = ref({})
/** 编辑头像 */
function editCropper() {
@ -117,84 +50,30 @@ function editCropper() {
function modalOpened() {
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) {
ElMessage({
message: '文件格式错误,请上传图片类型,如JPGPNG后缀的文件。',
type: 'error',
})
} else {
const reader = new FileReader()
reader.readAsDataURL(file)
reader.onload = () => {
options.img = reader.result
}
}
}
/** 上传图片 */
function uploadImg() {
proxy.$refs.cropper.getCropBlob((data) => {
let formData = new FormData()
formData.append('avatarfile', data)
uploadAvatar(formData).then((response) => {
open.value = false
options.img = import.meta.env.VITE_APP_BASE_API + response.imgUrl
userStore.user.avatar = options.img
ElMessage({
message: '上传成功',
type: 'success',
})
visible.value = false
function uploadImg(data) {
let formData = new FormData()
formData.append('avatarfile', data)
uploadAvatar(formData).then((response) => {
open.value = false
options.img = import.meta.env.VITE_APP_BASE_API + response.imgUrl
userStore.user.avatar = options.img
ElMessage({
message: '上传成功',
type: 'success',
})
visible.value = false
})
}
/** 实时预览 */
function realTime(data) {
options.previews = data
transform.value = Object.assign({}, data.img)
}
/** 关闭窗口 */
function closeDialog() {
options.img = userStore.avatar
// options.img = userStore.avatar
options.visible = false
}
watch(transform, () => {
//
show.value = true
//
const position = { x: 0, y: 0 }
if (elementX.value < 100) position.x = 0
else if (elementX.value > 300) position.x = 200
else position.x = elementX.value - 100
if (elementY.value < 100) position.y = 0
else if (elementY.value > 300) position.y = 200
else position.y = elementY.value - 100
//
layerPosition.left = position.x + 'px'
layerPosition.top = position.y + 'px'
largePosition.backgroundPositionX = -2 * position.x + 'px'
largePosition.backgroundPositionY = -2 * position.y + 'px'
},{deep:true})
const cancle = () => {
open.value = false
}
</script>
<style lang='scss' scoped>