feat: upload avatar logic

This commit is contained in:
KUN1007 2023-10-11 21:20:30 +08:00
parent 93e8b64aa7
commit 3271a1eed1
4 changed files with 371 additions and 15 deletions

View file

@ -141,6 +141,11 @@ onMounted(() => {
align-items: center; align-items: center;
border-radius: 5px; border-radius: 5px;
a { a {
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
color: var(--kungalgame-blue-5); color: var(--kungalgame-blue-5);
} }
&:hover { &:hover {

View file

@ -1,24 +1,205 @@
<script setup lang="ts"> <script setup lang="ts">
import { defineComponent, ref } from 'vue' import { ref } from 'vue'
import message from '@/components/alert/Message'
//
import {
checkImageValid,
resizeImage,
handleUploadAvatar,
} from '../utils/handleFileChange'
const selectedFile = ref(null) //
const uploadedImage = ref<Blob>()
//
const selectedFileUrl = ref<string>('')
// input
const input = ref<HTMLElement>()
const handleFileChange = (event: Event) => {} //
const handleFileChange = async (event: Event) => {
const imgUrl = await handleUploadAvatar(event)
if (imgUrl) {
selectedFileUrl.value = imgUrl
}
}
const handleSubmit = () => {} //
const handleDrop = async (event: DragEvent) => {
//
event.preventDefault()
event.stopPropagation()
const dataTransfer = event.dataTransfer
if (dataTransfer && dataTransfer.files.length > 0) {
const file = dataTransfer.files[0]
//
if (checkImageValid(file)) {
const resizedFile = await resizeImage(file)
uploadedImage.value = resizedFile
selectedFileUrl.value = URL.createObjectURL(resizedFile)
}
}
}
//
const handleDragOver = (event: DragEvent) => {
event.preventDefault()
event.dataTransfer!.dropEffect = 'copy'
}
//
const handleChangeAvatar = () => {
message(
'Image API is not yet completed, stay tuned for updates',
'图片接口还未完成,敬请期待',
'warn'
)
if (uploadedImage.value) {
const formData = new FormData()
formData.append('avatar', uploadedImage.value)
// TODO:
//
}
}
</script> </script>
<template> <template>
<div> <div class="avatar">
<label for="avatar">Choose images to upload (PNG, JPG)</label> <div class="title">更改头像</div>
<input
type="file" <div class="container">
id="avatar" <div
accept=".jpg, .png" ref="upload"
@change="handleFileChange" tabindex="0"
/> class="avatar-upload"
@drop="handleDrop($event)"
@dragover="handleDragOver"
@click="input?.click()"
>
<!-- 加号提示 -->
<span class="plus" v-if="!selectedFileUrl"></span>
<img
class="avatar-preview"
:src="selectedFileUrl"
alt="Uploaded Image"
v-if="selectedFileUrl"
/>
<input
ref="input"
hidden
type="file"
accept=".jpg, .jpeg, .png"
@change="handleFileChange($event)"
/>
</div>
<!-- 帮助区域 -->
<div class="help">
<p>拖拽图片到方框内或点击方框上传</p>
<div>支持 1007KB 以内的图片支持 jpg png 格式</div>
<button @click="handleChangeAvatar">确定更改</button>
</div>
</div>
</div> </div>
<button @click="handleSubmit">submit</button>
</template> </template>
<style lang="scss" scoped></style> <style lang="scss" scoped>
.avatar {
width: 100%;
margin-bottom: 20px;
}
.title {
margin-bottom: 10px;
}
.container {
width: 100%;
display: flex;
justify-content: space-around;
}
.avatar-upload {
width: 150px;
height: 150px;
border: 1px solid var(--kungalgame-blue-4);
border-radius: 5px;
display: flex;
justify-content: center;
align-items: center;
transition: all 0.2s;
&:hover {
border: 1px solid var(--kungalgame-pink-3);
.plus::before,
.plus::after {
background: var(--kungalgame-pink-3);
}
}
/* 单标签加号 */
.plus {
display: inline-block;
position: relative;
}
.plus::before,
.plus::after {
transition: all 0.2s;
content: '';
position: absolute;
background: var(--kungalgame-blue-4);
}
.plus::before {
width: 20px;
height: 2px;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
.plus::after {
width: 2px;
height: 20px;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
}
/* 头像预览 */
.avatar-preview {
max-width: 140px;
max-height: 140px;
}
.help {
display: flex;
flex-direction: column;
justify-content: space-around;
align-items: center;
font-size: small;
button {
cursor: pointer;
padding: 5px 17px;
border: 1px solid var(--kungalgame-blue-4);
background-color: var(--kungalgame-trans-white-9);
border-radius: 5px;
color: var(--kungalgame-blue-4);
transition: all 0.2s;
&:hover {
background-color: var(--kungalgame-blue-4);
color: var(--kungalgame-white);
}
&:active {
transform: scale(0.9);
}
}
}
</style>

View file

@ -1,11 +1,90 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref } from 'vue'
import Avatar from '../components/Avatar.vue' import Avatar from '../components/Avatar.vue'
//
const bioValue = ref('')
//
const handleChangeBio = () => {}
</script> </script>
<template> <template>
<div class="settings"> <div class="settings">
<!-- 更改头像 -->
<Avatar /> <Avatar />
<!-- 更改 bio -->
<div class="bio">
<div class="title">更改签名</div>
<textarea
name="bio"
placeholder="请输入您的新签名,最大 107 个字符"
rows="5"
v-model="bioValue"
>
</textarea>
<div class="help">
<span class="bioCount">字数{{ bioValue.length }}</span>
<button @click="handleChangeBio">确定更改</button>
</div>
</div>
</div> </div>
</template> </template>
<style lang="scss" scoped></style> <style lang="scss" scoped>
.settings {
width: 100%;
height: 100%;
padding: 10px 17px;
}
.bio {
width: 100%;
display: flex;
flex-direction: column;
textarea {
color: var(--kungalgame-font-color-3);
flex: 1;
margin-bottom: 10px;
width: 100%;
border: 1px solid var(--kungalgame-blue-4);
background-color: var(--kungalgame-trans-white-9);
border-radius: 5px;
padding: 5px;
&:focus {
border: 1px solid var(--kungalgame-pink-3);
}
}
}
.title {
margin-bottom: 10px;
}
.help {
display: flex;
justify-content: space-between;
align-items: center;
button {
cursor: pointer;
padding: 5px 17px;
border: 1px solid var(--kungalgame-blue-4);
background-color: var(--kungalgame-trans-white-9);
border-radius: 5px;
color: var(--kungalgame-blue-4);
transition: all 0.2s;
&:hover {
background-color: var(--kungalgame-blue-4);
color: var(--kungalgame-white);
}
&:active {
transform: scale(0.9);
}
}
}
</style>

View file

@ -0,0 +1,91 @@
import message from '@/components/alert/Message'
const dataURItoBlob = (dataURI: string) => {
const byteString = atob(dataURI.split(',')[1])
const ab = new ArrayBuffer(byteString.length)
const ia = new Uint8Array(ab)
for (let i = 0; i < byteString.length; i++) {
ia[i] = byteString.charCodeAt(i)
}
return new Blob([ab], { type: 'image/jpeg' })
}
// 检查图片类型是否合法,允许 png 和 jpg
export const checkImageValid = (file: File) => {
if (file.type === 'image/jpeg' || file.type === 'image/png') {
return true
} else {
message(
'Invalid file type. Please select a JPEG or PNG image.',
'非法的文件类型,请选择 JPG 或 PNG 图片!',
'warn'
)
return false
}
}
// 转换压缩图片
export const resizeImage = (file: File): Promise<Blob> => {
return new Promise((resolve, reject) => {
const image = new Image()
image.src = URL.createObjectURL(file)
image.onload = () => {
// 这里要求图片最大长宽均为 177px
const maxWidth = 177
const maxHeight = 177
let newWidth = image.width
let newHeight = image.height
if (image.width > maxWidth || image.height > maxHeight) {
const aspectRatio = image.width / image.height
if (aspectRatio > 1) {
newWidth = maxWidth
newHeight = newWidth / aspectRatio
} else {
newHeight = maxHeight
newWidth = newHeight * aspectRatio
}
}
const canvas = document.createElement('canvas')
const ctx = canvas.getContext('2d')
canvas.width = newWidth
canvas.height = newHeight
ctx?.drawImage(image, 0, 0, newWidth, newHeight)
const resizedFile = dataURItoBlob(canvas.toDataURL('image/webp', 0.77))
if (resizedFile.size > 1007 * 1024) {
message(
'Image is too large. Please select an image smaller than 1007KB!',
'文件过大, 请选择小于 1007KB 的文件! ',
'warn'
)
reject(
'Image is too large. Please select an image smaller than 1007KB!'
)
} else {
resolve(resizedFile)
}
}
})
}
// 上传头像
export const handleUploadAvatar = async (event: Event) => {
const input = event.target as HTMLInputElement
if (input.files && input.files[0]) {
const file = input.files[0]
// 检查图片是否合法,不合法则退出
const isFileValid = checkImageValid(file)
if (!isFileValid) {
return
}
// 处理图片
const resizedFile = await resizeImage(file)
return URL.createObjectURL(resizedFile)
}
}