<template>
|
<view class="file-page">
|
<PageHeader title="附件管理" @back="goBack" />
|
|
<view class="file-list">
|
<view v-if="files.length > 0">
|
<view v-for="(f, idx) in files" :key="f.id || idx" class="file-item">
|
<view class="file-info">
|
<text class="file-name">{{ f.name }}</text>
|
</view>
|
<view class="file-actions">
|
<view class="btn-link" @click="previewFile(f)">预览</view>
|
<view class="btn-link danger" @click="confirmDelete(f)">删除</view>
|
</view>
|
</view>
|
</view>
|
<view v-else class="empty">暂无附件</view>
|
</view>
|
|
<view class="upload-bar">
|
<view class="btn-upload" @tap="chooseFile">上传附件</view>
|
</view>
|
</view>
|
</template>
|
|
<script setup>
|
import { ref } from 'vue'
|
import { onLoad, onShow } from '@dcloudio/uni-app'
|
import PageHeader from '@/components/PageHeader.vue'
|
import config from '@/config'
|
import { getToken } from '@/utils/auth'
|
import { qualityInspectFileAdd, qualityInspectFileDel, qualityInspectFileListPage } from '@/api/qualityManagement/qualityInspectFile.js'
|
|
const inspectId = ref('')
|
const files = ref([])
|
|
const getList = () => {
|
if (!inspectId.value) return
|
qualityInspectFileListPage({ inspectId: inspectId.value, current: 1, size: 200 })
|
.then(res => {
|
files.value = res?.data?.records || []
|
})
|
.catch(() => { files.value = [] })
|
}
|
|
const chooseFile = () => {
|
if (!inspectId.value) {
|
uni.showToast({ title: '缺少检验记录ID', icon: 'none' })
|
return
|
}
|
const pickByChooseFile = () => {
|
if (typeof uni.chooseFile !== 'function') return false
|
uni.chooseFile({
|
count: 1,
|
success: (res) => {
|
const file = res?.tempFiles?.[0]
|
const path = file?.path
|
const name = file?.name
|
if (!path) return
|
uploadOne(path, name)
|
},
|
fail: () => {
|
// chooseFile 在部分 App 机型/基座可能会失败,做降级
|
pickByChooseImage()
|
}
|
})
|
return true
|
}
|
|
const pickByChooseImage = () => {
|
if (typeof uni.chooseImage !== 'function') {
|
uni.showToast({ title: '当前版本不支持选择文件', icon: 'none' })
|
return
|
}
|
uni.chooseImage({
|
count: 1,
|
success: (res) => {
|
const path = res?.tempFilePaths?.[0]
|
if (!path) return
|
uploadOne(path, '图片附件')
|
},
|
fail: () => {
|
uni.showToast({ title: '选择失败', icon: 'none' })
|
}
|
})
|
}
|
|
// App/H5 统一走 chooseFile,失败时降级 chooseImage
|
const ok = pickByChooseFile()
|
if (!ok) pickByChooseImage()
|
}
|
|
const uploadOne = (filePath, originalName) => {
|
const token = getToken()
|
if (!token) {
|
uni.showToast({ title: '未登录', icon: 'none' })
|
return
|
}
|
uni.showLoading({ title: '上传中...', mask: true })
|
uni.uploadFile({
|
url: config.baseUrl + '/file/upload',
|
filePath,
|
name: 'file',
|
header: { Authorization: 'Bearer ' + token },
|
success: (res) => {
|
uni.hideLoading()
|
try {
|
const resp = JSON.parse(res.data || '{}')
|
if (resp.code !== 200) throw new Error('upload fail')
|
const fileRow = {
|
inspectId: inspectId.value,
|
name: resp.data?.originalName || originalName || '附件',
|
url: resp.data?.tempPath
|
}
|
qualityInspectFileAdd(fileRow).then(() => {
|
uni.showToast({ title: '上传成功', icon: 'success' })
|
getList()
|
})
|
} catch (e) {
|
uni.showToast({ title: '上传失败', icon: 'none' })
|
}
|
},
|
fail: () => {
|
uni.hideLoading()
|
uni.showToast({ title: '上传失败', icon: 'none' })
|
}
|
})
|
}
|
|
const previewFile = (f) => {
|
const rawUrl = f?.url
|
if (!rawUrl) return
|
|
const url = normalizeUrl(rawUrl)
|
const name = f?.name || '附件'
|
if (!url) return
|
|
// 图片优先用原生预览
|
if (isImageUrl(url) || isImageName(name)) {
|
uni.previewImage({ urls: [url] })
|
return
|
}
|
|
// 非图片:下载后打开
|
const token = getToken()
|
uni.showLoading({ title: '打开中...', mask: true })
|
uni.downloadFile({
|
url,
|
header: token ? { Authorization: 'Bearer ' + token } : undefined,
|
success: (res) => {
|
const filePath = res?.tempFilePath
|
if (!filePath) {
|
uni.hideLoading()
|
uni.showToast({ title: '打开失败', icon: 'none' })
|
return
|
}
|
uni.openDocument({
|
filePath,
|
showMenu: true,
|
success: () => {
|
uni.hideLoading()
|
},
|
fail: () => {
|
uni.hideLoading()
|
uni.showToast({ title: '打开失败', icon: 'none' })
|
}
|
})
|
},
|
fail: () => {
|
uni.hideLoading()
|
uni.showToast({ title: '下载失败', icon: 'none' })
|
}
|
})
|
}
|
|
const normalizeUrl = (u) => {
|
const s = String(u || '').trim()
|
if (!s) return ''
|
if (s.startsWith('http://') || s.startsWith('https://')) return s
|
if (s.startsWith('//')) return 'https:' + s
|
// 系统里附件预览/下载一般走 fileUrl
|
if (s.startsWith('/')) return `${config.fileUrl}${s}`
|
return `${config.fileUrl}/${s.replace(/^\//, '')}`
|
}
|
|
const isImageName = (name) => {
|
const n = String(name || '').toLowerCase()
|
return /\.(png|jpe?g|gif|bmp|webp|heic)$/.test(n)
|
}
|
|
const isImageUrl = (url) => {
|
const u = String(url || '').toLowerCase()
|
return /\.(png|jpe?g|gif|bmp|webp|heic)(\?|#|$)/.test(u)
|
}
|
|
const confirmDelete = (f) => {
|
if (!f?.id) return
|
uni.showModal({
|
title: '删除',
|
content: '确认删除该附件?',
|
success: (r) => {
|
if (!r.confirm) return
|
qualityInspectFileDel([f.id]).then(() => {
|
uni.showToast({ title: '删除成功', icon: 'success' })
|
getList()
|
})
|
}
|
})
|
}
|
|
onLoad((options) => {
|
inspectId.value = options?.id || ''
|
if (!inspectId.value) {
|
const cached = uni.getStorageSync('rawMaterialFilesCtx')
|
if (cached) {
|
try {
|
const payload = typeof cached === 'string' ? JSON.parse(cached) : cached
|
inspectId.value = payload?.id || ''
|
uni.removeStorageSync('rawMaterialFilesCtx')
|
} catch (e) {
|
uni.removeStorageSync('rawMaterialFilesCtx')
|
}
|
}
|
}
|
})
|
|
onShow(() => {
|
getList()
|
})
|
|
const goBack = () => uni.navigateBack()
|
</script>
|
|
<style lang="scss" scoped>
|
.file-page { min-height: 100vh; background: #f5f5f5; padding-bottom: 120rpx; }
|
.file-list { margin: 24rpx; background: #fff; border-radius: 16rpx; padding: 12rpx 24rpx; }
|
.file-item { padding: 20rpx 0; border-bottom: 1rpx solid #eee; display: flex; justify-content: space-between; align-items: center; gap: 16rpx; }
|
.file-item:last-child { border-bottom: 0; }
|
.file-info { flex: 1; min-width: 0; }
|
.file-name {
|
display: block;
|
width: 420rpx;
|
max-width: 100%;
|
font-size: 28rpx;
|
color: #333;
|
overflow: hidden;
|
text-overflow: ellipsis;
|
white-space: nowrap;
|
}
|
.file-actions { display: flex; gap: 20rpx; flex-shrink: 0; }
|
.btn-link { color: #2979ff; font-size: 26rpx; }
|
.btn-link.danger { color: #f56c6c; }
|
.empty { text-align: center; padding: 60rpx 0; color: #999; font-size: 28rpx; }
|
.upload-bar { position: fixed; left: 0; right: 0; bottom: 0; padding: 16rpx 24rpx calc(16rpx + env(safe-area-inset-bottom)); background: #fff; box-shadow: 0 -4rpx 16rpx rgba(0,0,0,0.04); z-index: 20; }
|
.btn-upload { height: 88rpx; border-radius: 999rpx; background: #2979ff; color: #fff; font-size: 30rpx; display: flex; align-items: center; justify-content: center; }
|
</style>
|