feat: upload avatar logic
This commit is contained in:
parent
93e8b64aa7
commit
3271a1eed1
|
@ -141,6 +141,11 @@ onMounted(() => {
|
|||
align-items: center;
|
||||
border-radius: 5px;
|
||||
a {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
color: var(--kungalgame-blue-5);
|
||||
}
|
||||
&:hover {
|
||||
|
|
|
@ -1,24 +1,205 @@
|
|||
<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>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<label for="avatar">Choose images to upload (PNG, JPG)</label>
|
||||
<div class="avatar">
|
||||
<div class="title">更改头像</div>
|
||||
|
||||
<div class="container">
|
||||
<div
|
||||
ref="upload"
|
||||
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"
|
||||
id="avatar"
|
||||
accept=".jpg, .png"
|
||||
@change="handleFileChange"
|
||||
accept=".jpg, .jpeg, .png"
|
||||
@change="handleFileChange($event)"
|
||||
/>
|
||||
</div>
|
||||
<button @click="handleSubmit">submit</button>
|
||||
|
||||
<!-- 帮助区域 -->
|
||||
<div class="help">
|
||||
<p>拖拽图片到方框内或点击方框上传</p>
|
||||
<div>支持 1007KB 以内的图片,支持 jpg 和 png 格式</div>
|
||||
<button @click="handleChangeAvatar">确定更改</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</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>
|
||||
|
|
|
@ -1,11 +1,90 @@
|
|||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import Avatar from '../components/Avatar.vue'
|
||||
|
||||
// 签名的内容
|
||||
const bioValue = ref('')
|
||||
|
||||
// 更改签名
|
||||
const handleChangeBio = () => {}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="settings">
|
||||
<!-- 更改头像 -->
|
||||
<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>
|
||||
</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>
|
||||
|
|
91
src/views/kungalgamer/utils/handleFileChange.ts
Normal file
91
src/views/kungalgamer/utils/handleFileChange.ts
Normal 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)
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue