Files
meida/puton-master/src/components/PickAndUploadImage.vue
T
2026-08-19 21:48:12 +08:00

429 lines
9.9 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<ScrollWithBar>
<div class="pick-And-upload-image">
<div class="tip">
{{ $t('tips.upload') }} <span>{{ subNavText }}</span>
</div>
<el-upload
:fileList="fileList"
:auto-upload="true"
:headers="{ 'X-Access-Token': cookies.get('token') }"
action="/api/cloth/uploadClothFile"
multiple
accept="image/*"
class="custom-uploader"
list-type="picture-card"
:on-success="uploadSuccess"
:before-upload="beforeUpload"
ref="uploadRef"
:limit="20"
>
<template #trigger>
<div class="trigger-icon">
<MyIcons :size="89" :type="nav.clothType == 'hair' ? 'hair' : 'upload'" />
</div>
</template>
<template #file="{ file }">
<img class="thumbnail" :src="file.url" alt="" />
<span class="el-upload-list__item-actions">
<span class="el-upload-list__item-delete" @click="handleRemove(file)">
<el-icon><CircleClose /></el-icon>
</span>
</span>
</template>
</el-upload>
<!-- <p class="prompt">(1-8张,效果越多越好)</p> -->
<p class="prompt" v-if="nav.clothType == 'hair'">{{ $t('tips.uploadTip') }}</p>
<el-input
:placeholder="$t('tips.placeholder')"
size="large"
class="link-input"
v-model="linkText"
>
<template #prefix>
<IconLink />
</template>
<template #suffix>
<div class="grabButton" @click="grabImage">{{ $t('tips.pickImg') }}</div>
</template>
</el-input>
<div class="grab-img-list">
<el-row>
<el-col
v-for="(item, index) in grapImgList"
:key="index"
:lg="2"
:md="4"
:sm="6"
:xl="2"
:xs="12"
>
<div class="grap-image" @click="handleSelect(item)">
<el-image :src="item.fileUrl" class="cover-img" fit="cover" lazy />
<span
class="cover-icon"
:class="{ active: fileList.some((i) => i.name === item.id) }"
>
<el-icon
class="icon-item"
v-if="fileList.some((i) => i.name === item.id)"
color="#fff"
>
<SuccessFilled />
</el-icon>
</span>
</div>
</el-col>
</el-row>
</div>
<div class="upload-btn">
<el-checkbox
size="small"
v-model="isSyncClothesLibrary"
@change="handleSyncToClothingLibrary"
>{{ $t('tips.sync') }}</el-checkbox
>
<MyButton
style="width: 151px; height: 36px; margin-left: 5px"
:text="$t('tips.upload')"
@click="handleUpload"
></MyButton>
</div>
</div>
</ScrollWithBar>
</template>
<script setup lang="ts">
import { CircleClose, SuccessFilled } from '@element-plus/icons-vue'
import { useCookies } from '@vueuse/integrations/useCookies'
import { ElMessageBox, ElLoading, type UploadUserFile, type UploadProps } from 'element-plus'
import { storeToRefs } from 'pinia'
import IconLink from '@/components/icons/IconLink.vue'
import MyButton from '@/components/MyButton.vue'
import ScrollWithBar from '@/components/ScrollWithBar.vue'
import { MENU_CONFIG } from '@/constants'
import api from '@/plugins/axios'
import { useGlobalStore } from '@/stores/globalStore'
import { getLocalImageSize } from '@/utils'
const cookies = useCookies(['locale'])
const fileList = ref<UploadUserFile[]>([])
const grapImgList = ref<Record<'fileUrl' | 'id' | 'thumbnailFileUrl', string>[]>([])
const linkText = ref<string>()
const isSyncClothesLibrary = ref<boolean>(true)
const globalStore = useGlobalStore()
const { menu, nav, gender } = storeToRefs(globalStore)
const emit = defineEmits<{
onUploadSuccess: []
}>()
type IStatus = {
imgUploading: boolean
}
const status = reactive<IStatus>({
imgUploading: false
})
const uploadRef = ref()
const handleRemove = (file: any) => {
const name = file?.response?.result?.id || file?.name
const newFileList = fileList.value.filter((i) => i.name != name)
fileList.value = newFileList
}
const subNavText = computed(
() => MENU_CONFIG.value.find((item) => item.value === menu.value.subNav)?.name
)
const reset = () => {
// isSyncClothesLibrary.value = false
fileList.value = []
linkText.value = ''
grapImgList.value = []
}
// 切换tab 清空数据
watch(subNavText, () => reset())
const grabImage = async () => {
linkText.value && (await isValidURL(linkText.value))
const loading = ElLoading.service({
lock: true,
text: '抓取中',
background: 'rgba(0, 0, 0, 0.7)'
})
const { result } = await api.grabPicture({ grabUrl: linkText.value })
grapImgList.value = result
loading.close()
}
const successListMarkRef = ref<any[]>([])
const uploadSuccess = (response: any, uploadFile: any, uploadFiles: any) => {
successListMarkRef.value.push(uploadFile)
const { id: name, fileUrl: url } = response.result
fileList.value.push({ name, url })
const allUids = uploadFiles.map((i: any) => i.uid)
const successedUids = successListMarkRef.value.map((i) => i.uid)
status.imgUploading = !allUids.every((uid: number) => successedUids.includes(uid))
}
const beforeUpload: UploadProps['beforeUpload'] = async (rawFile) => {
if (!rawFile.type.startsWith('image/')) {
ElMessage.error('请选择正确的图片')
return Promise.reject()
}
const size = await getLocalImageSize(rawFile)
// console.log('AT-[ size xxxxxx &&&&&********** ]', size)
if (size.width < 500) {
ElMessage.error('为确保效果,请上传500像素以上的图片')
return Promise.reject()
}
status.imgUploading = true
return Promise.resolve()
}
const handleSelect = async (item: any) => {
console.log('AT-[ handleSelect &&&&&********** ]')
// 判断图片是否已存在
const existingFile = fileList.value.find((file) => item.fileUrl === file.url)
if (!existingFile) {
// 手动上传
const imageUrl = item.fileUrl
const response = await fetch(imageUrl)
const blob = await response.blob()
const file = new File([blob], item.id, { type: blob.type })
uploadRef.value.handleStart(file)
// 添加到列表
fileList.value.push({
name: item.id,
url: item.fileUrl
})
}
}
const handleSyncToClothingLibrary = (val: any) => {
isSyncClothesLibrary.value = val
}
const handleUpload = async () => {
if (!status.imgUploading && !fileList.value.length) {
ElMessage.warning('请先选择图片')
return
}
const loading = ElLoading.service({
lock: true,
text: '上传中',
background: 'rgba(0, 0, 0, 0.7)'
})
if (status.imgUploading) {
while (status.imgUploading) {
await new Promise((r) => setTimeout(r, 2000))
}
}
const clothFileList = fileList.value?.map((file) => file.name)
const clothType = nav.value.clothType as 'topCloth' | 'bottomCloth' | 'dress'
await api.addClothes({
clothFileList,
clothType,
isSync: isSyncClothesLibrary.value ? 'yes' : 'no',
sex: gender.value
})
emit('onUploadSuccess')
loading.close()
}
// expose
/**
* 输入链接校验
*/
const isValidURL = (url: string) => {
try {
const parsedURL = new URL(url)
const hostname = parsedURL.hostname
const domain = hostname.split('.').slice(-2).join('.')
return domain === 'vip.com'
} catch {
ElMessageBox({
type: 'error',
message: '请输入正确的链接'
})
return false
}
}
defineExpose({ reset })
</script>
<style scoped lang="scss">
.pick-And-upload-image {
padding-left: 20px;
padding-right: 20px;
padding-bottom: 20px;
border-right: 0.5px solid #000;
.tip {
font-size: 12px;
color: #000;
position: relative;
display: inline-block;
margin-bottom: 20px;
&::after {
content: '';
position: absolute;
left: 50%;
bottom: -5px;
width: 9px;
height: 1px;
background-color: #000;
transform: translateX(-50%);
}
}
& :deep(.el-upload--picture-card) {
--el-upload-picture-card-size: 89px;
}
& :deep(.el-upload-list) {
--el-upload-list-picture-card-size: 89px;
}
& :deep(.el-upload-list__item) {
border-radius: 0;
border: 0.5px solid #000;
}
.trigger-icon {
width: 89px;
height: 89px;
}
.thumbnail {
width: 89px;
height: 89px;
object-fit: cover;
}
:deep(.el-input__wrapper) {
border-radius: 0 !important;
box-shadow: none !important;
// background-color: red;
// border-width: 0.5px;
border: none;
outline: none;
}
.prompt {
font-size: 12px;
color: #000;
}
.link-input {
max-width: 580px;
width: 100%;
border: 0.5px solid #000000;
margin-top: 10px;
.grabButton {
cursor: pointer;
color: #000000;
font-size: 12px;
}
}
.grab-img-list {
margin-top: 10px;
height: auto;
.grap-image {
position: relative;
border: 0.5px solid #000;
.cover-icon {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
justify-content: center;
align-items: flex-end;
opacity: 1;
transition: opacity 0.3s ease;
display: none;
.icon-item {
margin-bottom: 10px;
}
&.active {
display: flex;
}
}
&:hover .cover-icon {
opacity: 1;
display: flex;
}
}
}
.upload-btn {
text-align: center;
& :deep(.el-checkbox__label) {
color: #87909e;
font-size: 12px;
font-weight: normal;
padding-left: 4px;
}
& :deep(.el-checkbox__inner) {
border-color: #87909e;
background-color: #87909e;
border-radius: 0;
}
}
}
</style>