|
|
@@ -4,7 +4,8 @@
|
|
|
<view v-if="readonly" class="file-list readonly-list">
|
|
|
<view v-for="(file, index) in innerFileList" :key="index" class="file-item readonly-item"
|
|
|
@click="previewFile(file, index)">
|
|
|
- <image v-if="isImage(file)" :src="file.url || file.path" mode="aspectFill" class="file-thumb" :style="imgStyle" />
|
|
|
+ <image v-if="isImage(file)" :src="file.url || file.path" mode="aspectFill" class="file-thumb"
|
|
|
+ :style="imgStyle" />
|
|
|
<view v-else class="file-icon" :class="getFileClass(file.name)">
|
|
|
<text class="file-icon-text">{{ getFileIcon(file.name) }}</text>
|
|
|
</view>
|
|
|
@@ -15,11 +16,12 @@
|
|
|
|
|
|
<!-- 正常模式:宫格上传(完全对齐官方 upload-image 样式) -->
|
|
|
<view v-else class="uni-file-picker__container">
|
|
|
- <view class="file-picker__box" v-for="(item, index) in innerFileList" :key="index" :style="typeof boxStyle === 'object' ? boxStyle : { cssText: boxStyle }">
|
|
|
+ <view class="file-picker__box" v-for="(item, index) in innerFileList" :key="index"
|
|
|
+ :style="typeof boxStyle === 'object' ? boxStyle : { cssText: boxStyle }">
|
|
|
<view class="file-picker__box-content" :style="borderStyle">
|
|
|
<!-- 图片 -->
|
|
|
- <image v-if="isImage(item)" class="file-image" :src="item.url || item.path" mode="aspectFill" :style="imgStyle"
|
|
|
- @click.stop="previewFile(item, index)" />
|
|
|
+ <image v-if="isImage(item)" class="file-image" :src="item.url || item.path" mode="aspectFill"
|
|
|
+ :style="imgStyle" @click.stop="previewFile(item, index)" />
|
|
|
|
|
|
<!-- 视频 → 显示第一帧 + 播放图标 -->
|
|
|
<view v-else-if="isVideo(item)" class="file-cover video-box" @click.stop="previewFile(item, index)">
|
|
|
@@ -54,20 +56,23 @@
|
|
|
</view>
|
|
|
|
|
|
<!-- 添加按钮 -->
|
|
|
- <view v-if="innerFileList?.length < limit" class="file-picker__box" :style="typeof boxStyle === 'object' ? boxStyle : { cssText: boxStyle }">
|
|
|
+ <view v-if="innerFileList?.length < limit" class="file-picker__box"
|
|
|
+ :style="typeof boxStyle === 'object' ? boxStyle : { cssText: boxStyle }">
|
|
|
<view class="file-picker__box-content is-add" :style="borderStyle" @click="handleChoose">
|
|
|
<cwg-icon name="icon_add" class="upload-icon" :size="24" />
|
|
|
</view>
|
|
|
</view>
|
|
|
</view>
|
|
|
</view>
|
|
|
+ <cop-chooseFile :trigger="triggerFlag" accept="*" @receiveRenderFile="handleFile">
|
|
|
+ </cop-chooseFile>
|
|
|
</template>
|
|
|
|
|
|
<script setup>
|
|
|
-import { ref, watch, nextTick,computed } from 'vue'
|
|
|
+import { ref, watch, nextTick, computed } from 'vue'
|
|
|
import config from '@/config'
|
|
|
import { userToken } from '@/composables/config'
|
|
|
-
|
|
|
+import copChooseFile from '@/uni_modules/cop-chooseFile/components/cop-chooseFile/cop-chooseFile.vue'
|
|
|
// === Vue3 v-model 标准写法 + 多类型兼容 ===
|
|
|
const props = defineProps({
|
|
|
modelValue: {
|
|
|
@@ -156,6 +161,7 @@ const emit = defineEmits([
|
|
|
'progress',
|
|
|
'select'
|
|
|
])
|
|
|
+const triggerFlag = ref(0)
|
|
|
|
|
|
// 内部数据
|
|
|
const innerFileList = ref([])
|
|
|
@@ -270,7 +276,6 @@ watch(
|
|
|
|
|
|
// 只返回干净的 path 数据
|
|
|
const cleanList = list.map(item => item.path || item.url || '')
|
|
|
- console.log(originalType);
|
|
|
|
|
|
// 按原始类型返回
|
|
|
if (props.limit === 1) {
|
|
|
@@ -361,6 +366,7 @@ const deleteFile = (index) => {
|
|
|
const handleChoose = () => {
|
|
|
if (props.disabled || props.readonly) return
|
|
|
const count = props.limit - innerFileList.value.length
|
|
|
+ // #ifdef H5
|
|
|
uni.chooseFile({
|
|
|
type: props.fileMediatype,
|
|
|
count,
|
|
|
@@ -373,12 +379,105 @@ const handleChoose = () => {
|
|
|
if (props.autoUpload) startUpload()
|
|
|
}
|
|
|
})
|
|
|
-}
|
|
|
+ // #endif
|
|
|
+ // #ifdef APP-PLUS
|
|
|
+ triggerFlag.value = Date.now()
|
|
|
+ // chooseFileFromModule({
|
|
|
+ // complete: (res) => {
|
|
|
+ // console.log(res)
|
|
|
+ // let path = res.path
|
|
|
+ // let name = res.name
|
|
|
+ // let fileType = ''
|
|
|
+ // // 如果没有name,默认为:截取最后一个/之后的内容
|
|
|
+ // if (!name) {
|
|
|
+ // let lastIndex = path.lastIndexOf('/');
|
|
|
+ // if (lastIndex !== -1) {
|
|
|
+ // name = path.substring(lastIndex + 1);
|
|
|
+ // } else {
|
|
|
+ // name = Math.random().toString(36).substr(2) + Date.now();
|
|
|
+ // }
|
|
|
+ // }
|
|
|
+ // // 使用 lastIndexOf 方法找到最后一个 . 的位置
|
|
|
+ // let lastDotIndex = name.lastIndexOf('.');
|
|
|
+ // if (lastDotIndex !== -1) {
|
|
|
+ // fileType = name.substring(lastDotIndex + 1);
|
|
|
+ // } else {
|
|
|
+ // console.log('文件路径中没有 .');
|
|
|
+ // }
|
|
|
+ // let file = {
|
|
|
+ // size: res.size,
|
|
|
+ // path,
|
|
|
+ // fileType,
|
|
|
+ // name
|
|
|
+ // }
|
|
|
+ // console.log('根据需求构造的数据', file)
|
|
|
+ // }
|
|
|
+ // })
|
|
|
+
|
|
|
+ // #endif
|
|
|
+}
|
|
|
+// 处理选择的文件
|
|
|
+const handleFile = async (fileData) => {
|
|
|
+
|
|
|
+ try {
|
|
|
+
|
|
|
+ const tempPath = await base64ToTempFile(
|
|
|
+ fileData.filePath,
|
|
|
+ fileData.name
|
|
|
+ )
|
|
|
+
|
|
|
+ const file = {
|
|
|
+ name: fileData.name,
|
|
|
+ size: fileData.size,
|
|
|
+ type: fileData.type,
|
|
|
+
|
|
|
+ // 关键
|
|
|
+ path: tempPath,
|
|
|
+
|
|
|
+ status: 'ready'
|
|
|
+ }
|
|
|
+
|
|
|
+ console.log(file, 121212);
|
|
|
+
|
|
|
+
|
|
|
+ tempFileQueue.value = [file]
|
|
|
+
|
|
|
+ emit('select', {
|
|
|
+ tempFiles: [file]
|
|
|
+ })
|
|
|
+
|
|
|
+ if (props.autoUpload) {
|
|
|
+ startUpload()
|
|
|
+ }
|
|
|
+
|
|
|
+ } catch (e) {
|
|
|
|
|
|
+ uni.showToast({
|
|
|
+ title: '文件处理失败',
|
|
|
+ icon: 'none'
|
|
|
+ })
|
|
|
+
|
|
|
+ console.error(e)
|
|
|
+ }
|
|
|
+}
|
|
|
const startUpload = async () => {
|
|
|
+
|
|
|
const files = tempFileQueue.value
|
|
|
+
|
|
|
tempFileQueue.value = []
|
|
|
- for (const file of files) await uploadFile(file)
|
|
|
+
|
|
|
+ for (const file of files) {
|
|
|
+
|
|
|
+ // APP base64
|
|
|
+ if (file.base64) {
|
|
|
+ await uploadBase64(file)
|
|
|
+ }
|
|
|
+
|
|
|
+ // H5 正常文件
|
|
|
+ else {
|
|
|
+ await uploadFile(file)
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
const uploadFile = (fileItem) => {
|
|
|
@@ -386,6 +485,13 @@ const uploadFile = (fileItem) => {
|
|
|
innerFileList.value.push({ ...fileItem, status: 'uploading', progress: 0 })
|
|
|
const index = innerFileList.value.length - 1
|
|
|
const url = props.action || config.Host80 + props.uploadUrl
|
|
|
+ console.log({
|
|
|
+ url,
|
|
|
+ filePath: fileItem.path,
|
|
|
+ name: props.uploadName,
|
|
|
+ header: { 'Access-Token': userToken.value, ...props.uploadHeaders },
|
|
|
+ formData: props.uploadData
|
|
|
+ }, 100000);
|
|
|
|
|
|
const task = uni.uploadFile({
|
|
|
url,
|
|
|
@@ -395,7 +501,20 @@ const uploadFile = (fileItem) => {
|
|
|
formData: props.uploadData,
|
|
|
|
|
|
success: (res) => {
|
|
|
- const result = props.responseHandler(res.data)
|
|
|
+ const result = typeof props.responseHandler === 'function'
|
|
|
+ ? props.responseHandler(res.data)
|
|
|
+ : {
|
|
|
+ success: typeof res.data === 'string'
|
|
|
+ ? JSON.parse(res.data).code === 200
|
|
|
+ : res.data.code === 200,
|
|
|
+ path: typeof res.data === 'string'
|
|
|
+ ? (JSON.parse(res.data).data?.path || JSON.parse(res.data).data)
|
|
|
+ : (res.data.data?.path || res.data.data),
|
|
|
+ message: typeof res.data === 'string'
|
|
|
+ ? JSON.parse(res.data).msg
|
|
|
+ : res.data.msg
|
|
|
+ }
|
|
|
+
|
|
|
if (result.success) {
|
|
|
innerFileList.value[index].progress = 100
|
|
|
innerFileList.value[index].status = 'success'
|
|
|
@@ -404,8 +523,8 @@ const uploadFile = (fileItem) => {
|
|
|
emit('success', innerFileList.value[index])
|
|
|
} else {
|
|
|
innerFileList.value[index].status = 'error'
|
|
|
- uni.showToast({ title: result.message, icon: 'error' })
|
|
|
- emit('fail', result.message)
|
|
|
+ uni.showToast({ title: result.message || '上传失败', icon: 'error' })
|
|
|
+ emit('fail', result.message || '上传失败')
|
|
|
}
|
|
|
resolve(result)
|
|
|
},
|
|
|
@@ -425,6 +544,169 @@ const uploadFile = (fileItem) => {
|
|
|
})
|
|
|
}
|
|
|
|
|
|
+const uploadBase64 = (fileItem) => {
|
|
|
+ return new Promise((resolve) => {
|
|
|
+ innerFileList.value.push({
|
|
|
+ ...fileItem,
|
|
|
+ status: 'uploading',
|
|
|
+ progress: 0
|
|
|
+ })
|
|
|
+
|
|
|
+ const index =
|
|
|
+ innerFileList.value.length - 1
|
|
|
+
|
|
|
+ const url =
|
|
|
+ props.action ||
|
|
|
+ config.Host80 + props.uploadUrl
|
|
|
+
|
|
|
+ uni.request({
|
|
|
+
|
|
|
+ url,
|
|
|
+
|
|
|
+ method: 'POST',
|
|
|
+
|
|
|
+ header: {
|
|
|
+ 'Content-Type': 'application/json',
|
|
|
+ 'Access-Token': userToken.value,
|
|
|
+ ...props.uploadHeaders
|
|
|
+ },
|
|
|
+
|
|
|
+ data: {
|
|
|
+ file: fileItem.base64,
|
|
|
+ fileName: fileItem.name,
|
|
|
+ fileType: fileItem.type,
|
|
|
+ ...props.uploadData
|
|
|
+ },
|
|
|
+
|
|
|
+ success: (res) => {
|
|
|
+
|
|
|
+ const result =
|
|
|
+ typeof props.responseHandler === 'function'
|
|
|
+ ? props.responseHandler(res.data)
|
|
|
+ : {
|
|
|
+ success: res.data.code === 200,
|
|
|
+ path: res.data.data?.path || res.data.data,
|
|
|
+ message: res.data.msg
|
|
|
+ }
|
|
|
+
|
|
|
+ if (result.success) {
|
|
|
+
|
|
|
+ innerFileList.value[index].progress = 100
|
|
|
+ innerFileList.value[index].status = 'success'
|
|
|
+
|
|
|
+ // 这里继续用后端返回URL
|
|
|
+ innerFileList.value[index].url =
|
|
|
+ config.Host05 + result.path
|
|
|
+
|
|
|
+ innerFileList.value[index].path =
|
|
|
+ result.path
|
|
|
+
|
|
|
+ emit(
|
|
|
+ 'success',
|
|
|
+ innerFileList.value[index]
|
|
|
+ )
|
|
|
+
|
|
|
+ } else {
|
|
|
+
|
|
|
+ innerFileList.value[index].status = 'error'
|
|
|
+
|
|
|
+ uni.showToast({
|
|
|
+ title: result.message || '上传失败',
|
|
|
+ icon: 'none'
|
|
|
+ })
|
|
|
+
|
|
|
+ emit('fail', result.message)
|
|
|
+ }
|
|
|
+
|
|
|
+ resolve(result)
|
|
|
+ },
|
|
|
+
|
|
|
+ fail: () => {
|
|
|
+
|
|
|
+ innerFileList.value[index].status = 'error'
|
|
|
+
|
|
|
+ uni.showToast({
|
|
|
+ title: '上传失败',
|
|
|
+ icon: 'none'
|
|
|
+ })
|
|
|
+
|
|
|
+ emit('fail', '网络异常')
|
|
|
+
|
|
|
+ resolve(null)
|
|
|
+ }
|
|
|
+ })
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+const base64ToTempFile = (base64, fileName = 'file.png') => {
|
|
|
+
|
|
|
+ return new Promise((resolve, reject) => {
|
|
|
+
|
|
|
+ const matches = base64.match(/^data:(.+);base64,(.+)$/)
|
|
|
+
|
|
|
+ if (!matches) {
|
|
|
+ reject('base64格式错误')
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ const base64Data = matches[2]
|
|
|
+
|
|
|
+ const filePath =
|
|
|
+ `${plus.io.convertLocalFileSystemURL('_doc/')}${Date.now()}_${fileName}`
|
|
|
+
|
|
|
+ plus.io.resolveLocalFileSystemURL(
|
|
|
+ '_doc/',
|
|
|
+ (entry) => {
|
|
|
+
|
|
|
+ entry.getFile(
|
|
|
+ `${Date.now()}_${fileName}`,
|
|
|
+ { create: true },
|
|
|
+
|
|
|
+ (fileEntry) => {
|
|
|
+
|
|
|
+ fileEntry.createWriter((writer) => {
|
|
|
+
|
|
|
+ writer.onwrite = () => {
|
|
|
+ resolve(fileEntry.toLocalURL())
|
|
|
+ }
|
|
|
+
|
|
|
+ writer.onerror = reject
|
|
|
+
|
|
|
+ const bitmap = new plus.nativeObj.Bitmap()
|
|
|
+
|
|
|
+ bitmap.loadBase64Data(
|
|
|
+ base64,
|
|
|
+
|
|
|
+ () => {
|
|
|
+
|
|
|
+ bitmap.save(
|
|
|
+ fileEntry.toLocalURL(),
|
|
|
+
|
|
|
+ {},
|
|
|
+
|
|
|
+ () => {
|
|
|
+ resolve(fileEntry.toLocalURL())
|
|
|
+ },
|
|
|
+
|
|
|
+ reject
|
|
|
+ )
|
|
|
+ },
|
|
|
+
|
|
|
+ reject
|
|
|
+ )
|
|
|
+ })
|
|
|
+ },
|
|
|
+
|
|
|
+ reject
|
|
|
+ )
|
|
|
+ },
|
|
|
+
|
|
|
+ reject
|
|
|
+ )
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
const reUploadFile = (index) => {
|
|
|
const file = innerFileList.value[index]
|
|
|
uploadFile(file)
|