| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133 |
- <template>
- <view class="img-grid">
- <text v-if="label" class="img-grid__label">{{ label }}</text>
- <view class="img-grid__list">
- <view v-for="(url, index) in modelValue" :key="index" class="img-grid__item">
- <image-preview class="img-grid__pic" :src="url" preview />
- <view class="img-grid__del" @click="removeAt(index)">×</view>
- </view>
- <view
- v-if="modelValue.length < max"
- class="img-grid__add"
- @click="chooseImages"
- >
- <u-icon name="camera-fill" color="#9a938c" size="36" />
- <text class="img-grid__tip">{{ uploading ? '上传中' : '添加图片' }}</text>
- </view>
- </view>
- </view>
- </template>
- <script setup>
- import { ref } from 'vue'
- import { uploadFile } from '@/utils/upload'
- const props = defineProps({
- modelValue: {
- type: Array,
- default: () => []
- },
- label: {
- type: String,
- default: ''
- },
- max: {
- type: Number,
- default: 9
- }
- })
- const emit = defineEmits(['update:modelValue'])
- const uploading = ref(false)
- function removeAt(index) {
- const next = [...props.modelValue]
- next.splice(index, 1)
- emit('update:modelValue', next)
- }
- function chooseImages() {
- if (uploading.value) return
- const left = props.max - props.modelValue.length
- if (left <= 0) return
- uni.chooseImage({
- count: left,
- sizeType: ['compressed'],
- success: async (res) => {
- const paths = res.tempFilePaths || []
- if (!paths.length) return
- uploading.value = true
- const uploaded = [...props.modelValue]
- try {
- for (const path of paths) {
- if (uploaded.length >= props.max) break
- const url = await uploadFile(path)
- uploaded.push(url)
- }
- emit('update:modelValue', uploaded)
- } catch (e) {
- uni.showToast({ title: (e && e.message) || '上传失败', icon: 'none' })
- } finally {
- uploading.value = false
- }
- }
- })
- }
- </script>
- <style lang="scss" scoped>
- .img-grid {
- padding: 16rpx 0;
- }
- .img-grid__label {
- display: block;
- margin-bottom: 12rpx;
- font-size: 28rpx;
- color: #333;
- }
- .img-grid__list {
- display: flex;
- flex-wrap: wrap;
- gap: 16rpx;
- }
- .img-grid__item,
- .img-grid__add {
- position: relative;
- width: 160rpx;
- height: 160rpx;
- border-radius: 12rpx;
- overflow: hidden;
- }
- .img-grid__pic {
- width: 100%;
- height: 100%;
- background: #eee;
- }
- .img-grid__del {
- position: absolute;
- top: 0;
- right: 0;
- width: 40rpx;
- height: 40rpx;
- line-height: 36rpx;
- text-align: center;
- background: rgba(0, 0, 0, 0.5);
- color: #fff;
- font-size: 28rpx;
- }
- .img-grid__add {
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
- gap: 8rpx;
- background: #f5f2ef;
- border: 1rpx dashed #ddd;
- }
- .img-grid__tip {
- font-size: 22rpx;
- color: #9a938c;
- }
- </style>
|