hn-hlw-app/pages/consult/components/medication-proof-upload.vue
2026-07-27 11:26:39 +08:00

392 lines
9.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>
<uni-popup ref="popup" type="center" :mask-click="false">
<view class="popup-container" :class="{ 'popup-container--elder': elderMode }">
<view class="popup-header" :class="{ 'popup-header--elder': elderMode }">
<view class="popup-title" :class="{ 'popup-title--elder': elderMode }">上传用药证明</view>
<uni-icons type="close" :size="elderMode ? 32 : 24" color="#999" @click="close"></uni-icons>
</view>
<view class="popup-content" :class="{ 'popup-content--elder': elderMode }">
<scroll-view scroll-y="true" style="max-height: 50vh;">
<view class="tip-text" :class="{ 'tip-text--elder': elderMode }">
根据用药安全规范您购买的以下药品需要上传就诊人的用药证明
</view>
<view v-for="drug in needProofMedicines" :key="drug._id" class="drug-item"
:class="{ 'drug-item--elder': elderMode }">
<view class="drug-info" :class="{ 'drug-info--elder': elderMode }">
<view class="drug-name" :class="{ 'drug-name--elder': elderMode }">{{ drug.name }}</view>
<view class="drug-desc" :class="{ 'drug-desc--elder': elderMode }">{{ drug.medication_proof_desc }}</view>
</view>
<view class="upload-section" :class="{ 'upload-section--elder': elderMode }">
<view v-if="drugProofMap[drug._id]" class="upload-grid" :class="{ 'upload-grid--elder': elderMode }">
<view v-for="(item, index) in drugProofMap[drug._id]" :key="item.key" class="image-item"
:class="{ 'image-item--elder': elderMode }" @click="previewImage(drugProofMap[drug._id], index)">
<image :src="item.tempPath || item.url" mode="aspectFill" class="uploaded-image"></image>
<view class="delete-btn" @click.stop="removeImage(drug._id, index)">
<uni-icons type="close" size="16" color="#fff"></uni-icons>
</view>
</view>
<view v-if="drugProofMap[drug._id] && drugProofMap[drug._id].length < 3" class="upload-btn"
:class="{ 'upload-btn--elder': elderMode }" @click="chooseImage(drug._id)">
<uni-icons type="plus" :size="elderMode ? 48 : 36" color="#999"></uni-icons>
</view>
</view>
<view class="upload-tip" :class="{ 'upload-tip--elder': elderMode }">
最多上传3张{{ drugProofMap[drug._id] ? drugProofMap[drug._id].length : 0 }}/3
</view>
</view>
</view>
</scroll-view>
</view>
<view class="popup-footer" :class="{ 'popup-footer--elder': elderMode }">
<view class="btn btn-cancel" :class="{ 'btn--elder': elderMode }" @click="close">取消</view>
<view class="btn btn-save" :class="{ 'btn--elder': elderMode }" @click="save">保存</view>
</view>
</view>
</uni-popup>
</template>
<script setup>
import { ref, computed, watch } from 'vue'
import { uploadUrl, getFullPath } from '@/utils/http'
import { toast, loading, hideLoading } from '@/utils/widget'
const emits = defineEmits(['close', 'save'])
const props = defineProps({
visible: { type: Boolean, default: false },
drugs: { type: Array, default: () => [] },
elderMode: { type: Boolean, default: false },
})
const popup = ref()
const drugProofMap = ref({})
const uploading = ref(false)
const needProofMedicines = computed(() => props.drugs.filter(drug => drug.medication_proof_desc))
function chooseImage(drugId) {
if (uploading.value || drugProofMap.value[drugId].length >= 3) return
uni.chooseImage({
count: 3 - drugProofMap.value[drugId].length,
sizeType: ['compressed'],
sourceType: ['album', 'camera'],
success: (res) => {
drugProofMap.value[drugId].push(...res.tempFilePaths.map((tempPath, idx) => ({
key: `${drugId}-${Date.now()}_${idx}`,
url: '',
drugId,
tempPath,
})))
toast(`新增${res.tempFilePaths.length}张图片`)
}
})
}
function close() {
emits('close')
}
function initProofImages() {
drugProofMap.value = {};
needProofMedicines.value.forEach(drug => {
drugProofMap.value[drug._id] = Array.isArray(drug.proofImages) ? drug.proofImages.map((url, idx) => ({
key: `${drug._id}-${idx}`,
url,
drugId: drug._id,
tempPath: '',
})) : []
})
}
function previewImage(images, current) {
uni.previewImage({
urls: images.map(item => item.tempPath || item.url),
current: current
})
}
function removeImage(drugId, index) {
drugProofMap.value[drugId].splice(index, 1)
}
function upload(path) {
return new Promise((resolve) => {
uni.uploadFile({
url: uploadUrl,
filePath: path,
name: 'file',
fileType: 'image',
success: (res) => {
try {
const url = JSON.parse(res.data).filePath
const fullUrl = url ? getFullPath(url) : ''
console.log('解析后的完整URL:', fullUrl)
resolve(fullUrl)
} catch (e) {
resolve('')
}
},
fail: (err) => {
console.error('上传失败:', err)
resolve('')
}
})
})
}
async function save() {
if (uploading.value) {
return toast('正在上传图片,请稍候');
}
for (let med of needProofMedicines.value) {
const images = drugProofMap.value[med._id] || [];
if (images.length === 0) {
return toast(`请上传药品【${med.name}】的用药证明`);
}
}
const uploadQueue = Object.values(drugProofMap.value).reduce((list, images) => {
const items = images.filter(i => !i.url);
return [...list, ...items]
}, [])
if (uploadQueue.length) {
let failCount = 0;
loading('上传图片中...')
uploading.value = true;
for (let item of uploadQueue) {
console.log('上传队列: ', item, item.tempPath)
const url = await upload(item.tempPath);
if (url) {
const idx = drugProofMap.value[item.drugId].findIndex(i => i.key === item.key);
if (idx !== -1) {
drugProofMap.value[item.drugId][idx].url = url;
}
} else {
failCount++;
}
}
uploading.value = false;
hideLoading();
if (failCount) {
return toast(`${failCount}张图片上传失败,请重新上传`);
}
}
emits('save', { ...drugProofMap.value })
close()
}
watch(() => props.visible, (val) => {
if (val) {
popup.value?.open()
initProofImages()
} else {
popup.value?.close()
}
})
</script>
<style lang="scss" scoped>
.popup-container {
width: 690rpx;
background: white;
border-radius: 24rpx;
overflow: hidden;
}
.popup-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 24rpx 30rpx;
border-bottom: 1px solid #f0f0f0;
flex-shrink: 0;
}
.popup-title {
font-size: 36rpx;
font-weight: bold;
color: #333;
}
.popup-content {
padding: 30rpx;
}
.tip-text {
font-size: 28rpx;
color: #666;
line-height: 40rpx;
margin-bottom: 24rpx;
}
.drug-item {
margin-bottom: 24rpx;
padding-bottom: 24rpx;
border-bottom: 1px solid #f0f0f0;
&:last-child {
border-bottom: none;
margin-bottom: 0;
}
}
.drug-info {
margin-bottom: 24rpx;
}
.drug-name {
font-size: 32rpx;
font-weight: bold;
color: #333;
margin-bottom: 8rpx;
}
.drug-desc {
font-size: 26rpx;
color: #ff4757;
line-height: 36rpx;
}
.upload-grid {
display: flex;
flex-wrap: wrap;
gap: 16rpx;
}
.image-item {
position: relative;
width: 160rpx;
height: 160rpx;
border-radius: 12rpx;
overflow: hidden;
}
.uploaded-image {
width: 100%;
height: 100%;
}
.delete-btn {
position: absolute;
top: 8rpx;
right: 8rpx;
width: 32rpx;
height: 32rpx;
background: rgba(0, 0, 0, 0.6);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
}
.upload-btn {
width: 160rpx;
height: 160rpx;
border: 2rpx dashed #ddd;
border-radius: 12rpx;
display: flex;
align-items: center;
justify-content: center;
background: #fafafa;
}
.upload-tip {
font-size: 24rpx;
color: #999;
margin-top: 16rpx;
}
.popup-footer {
display: flex;
padding: 24rpx 30rpx;
border-top: 1px solid #f0f0f0;
background: #fff;
}
.btn {
flex: 1;
height: 80rpx;
border-radius: 16rpx;
display: flex;
align-items: center;
justify-content: center;
font-size: 32rpx;
font-weight: bold;
}
.btn-cancel {
background: #f5f5f5;
color: #666;
}
.btn-save {
background: $theme-brown-primary;
color: white;
}
.popup-title--elder {
font-size: 44rpx;
font-weight: bold;
}
.tip-text--elder {
font-size: 36rpx;
line-height: 50rpx;
font-weight: 500;
}
.drug-info--elder {
margin-bottom: 24rpx;
}
.drug-name--elder {
font-size: 40rpx;
font-weight: bold;
margin-bottom: 12rpx;
}
.drug-desc--elder {
font-size: 32rpx;
line-height: 44rpx;
font-weight: 500;
}
.upload-grid--elder {
gap: 20rpx;
}
.image-item--elder {
width: 180rpx;
height: 180rpx;
border-radius: 16rpx;
}
.upload-btn--elder {
width: 180rpx;
height: 180rpx;
border: 3rpx dashed #ddd;
border-radius: 16rpx;
}
.upload-tip--elder {
font-size: 28rpx;
margin-top: 20rpx;
font-weight: 500;
}
.popup-footer--elder {
padding: 40rpx 48rpx;
gap: 32rpx;
border-top: 3rpx solid #f0f0f0;
flex-shrink: 0;
}
.btn--elder {
height: 96rpx;
border-radius: 20rpx;
font-size: 40rpx;
font-weight: bold;
}
</style>