<template>
|
<view class="attachment-upload-container">
|
<view class="detail-section">
|
<view class="section-title">
|
{{ title }}
|
<button class="upload-btn" @click="showUploadDialog" v-if="!readonly">上传附件</button>
|
</view>
|
<view v-if="attachmentList && attachmentList.length > 0">
|
<view class="attachment-item" v-for="(item, index) in attachmentList" :key="item.attachmentId">
|
<view class="attachment-thumbnail" @click="viewAttachment(item)">
|
<image :src="getImageUrl(item)" mode="aspectFill" class="thumbnail-image"></image>
|
</view>
|
<view class="attachment-info">
|
<view class="attachment-category">
|
<text class="category-tag">{{ getCategoryName(item.attachmentCategory) }}</text>
|
</view>
|
<view class="attachment-meta">
|
<text class="upload-time">{{ formatTime(item.uploadTime) }}</text>
|
</view>
|
</view>
|
<view class="attachment-actions">
|
<button class="action-btn view-btn" @click="viewAttachment(item)">查看</button>
|
<button class="action-btn delete-btn" @click="deleteAttachment(item.attachmentId, index)" v-if="!readonly">删除</button>
|
</view>
|
</view>
|
</view>
|
<view v-else class="no-attachment">
|
<text>暂无附件</text>
|
</view>
|
</view>
|
|
<!-- 附件上传对话框 -->
|
<uni-popup ref="uploadPopup" type="bottom">
|
<view class="upload-dialog">
|
<view class="dialog-header">
|
<text class="dialog-title">上传附件</text>
|
<uni-icons type="closeempty" size="24" @click="closeUploadDialog"></uni-icons>
|
</view>
|
<view class="dialog-content">
|
<view class="form-item">
|
<view class="form-label">附件分类</view>
|
<picker @change="onCategoryChange" :value="selectedCategoryIndex" :range="categoryList" range-key="label">
|
<view class="picker-value">
|
{{ categoryList[selectedCategoryIndex].label }}
|
<uni-icons type="arrowdown" size="16"></uni-icons>
|
</view>
|
</picker>
|
</view>
|
<view class="form-item">
|
<view class="form-label">选择图片</view>
|
<button class="choose-image-btn" @click="chooseImage">
|
<uni-icons type="image" size="20"></uni-icons>
|
<text>点击选择</text>
|
</button>
|
</view>
|
<view class="preview-area" v-if="tempImagePath">
|
<image :src="tempImagePath" mode="aspectFit" class="preview-image"></image>
|
</view>
|
</view>
|
<view class="dialog-footer">
|
<button class="cancel-btn" @click="closeUploadDialog">取消</button>
|
<button class="confirm-btn" @click="confirmUpload" :disabled="!tempImagePath">确定上传</button>
|
</view>
|
</view>
|
</uni-popup>
|
</view>
|
</template>
|
|
<script>
|
import { getAttachmentList, deleteAttachment } from '@/api/task'
|
import { formatDateTime } from '@/utils/common'
|
import { getToken } from '@/utils/auth'
|
import config from '@/config'
|
|
export default {
|
name: 'AttachmentUpload',
|
props: {
|
// 任务ID
|
taskId: {
|
type: [String, Number],
|
required: true
|
},
|
// 标题
|
title: {
|
type: String,
|
default: '任务附件'
|
},
|
// 是否自动加载附件列表
|
autoLoad: {
|
type: Boolean,
|
default: true
|
},
|
// 是否只读模式(禁止上传和删除)
|
readonly: {
|
type: Boolean,
|
default: false
|
}
|
},
|
data() {
|
return {
|
attachmentList: [],
|
categoryList: [
|
{ label: '知情同意书', value: '1' },
|
{ label: '病人资料', value: '2' },
|
{ label: '操作记录', value: '3' },
|
{ label: '出车前', value: '4' },
|
{ label: '出车后', value: '5' },
|
{ label: '系安全带', value: '6' }
|
],
|
selectedCategoryIndex: 0,
|
tempImagePath: null
|
}
|
},
|
mounted() {
|
// 自动加载附件列表
|
if (this.autoLoad && this.taskId) {
|
this.loadAttachmentList()
|
}
|
},
|
watch: {
|
// 监听taskId变化,重新加载附件列表
|
taskId(newVal) {
|
if (newVal && this.autoLoad) {
|
this.loadAttachmentList()
|
}
|
}
|
},
|
methods: {
|
// 加载附件列表
|
loadAttachmentList() {
|
if (!this.taskId) {
|
return
|
}
|
|
getAttachmentList(this.taskId).then(response => {
|
this.attachmentList = response.data || response || []
|
this.$emit('loaded', this.attachmentList)
|
}).catch(error => {
|
console.error('加载附件列表失败:', error)
|
this.$emit('error', error)
|
})
|
},
|
|
// 显示上传对话框
|
showUploadDialog() {
|
this.selectedCategoryIndex = 0
|
this.tempImagePath = null
|
this.$refs.uploadPopup.open()
|
},
|
|
// 关闭上传对话框
|
closeUploadDialog() {
|
this.$refs.uploadPopup.close()
|
},
|
|
// 分类选择变化
|
onCategoryChange(e) {
|
this.selectedCategoryIndex = e.detail.value
|
},
|
|
// 选择图片
|
chooseImage() {
|
const that = this
|
uni.chooseImage({
|
count: 1,
|
sizeType: ['compressed'],
|
sourceType: ['album', 'camera'],
|
success: function(res) {
|
that.tempImagePath = res.tempFilePaths[0]
|
},
|
fail: function(err) {
|
console.error('选择图片失败:', err)
|
that.$modal.showToast('选择图片失败')
|
}
|
})
|
},
|
|
// 确认上传
|
confirmUpload() {
|
if (!this.tempImagePath) {
|
this.$modal.showToast('请先选择图片')
|
return
|
}
|
|
const that = this
|
const category = this.categoryList[this.selectedCategoryIndex].value
|
|
// 统一直接上传到后端服务器
|
uni.showLoading({
|
title: '上传中...'
|
})
|
|
uni.uploadFile({
|
url: config.baseUrl + '/task/attachment/upload/' + that.taskId,
|
filePath: that.tempImagePath,
|
name: 'file',
|
formData: {
|
'category': category
|
},
|
header: {
|
'Authorization': 'Bearer ' + getToken()
|
},
|
success: function(uploadRes) {
|
uni.hideLoading()
|
|
if (uploadRes.statusCode === 200) {
|
const result = JSON.parse(uploadRes.data)
|
if (result.code === 200) {
|
that.$modal.showToast('上传成功')
|
that.closeUploadDialog()
|
that.loadAttachmentList()
|
that.$emit('uploaded', result)
|
} else {
|
that.$modal.showToast(result.msg || '上传失败')
|
that.$emit('error', result)
|
}
|
} else {
|
that.$modal.showToast('上传失败')
|
that.$emit('error', uploadRes)
|
}
|
},
|
fail: function(err) {
|
uni.hideLoading()
|
console.error('上传失败:', err)
|
that.$modal.showToast('上传失败:' + (err.errMsg || '请检查网络'))
|
that.$emit('error', err)
|
}
|
})
|
},
|
|
// 查看附件
|
viewAttachment(item) {
|
// 如果是图片,使用图片预览
|
const imageTypes = ['jpg', 'jpeg', 'png', 'gif', 'bmp']
|
const fileExt = item.fileName.split('.').pop().toLowerCase()
|
|
if (imageTypes.includes(fileExt)) {
|
// 优先使用fileUrl字段(后端拼接好的完整URL)
|
let imageUrl = item.fileUrl
|
// 如果没有fileUrl,则使用下载接口
|
if (!imageUrl) {
|
imageUrl = config.baseUrl + '/task/attachment/download/' + item.attachmentId
|
}
|
|
// 微信小程序中预览图片
|
// #ifdef MP-WEIXIN
|
// 微信小程序需要先下载到本地再预览
|
uni.showLoading({ title: '加载中...' })
|
uni.downloadFile({
|
url: imageUrl,
|
success: function(res) {
|
uni.hideLoading()
|
if (res.statusCode === 200) {
|
uni.previewImage({
|
urls: [res.tempFilePath],
|
current: res.tempFilePath
|
})
|
} else {
|
uni.showToast({ title: '加载图片失败', icon: 'none' })
|
}
|
},
|
fail: function() {
|
uni.hideLoading()
|
uni.showToast({ title: '下载失败', icon: 'none' })
|
}
|
})
|
// #endif
|
|
// 非微信小程序环境,直接预览
|
// #ifndef MP-WEIXIN
|
uni.previewImage({
|
urls: [imageUrl],
|
current: imageUrl
|
})
|
// #endif
|
} else {
|
this.$modal.showToast('仅支持预览图片')
|
}
|
|
this.$emit('view', item)
|
},
|
|
// 删除附件
|
deleteAttachment(attachmentId, index) {
|
const that = this
|
this.$modal.confirm('确定要删除该附件吗?').then(() => {
|
deleteAttachment(attachmentId).then(response => {
|
that.$modal.showToast('删除成功')
|
that.attachmentList.splice(index, 1)
|
that.$emit('deleted', attachmentId)
|
}).catch(error => {
|
console.error('删除附件失败:', error)
|
that.$modal.showToast('删除失败')
|
that.$emit('error', error)
|
})
|
}).catch(() => {})
|
},
|
|
// 获取分类名称
|
getCategoryName(category) {
|
console.log('category', category)
|
const item = this.categoryList.find(c => c.value === category)
|
return item ? item.label : '未分类'
|
},
|
|
// 格式化时间
|
formatTime(time) {
|
if (!time) return ''
|
return formatDateTime(time, 'YYYY-MM-DD HH:mm')
|
},
|
|
// 格式化文件大小
|
formatFileSize(size) {
|
if (!size) return '0B'
|
if (size < 1024) return size + 'B'
|
if (size < 1024 * 1024) return (size / 1024).toFixed(2) + 'KB'
|
return (size / 1024 / 1024).toFixed(2) + 'MB'
|
},
|
|
// 获取图片URL
|
getImageUrl(item) {
|
|
// 优先使用fileUrl字段(后端拼接好的完整URL)
|
if (item.fileUrl) {
|
return item.fileUrl
|
}
|
// 使用下载接口
|
if (item.attachmentId) {
|
return config.baseUrl + '/task/attachment/download/' + item.attachmentId
|
}
|
// 默认占位图
|
return '/static/images/default-image.png'
|
},
|
|
// 刷新附件列表(供外部调用)
|
refresh() {
|
this.loadAttachmentList()
|
}
|
}
|
}
|
</script>
|
|
<style lang="scss" scoped>
|
.attachment-upload-container {
|
.detail-section {
|
background-color: white;
|
border-radius: 15rpx;
|
padding: 30rpx;
|
margin-bottom: 20rpx;
|
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
|
|
.section-title {
|
font-size: 32rpx;
|
font-weight: bold;
|
margin-bottom: 20rpx;
|
color: #333;
|
border-bottom: 1rpx solid #f0f0f0;
|
padding-bottom: 10rpx;
|
display: flex;
|
justify-content: space-between;
|
align-items: center;
|
|
.upload-btn {
|
font-size: 24rpx;
|
padding: 8rpx 20rpx;
|
background-color: #007AFF;
|
color: white;
|
border-radius: 8rpx;
|
border: none;
|
}
|
}
|
|
.no-attachment {
|
text-align: center;
|
padding: 40rpx 0;
|
color: #999;
|
font-size: 28rpx;
|
}
|
|
.attachment-item {
|
display: flex;
|
align-items: center;
|
padding: 20rpx;
|
margin-bottom: 15rpx;
|
background-color: #f9f9f9;
|
border-radius: 10rpx;
|
|
&:last-child {
|
margin-bottom: 0;
|
}
|
|
.attachment-thumbnail {
|
width: 120rpx;
|
height: 120rpx;
|
border-radius: 8rpx;
|
overflow: hidden;
|
margin-right: 20rpx;
|
flex-shrink: 0;
|
background-color: #f0f0f0;
|
|
.thumbnail-image {
|
width: 100%;
|
height: 100%;
|
}
|
}
|
|
.attachment-info {
|
flex: 1;
|
margin-right: 20rpx;
|
|
.attachment-category {
|
margin-bottom: 8rpx;
|
|
.category-tag {
|
display: inline-block;
|
padding: 4rpx 12rpx;
|
background-color: #007AFF;
|
color: white;
|
font-size: 22rpx;
|
border-radius: 4rpx;
|
}
|
}
|
|
.attachment-meta {
|
font-size: 24rpx;
|
color: #999;
|
|
.upload-time {
|
margin-right: 20rpx;
|
}
|
}
|
}
|
|
.attachment-actions {
|
display: flex;
|
flex-direction: column;
|
gap: 10rpx;
|
|
.action-btn {
|
padding: 8rpx 20rpx;
|
font-size: 24rpx;
|
border-radius: 6rpx;
|
border: none;
|
|
&.view-btn {
|
background-color: #007AFF;
|
color: white;
|
}
|
|
&.delete-btn {
|
background-color: #ff3b30;
|
color: white;
|
}
|
}
|
}
|
}
|
}
|
|
.upload-dialog {
|
background-color: white;
|
border-radius: 20rpx 20rpx 0 0;
|
padding: 30rpx;
|
|
.dialog-header {
|
display: flex;
|
justify-content: space-between;
|
align-items: center;
|
margin-bottom: 30rpx;
|
|
.dialog-title {
|
font-size: 32rpx;
|
font-weight: bold;
|
color: #333;
|
}
|
}
|
|
.dialog-content {
|
.form-item {
|
margin-bottom: 30rpx;
|
|
.form-label {
|
font-size: 28rpx;
|
color: #333;
|
margin-bottom: 15rpx;
|
}
|
|
.picker-value {
|
display: flex;
|
justify-content: space-between;
|
align-items: center;
|
padding: 20rpx;
|
background-color: #f5f5f5;
|
border-radius: 10rpx;
|
font-size: 28rpx;
|
color: #333;
|
}
|
|
.choose-image-btn {
|
display: flex;
|
align-items: center;
|
justify-content: center;
|
padding: 30rpx;
|
background-color: #f5f5f5;
|
border-radius: 10rpx;
|
border: 2rpx dashed #ccc;
|
color: #666;
|
font-size: 28rpx;
|
|
text {
|
margin-left: 10rpx;
|
}
|
}
|
}
|
|
.preview-area {
|
margin-top: 20rpx;
|
|
.preview-image {
|
width: 100%;
|
height: 400rpx;
|
border-radius: 10rpx;
|
}
|
}
|
}
|
|
.dialog-footer {
|
display: flex;
|
gap: 20rpx;
|
margin-top: 30rpx;
|
|
button {
|
flex: 1;
|
height: 80rpx;
|
border-radius: 10rpx;
|
font-size: 30rpx;
|
border: none;
|
}
|
|
.cancel-btn {
|
background-color: #f5f5f5;
|
color: #666;
|
}
|
|
.confirm-btn {
|
background-color: #007AFF;
|
color: white;
|
|
&:disabled {
|
background-color: #ccc;
|
}
|
}
|
}
|
}
|
}
|
</style>
|