feat: 图片拖拽问题处理,VueDraggable不支持jsx写法

This commit is contained in:
rd
2025-08-12 13:53:54 +08:00
parent a4cf89cd5a
commit 7f77b663f5
8 changed files with 237 additions and 119 deletions

View File

@ -220,9 +220,9 @@ export default {
</div> </div>
</header> </header>
{loading.value ? ( {loading.value ? (
<Spin spinning={loading.value} class="h-500px w-full flex justify-center items-center" size={60} /> <Spin spinning={loading.value} class="flex-1 w-full flex justify-center items-center" size={60} />
) : ( ) : (
<div class={`page-wrap relative ${isExpand.value ? 'expand' : ''}`}> <section class={`page-wrap relative ${isExpand.value ? 'expand' : ''}`}>
<div class="fold-box cursor-pointer" onClick={() => (isExpand.value = true)}> <div class="fold-box cursor-pointer" onClick={() => (isExpand.value = true)}>
<icon-menu-fold size={20} class="color-#55585F hover:color-#6D4CFE" /> <icon-menu-fold size={20} class="color-#55585F hover:color-#6D4CFE" />
</div> </div>
@ -261,7 +261,7 @@ export default {
onDeleteComment={onDeleteComment} onDeleteComment={onDeleteComment}
/> />
)} )}
</div> </section>
)} )}
</div> </div>
); );

View File

@ -34,6 +34,7 @@
flex: 1; flex: 1;
display: flex; display: flex;
justify-content: center; justify-content: center;
background: #fff;
.explore-detail-wrap { .explore-detail-wrap {
min-height: 500px; min-height: 500px;
width: 684px; width: 684px;

View File

@ -50,10 +50,10 @@ export default {
</div> </div>
</div> </div>
</header> </header>
{loading.value ? ( <section class="page-wrapper flex justify-center">
<Spin spinning={loading.value} class="h-500px w-full flex justify-center items-center" size={60} /> {loading.value ? (
) : ( <Spin spinning={loading.value} class="w-full flex justify-center items-center" size={60} />
<section class="page-wrapper flex justify-center"> ) : (
<div class="explore-container"> <div class="explore-container">
<div class="explore-list-wrap pt-24px pb-28px"> <div class="explore-list-wrap pt-24px pb-28px">
<TextOverTips context={`${works.value[0]?.title}${works.value.length}个文件`} class="mb-8px" /> <TextOverTips context={`${works.value[0]?.title}${works.value.length}个文件`} class="mb-8px" />
@ -99,8 +99,8 @@ export default {
</div> </div>
</div> </div>
</div> </div>
</section> )}
)} </section>
</div> </div>
); );
}; };

View File

