feat: upload avatar logic
This commit is contained in:
parent
93e8b64aa7
commit
3271a1eed1
|
@ -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 {
|
||||||
|
|
|
@ -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>
|
||||||
|
|
||||||
|
<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
|
<input
|
||||||
|
ref="input"
|
||||||
|
hidden
|
||||||
type="file"
|
type="file"
|
||||||
id="avatar"
|
accept=".jpg, .jpeg, .png"
|
||||||
accept=".jpg, .png"
|
@change="handleFileChange($event)"
|
||||||
@change="handleFileChange"
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<button @click="handleSubmit">submit</button>
|
|
||||||
|
<!-- 帮助区域 -->
|
||||||
|
<div class="help">
|
||||||
|
<p>拖拽图片到方框内或点击方框上传</p>
|
||||||
|
<div>支持 1007KB 以内的图片,支持 jpg 和 png 格式</div>
|
||||||
|
<button @click="handleChangeAvatar">确定更改</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
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