176 lines
3.1 KiB
Vue
176 lines
3.1 KiB
Vue
<template>
|
||
<view class="attachment-block">
|
||
<view class="form-item clickable-item" @click="handleChooseImages">
|
||
<view class="form-icon">{{ icon }}</view>
|
||
<text class="form-label">{{ title }}</text>
|
||
<text class="arrow">›</text>
|
||
</view>
|
||
|
||
<view class="images-preview" v-if="images.length">
|
||
<view
|
||
class="image-item"
|
||
v-for="(image, index) in images"
|
||
:key="image + index"
|
||
>
|
||
<image
|
||
:src="image"
|
||
mode="aspectFill"
|
||
class="preview-image"
|
||
@click="previewImage(index)"
|
||
/>
|
||
<view class="remove-btn" @click.stop="removeImage(index)">✕</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { computed } from 'vue';
|
||
import { chooseAndUploadImages } from '@/utils/qiniu.js';
|
||
|
||
const props = defineProps({
|
||
modelValue: {
|
||
type: Array,
|
||
default: () => []
|
||
},
|
||
maxCount: {
|
||
type: Number,
|
||
default: 9
|
||
},
|
||
title: {
|
||
type: String,
|
||
default: '添加照片'
|
||
},
|
||
icon: {
|
||
type: String,
|
||
default: '🏔️'
|
||
}
|
||
});
|
||
|
||
const emit = defineEmits(['update:modelValue', 'change']);
|
||
|
||
const images = computed({
|
||
get: () => props.modelValue,
|
||
set: (val) => {
|
||
emit('update:modelValue', val);
|
||
emit('change', val);
|
||
}
|
||
});
|
||
|
||
const handleChooseImages = async () => {
|
||
const remainingCount = props.maxCount - images.value.length;
|
||
if (remainingCount <= 0) {
|
||
uni.showToast({
|
||
title: `最多只能添加${props.maxCount}张图片`,
|
||
icon: 'none'
|
||
});
|
||
return;
|
||
}
|
||
|
||
try {
|
||
const urls = await chooseAndUploadImages({
|
||
count: remainingCount,
|
||
sizeType: ['original', 'compressed'],
|
||
sourceType: ['album', 'camera']
|
||
});
|
||
images.value = [...images.value, ...urls];
|
||
} catch (err) {
|
||
console.error('选择或上传图片失败:', err);
|
||
uni.showToast({
|
||
title: err?.message || '选择图片失败',
|
||
icon: 'none'
|
||
});
|
||
}
|
||
};
|
||
|
||
const previewImage = (index) => {
|
||
uni.previewImage({
|
||
urls: images.value,
|
||
current: index
|
||
});
|
||
};
|
||
|
||
const removeImage = (index) => {
|
||
const next = [...images.value];
|
||
next.splice(index, 1);
|
||
images.value = next;
|
||
};
|
||
</script>
|
||
|
||
<style scoped lang="scss">
|
||
.attachment-block {
|
||
margin-bottom: 12px;
|
||
}
|
||
|
||
.form-item {
|
||
display: flex;
|
||
align-items: center;
|
||
padding: 16px;
|
||
background-color: #fff;
|
||
border-radius: 8px;
|
||
gap: 12px;
|
||
}
|
||
|
||
.clickable-item {
|
||
cursor: pointer;
|
||
|
||
&:active {
|
||
background-color: #f5f5f5;
|
||
}
|
||
}
|
||
|
||
.form-icon {
|
||
font-size: 20px;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.form-label {
|
||
flex: 1;
|
||
font-size: 15px;
|
||
color: #333;
|
||
}
|
||
|
||
.arrow {
|
||
font-size: 20px;
|
||
color: #999;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.images-preview {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 8px;
|
||
margin-top: 12px;
|
||
}
|
||
|
||
.image-item {
|
||
position: relative;
|
||
width: 100px;
|
||
height: 100px;
|
||
border-radius: 8px;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.preview-image {
|
||
width: 100%;
|
||
height: 100%;
|
||
}
|
||
|
||
.remove-btn {
|
||
position: absolute;
|
||
top: 6px;
|
||
right: 6px;
|
||
width: 24px;
|
||
height: 24px;
|
||
background-color: rgba(0, 0, 0, 0.6);
|
||
color: #fff;
|
||
border-radius: 50%;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-size: 14px;
|
||
cursor: pointer;
|
||
}
|
||
</style>
|
||
|