@ -26,6 +26,7 @@
} }
} }
.page-wrapper { .page-wrapper {
background: #fff;
min-height: calc(100vh - $navbar-height); min-height: calc(100vh - $navbar-height);
.explore-container { .explore-container {
width: 1200px; width: 1200px;

View File

@ -0,0 +1,99 @@
<template>
<a-form-item field="files">
<template #label>
<div class="flex items-center">
<span class="cts !color-#211F24 mr-4px">图片</span>
<span class="cts mr-8px !color-#939499">{{ `(${files.length ?? 0}/18)` }}</span>
<span class="cts !color-#939499">第一张为首图支持拖拽排序</span>
</div>
</template>
<div class="flex items-center">
<VueDraggable v-model="files" class="grid grid-cols-7 gap-y-8px" @end="handleChange" draggable=".group">
<div
v-for="(file, index) in files"
:key="file.url"
class="group relative cursor-move overflow-visible py-8px pr-8px"
>
<div class="group-container relative rounded-8px w-100px h-100px">
<img :src="file.url" class="object-cover w-full h-full border-1px border-#E6E6E8 rounded-8px" />
<icon-close-circle-fill
:size="16"
class="absolute top--8px right--8px cursor-pointer hidden color-#939499 hidden group-hover:block z-50"
@click="() => handleDeleteFile(index)"
/>
</div>
</div>
<a-upload
v-if="files.length < 18"
ref="uploadRef"
action="/"
draggable
:custom-request="(option) => emit('upload', option)"
accept=".jpg,.jpeg,.png,.gif,.webp"
:show-file-list="false"
multiple
class="!flex !items-center"
:limit="18 - files.length"
>
<template #upload-button>
<div class="upload-box">
<icon-plus size="14" class="mb-16px color-#3C4043" />
<span class="cts !color-#211F24">上传图片</span>
</div>
</template>
</a-upload>
</VueDraggable>
</div>
</a-form-item>
</template>
<script setup>
import { VueDraggable } from 'vue-draggable-plus';
const props = defineProps({
files: {
type: Array,
default: () => [],
},
});
const files = ref([]);
const uploadRef = ref(null);
const emit = defineEmits(['change', 'delete', 'upload']);
const handleChange = () => {
emit('change', files.value);
};
const handleDeleteFile = (index) => {
emit('delete', index);
};
watch(
() => props.files,
(newVal) => {
files.value = newVal;
},
{ immediate: true, deep: true },
);
</script>
<style scoped lang="scss">
.upload-box {
display: flex;
width: 100px;
height: 100px;
cursor: pointer;
transition: all 0.3s ease;
flex-direction: column;
justify-content: center;
align-items: center;
border-radius: 8px;
border: 1px dashed var(--Border-1, #d7d7d9);
background: var(--BG-200, #f2f3f5);
&:hover {
background: var(--Primary-1, #e6e6e8);
}
}
</style>

View File

@ -4,6 +4,7 @@ import { Form, FormItem, Input, Textarea, Upload, Message as AMessage, Button }
import CommonSelect from '@/components/common-select'; import CommonSelect from '@/components/common-select';
import { VueDraggable } from 'vue-draggable-plus'; import { VueDraggable } from 'vue-draggable-plus';
import TextOverTips from '@/components/text-over-tips'; import TextOverTips from '@/components/text-over-tips';
import ImgBox from './img-box';
import { formatFileSize, getVideoInfo, formatDuration, formatUploadSpeed } from '@/utils/tools'; import { formatFileSize, getVideoInfo, formatDuration, formatUploadSpeed } from '@/utils/tools';
import { getProjectList } from '@/api/all/propertyMarketing'; import { getProjectList } from '@/api/all/propertyMarketing';
@ -293,60 +294,9 @@ export default {
); );
}; };
const renderImage = () => { const handleImagesChange = (files) => {
return ( formData.value.files = cloneDeep(files);
<FormItem onChange();
field="files"
v-slots={{
label: () => (
<div class="flex items-center">
<span class="cts !color-#211F24 mr-4px">图片</span>
<span class="cts mr-8px !color-#939499">{`(${formData.value.files?.length ?? 0}/18)`}</span>
<span class="cts !color-#939499">第一张为首图支持拖拽排序</span>
</div>
),
}}
>
<div>
{/* 已上传的图片列表 */}
<VueDraggable v-model={formData.value.files} class="grid grid-cols-7 gap-y-8px">
{formData.value.files?.map((file, index) => (
<div key={index} class="group relative cursor-move overflow-visible py-8px pr-8px">
<div class="group-container relative rounded-8px w-100px h-100px">
<img src={file.url} class=" object-cover w-full h-full border-1px border-#E6E6E8 rounded-8px" />
<icon-close-circle-fill
size={16}
class="absolute top--8px right--8px cursor-pointer hidden color-#939499 hidden group-hover:block z-50"
onClick={() => handleDeleteFile(index)}
/>
</div>
</div>
))}
</VueDraggable>
{formData.value.files?.length < 18 && (
<Upload
ref={uploadRef}
action="/"
draggable
custom-request={uploadImage}
accept=".jpg,.jpeg,.png,.gif,.webp"
show-file-list={false}
multiple
limit={18 - formData.value.files?.length}
>
{{
'upload-button': () => (
<div class="upload-box">
<icon-plus size="14" class="mb-16px color-#3C4043" />
<span class="cts !color-#211F24">上传图片</span>
</div>
),
}}
</Upload>
)}
</div>
</FormItem>
);
}; };
// 暴露方法 // 暴露方法
@ -392,7 +342,16 @@ export default {
auto-size={{ minRows: 7, maxRows: 12 }} auto-size={{ minRows: 7, maxRows: 12 }}
/> />
</FormItem> </FormItem>
{isVideo.value ? renderVideo() : renderImage()} {isVideo.value ? (
renderVideo()
) : (
<ImgBox
files={formData.value.files}
onChange={handleImagesChange}
onDelete={handleDeleteFile}
onUpload={uploadImage}
/>
)}
{/* <FormItem label="所属项目" field="project_ids"> {/* <FormItem label="所属项目" field="project_ids">
<CommonSelect <CommonSelect

View File

@ -0,0 +1,99 @@
<template>
<a-form-item field="files">
<template #label>
<div class="flex items-center">
<span class="cts !color-#211F24 mr-4px">图片</span>
<span class="cts mr-8px !color-#939499">{{ `(${files.length ?? 0}/18)` }}</span>
<span class="cts !color-#939499">第一张为首图支持拖拽排序</span>
</div>
</template>
<div class="flex items-center">
<VueDraggable v-model="files" class="grid grid-cols-7 gap-y-8px" @end="handleChange" draggable=".group">
<div
v-for="(file, index) in files"
:key="file.url"
class="group relative cursor-move overflow-visible py-8px pr-8px"
>
<div class="group-container relative rounded-8px w-100px h-100px">
<img :src="file.url" class="object-cover w-full h-full border-1px border-#E6E6E8 rounded-8px" />
<icon-close-circle-fill
:size="16"
class="absolute top--8px right--8px cursor-pointer hidden color-#939499 hidden group-hover:block z-50"
@click="() => handleDeleteFile(index)"
/>
</div>
</div>
<a-upload
v-if="files.length < 18"
ref="uploadRef"
action="/"
draggable
:custom-request="(option) => emit('upload', option)"
accept=".jpg,.jpeg,.png,.gif,.webp"
:show-file-list="false"
multiple
class="!flex !items-center"
:limit="18 - files.length"
>
<template #upload-button>
<div class="upload-box">
<icon-plus size="14" class="mb-16px color-#3C4043" />
<span class="cts !color-#211F24">上传图片</span>
</div>
</template>
</a-upload>
</VueDraggable>
</div>
</a-form-item>
</template>
<script setup>
import { VueDraggable } from 'vue-draggable-plus';
const props = defineProps({
files: {
type: Array,
default: () => [],
},
});
const files = ref([]);
const uploadRef = ref(null);
const emit = defineEmits(['change', 'delete', 'upload']);
const handleChange = () => {
emit('change', files.value);
};
const handleDeleteFile = (index) => {
emit('delete', index);
};
watch(
() => props.files,
(newVal) => {
files.value = newVal;
},
{ immediate: true, deep: true },
);
</script>
<style scoped lang="scss">
.upload-box {
display: flex;
width: 100px;
height: 100px;
cursor: pointer;
transition: all 0.3s ease;
flex-direction: column;
justify-content: center;
align-items: center;
border-radius: 8px;
border: 1px dashed var(--Border-1, #d7d7d9);
background: var(--BG-200, #f2f3f5);
&:hover {
background: var(--Primary-1, #e6e6e8);
}
}
</style>

View File

@ -4,6 +4,7 @@ import { Form, FormItem, Input, Textarea, Upload, Message as AMessage, Button }
import CommonSelect from '@/components/common-select'; import CommonSelect from '@/components/common-select';
import { VueDraggable } from 'vue-draggable-plus'; import { VueDraggable } from 'vue-draggable-plus';
import TextOverTips from '@/components/text-over-tips'; import TextOverTips from '@/components/text-over-tips';
import ImgBox from './img-box';
import { formatFileSize, getVideoInfo, formatDuration, formatUploadSpeed } from '@/utils/tools'; import { formatFileSize, getVideoInfo, formatDuration, formatUploadSpeed } from '@/utils/tools';
import { getProjectList } from '@/api/all/propertyMarketing'; import { getProjectList } from '@/api/all/propertyMarketing';
@ -293,60 +294,9 @@ export default {
); );
}; };
const renderImage = () => { const handleImagesChange = (files) => {
return ( formData.value.files = cloneDeep(files);
<FormItem onChange();
field="files"
v-slots={{
label: () => (
<div class="flex items-center">
<span class="cts !color-#211F24 mr-4px">图片</span>
<span class="cts mr-8px !color-#939499">{`(${formData.value.files?.length ?? 0}/18)`}</span>
<span class="cts !color-#939499">第一张为首图支持拖拽排序</span>
</div>
),
}}
>
<div>
{/* 已上传的图片列表 */}
<VueDraggable v-model={formData.value.files} class="grid grid-cols-7 gap-y-8px">
{formData.value.files?.map((file, index) => (
<div key={index} class="group relative cursor-move overflow-visible py-8px pr-8px">
<div class="group-container relative rounded-8px w-100px h-100px">
<img src={file.url} class=" object-cover w-full h-full border-1px border-#E6E6E8 rounded-8px" />
<icon-close-circle-fill
size={16}
class="absolute top--8px right--8px cursor-pointer hidden color-#939499 hidden group-hover:block z-50"
onClick={() => handleDeleteFile(index)}
/>
</div>
</div>
))}
</VueDraggable>
{formData.value.files?.length < 18 && (
<Upload
ref={uploadRef}
action="/"
draggable
custom-request={uploadImage}
accept=".jpg,.jpeg,.png,.gif,.webp"
show-file-list={false}
multiple
limit={18 - formData.value.files?.length}
>
{{
'upload-button': () => (
<div class="upload-box">
<icon-plus size="14" class="mb-16px color-#3C4043" />
<span class="cts !color-#211F24">上传图片</span>
</div>
),
}}
</Upload>
)}
</div>
</FormItem>
);
}; };
// 暴露方法 // 暴露方法
@ -392,7 +342,16 @@ export default {
auto-size={{ minRows: 7, maxRows: 12 }} auto-size={{ minRows: 7, maxRows: 12 }}
/> />
</FormItem> </FormItem>
{isVideo.value ? renderVideo() : renderImage()} {isVideo.value ? (
renderVideo()
) : (
<ImgBox
files={formData.value.files}
onChange={handleImagesChange}
onDelete={handleDeleteFile}
onUpload={uploadImage}
/>
)}
{/* <FormItem label="所属项目" field="project_ids"> {/* <FormItem label="所属项目" field="project_ids">
<CommonSelect <CommonSelect