Files
property-manager-side/wuye-manager-uniapp/components/filePicker/filePicker.vue
2026-05-28 11:52:50 +08:00

268 lines
6.3 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>
<view class="simple-uploader">
<!-- 图片列表 -->
<view class="image-list">
<!-- 已选图片 -->
<view v-for="(img, index) in innerList" :key="index" class="image-item">
<image :src="img.url || img" class="img" mode="aspectFill" @tap="preview(index)"></image>
<!-- 删除按钮 -->
<view class="delete-btn" @tap.stop="remove(index)">
<uni-icons type="closeempty" size="16" color="#fff"></uni-icons>
</view>
<!-- 上传状态 -->
<view v-if="img.uploading" class="uploading-mask">
<text>上传中...</text>
</view>
</view>
<!-- 添加按钮 -->
<view class="add-btn" @tap="choose">
<uni-icons color="#a6a6a6" type="plusempty" size="25"></uni-icons>
</view>
</view>
</view>
</template>
<script setup>
import { ref, watch } from 'vue'
import baseUrl from '@/api/BaseUrl.js'
// ======================
// Props(可配置)
// ======================
const props = defineProps({
// 双向绑定的图片列表
modelValue: {
type: Array,
default: () => []
},
/** 多选 最多上传数量 默认9*/
limit: {
type: Number,
default: 9
},
// 选图来源:album(相册)/ camera(拍照)
sourceType: {
type: Array,
default: () => ['album', 'camera']
},
// 图片大小:original(原图)/ compressed(压缩图)
sizeType: {
type: Array,
default: () => ['compressed']
},
// 后端上传接口地址(必填)
uploadUrl: {
type: String,
default: ''
},
// 后端接收的文件字段名
fileFieldName: {
type: String,
default: 'file'
},
// ✅ 新增:上传时携带的额外参数
extraData: {
type: Object,
default: () => ({})
}
})
// ======================
// Emits
// ======================
const emit = defineEmits(['update:modelValue', 'success', 'fail', 'remove'])
// ======================
// 内部数据
// ======================
const innerList = ref([])
// 监听外部数据变化
watch(
() => props.modelValue,
newVal => {
innerList.value = newVal.map(item => {
if (typeof item === 'string') {
return { url: item }
}
return item
})
},
{ immediate: true, deep: true }
)
// ======================
// 核心方法
// ======================
// 1. 选择图片
function choose() {
uni.chooseImage({
count: props.limit,
sourceType: props.sourceType,
sizeType: props.sizeType,
success: async res => {
const tempPaths = res.tempFilePaths
// 如果没传上传接口,只返回临时路径
if (!props.uploadUrl) {
const newItems = tempPaths.map(path => ({ url: path }))
innerList.value = [...innerList.value, ...newItems]
emit(
'update:modelValue',
innerList.value.map(i => i.url)
)
emit('success', { tempFilePaths: tempPaths })
return
}
// 有上传接口,自动上传
for (const path of tempPaths) {
await uploadSingle(path)
}
},
fail: err => {
console.log('选图失败', err)
emit('fail', err)
}
})
}
// 2. 上传单张图片
async function uploadSingle(filePath) {
// 先添加到列表,标记为上传中
const tempItem = { url: filePath, uploading: true }
innerList.value = [...innerList.value, tempItem]
return new Promise((resolve, reject) => {
uni.uploadFile({
url: `${baseUrl}${props.uploadUrl}`,
filePath: filePath,
name: props.fileFieldName,
header: {
Authorization: 'Bearer ' + (uni.getStorageSync('token') || ''),
clientid: 'butler'
},
// ✅ 新增:携带额外参数
formData: props.extraData,
success: res => {
try {
const data = JSON.parse(res.data)
// 假设后端返回格式:{ code: 200, url: "https://xxx.com/xxx.png" }
// 如果你的后端返回格式不同,请在这里修改
if (data.code === 200 || data.url) {
const finalUrl = data.url || data.data?.url
// 更新列表,替换为网络 URL
const index = innerList.value.findIndex(item => item.url === filePath)
if (index !== -1) {
innerList.value[index] = { url: finalUrl, uploading: false }
}
emit(
'update:modelValue',
innerList.value.map(i => i.url)
)
emit('success', { url: finalUrl, tempPath: filePath })
resolve(finalUrl)
} else {
throw new Error(data.msg || '上传失败')
}
} catch (e) {
console.error('解析上传结果失败', e)
remove(innerList.value.findIndex(item => item.url === filePath))
emit('fail', e)
reject(e)
}
},
fail: err => {
console.error('上传失败', err)
remove(innerList.value.findIndex(item => item.url === filePath))
emit('fail', err)
reject(err)
}
})
})
}
// 3. 删除图片
function remove(index) {
const removed = innerList.value.splice(index, 1)[0]
emit(
'update:modelValue',
innerList.value.map(i => i.url)
)
emit('remove', { index, url: removed.url })
}
// 4. 预览图片
function preview(index) {
const urls = innerList.value.map(i => i.url)
uni.previewImage({
urls: urls,
current: index
})
}
</script>
<style scoped>
.simple-uploader {
width: 100%;
}
.image-list {
display: grid;
grid-template-columns: repeat(3, 1fr);
}
.image-list > .image-item:not(:last-child) {
margin-right: 20rpx;
}
.image-item {
position: relative;
width: 160rpx;
height: 160rpx;
}
.img {
width: 100%;
height: 100%;
border-radius: 8rpx;
}
.delete-btn {
position: absolute;
top: 0rpx;
right: 0rpx;
width: 32rpx;
height: 32rpx;
background: rgba(0, 0, 0, 0.6);
display: flex;
align-items: center;
justify-content: center;
z-index: 10;
}
.uploading-mask {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
border-radius: 8rpx;
display: flex;
align-items: center;
justify-content: center;
color: #fff;
font-size: 24rpx;
}
.add-btn {
width: 160rpx;
height: 160rpx;
background-color: #f3f3f3;
display: flex;
align-items: center;
justify-content: center;
}
</style>