|
|
@@ -0,0 +1,156 @@
|
|
|
+<!-- 系统相册选择器 -->
|
|
|
+<template>
|
|
|
+ <ul class="app-gallery">
|
|
|
+ <li class="app-gallery-item" v-for="(item, index) in uploadFiles" :key="index">
|
|
|
+ <Image :src="item.filePath" width="100%" height="100%" fit="cover" @click="chooseFile(index)" />
|
|
|
+ <div class="app-gallery-item__mask" v-if="item.loading">
|
|
|
+ <Loading />
|
|
|
+ <span class="app-gallery-item__message">{{ loadingText }}</span>
|
|
|
+ </div>
|
|
|
+ <template v-else>
|
|
|
+ <div class="app-gallery-item__mask" v-if="item.message">
|
|
|
+ <Icon name="close" />
|
|
|
+ <span class="app-gallery-item__message">{{ item.message }}</span>
|
|
|
+ </div>
|
|
|
+ <div class="app-gallery-item__close" @click="deleteFile(index)">
|
|
|
+ <Icon name="cross" />
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </li>
|
|
|
+ <li class="app-gallery-item" v-if="uploadFiles.length < maxCount">
|
|
|
+ <div class="app-gallery-item__upload" @click="chooseFile()">
|
|
|
+ <Icon name="photograph" />
|
|
|
+ </div>
|
|
|
+ </li>
|
|
|
+ </ul>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script lang="ts" setup>
|
|
|
+import { computed, PropType } from 'vue'
|
|
|
+import { Image, Icon, Loading, showFailToast } from 'vant'
|
|
|
+import { UploadFile } from './types'
|
|
|
+import plus from '@/utils/h5plus'
|
|
|
+
|
|
|
+const props = defineProps({
|
|
|
+ modelValue: {
|
|
|
+ type: Array as PropType<UploadFile[]>,
|
|
|
+ required: true
|
|
|
+ },
|
|
|
+ // https://www.html5plus.org/doc/zh_cn/gallery.html#plus.gallery.GalleryFilter
|
|
|
+ fileType: {
|
|
|
+ type: String as PropType<'image' | 'video' | 'none'>,
|
|
|
+ default: 'none'
|
|
|
+ },
|
|
|
+ // 文件大小限制
|
|
|
+ maxSize: {
|
|
|
+ type: Number,
|
|
|
+ default: 0
|
|
|
+ },
|
|
|
+ // 文件上传数量限制
|
|
|
+ maxCount: {
|
|
|
+ type: Number,
|
|
|
+ default: 1
|
|
|
+ },
|
|
|
+ loadingText: {
|
|
|
+ type: String,
|
|
|
+ default: '上传中...'
|
|
|
+ },
|
|
|
+ errorText: {
|
|
|
+ type: String,
|
|
|
+ default: '上传失败'
|
|
|
+ },
|
|
|
+ // 是否开启覆盖上传,开启后会关闭图片预览
|
|
|
+ reupload: {
|
|
|
+ type: Boolean,
|
|
|
+ default: true
|
|
|
+ },
|
|
|
+ autoUpload: {
|
|
|
+ type: Function,
|
|
|
+ }
|
|
|
+})
|
|
|
+
|
|
|
+const emit = defineEmits(['update:modelValue'])
|
|
|
+
|
|
|
+const uploadFiles = computed({
|
|
|
+ get: () => props.modelValue,
|
|
|
+ set: (val) => emit('update:modelValue', val)
|
|
|
+})
|
|
|
+
|
|
|
+// https://www.html5plus.org/doc/zh_cn/gallery.html#plus.gallery.pick
|
|
|
+const chooseFile = (index = -1) => {
|
|
|
+ plus.onPlusReady((plus) => {
|
|
|
+ plus.gallery.pick((path: string) => {
|
|
|
+ plus.io.resolveLocalFileSystemURL(path, (entry: FileSystemFileEntry) => {
|
|
|
+ entry.file((file) => {
|
|
|
+ const reader = new plus.io.FileReader()
|
|
|
+ reader.onloadend = (e: any) => {
|
|
|
+ const fileBlob = base64ToFile(e.target.result, entry.name)
|
|
|
+
|
|
|
+ const uploadFile: UploadFile = {
|
|
|
+ fileName: entry.name,
|
|
|
+ filePath: path,
|
|
|
+ file: fileBlob
|
|
|
+ }
|
|
|
+
|
|
|
+ if (index > -1) {
|
|
|
+ uploadFiles.value[index] = uploadFile
|
|
|
+ } else {
|
|
|
+ uploadFiles.value.push(uploadFile)
|
|
|
+ }
|
|
|
+
|
|
|
+ const currentIndex = index > -1 ? index : uploadFiles.value.length - 1
|
|
|
+ uploadToServer(currentIndex)
|
|
|
+ }
|
|
|
+ reader.readAsDataURL(file)
|
|
|
+ }, (error) => {
|
|
|
+ showFailToast(error.message)
|
|
|
+ })
|
|
|
+ }, (error: DOMException) => {
|
|
|
+ showFailToast(error.message)
|
|
|
+ })
|
|
|
+ }, () => {
|
|
|
+ console.log('取消选择')
|
|
|
+ }, {
|
|
|
+ filter: props.fileType
|
|
|
+ })
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+const uploadToServer = (index: number) => {
|
|
|
+ const uploadFile = uploadFiles.value[index]
|
|
|
+
|
|
|
+ if (props.autoUpload && uploadFile.file) {
|
|
|
+ const formData = new FormData()
|
|
|
+ formData.append('file', uploadFile.file)
|
|
|
+
|
|
|
+ uploadFile.loading = true
|
|
|
+ props.autoUpload(formData).catch((error: string) => {
|
|
|
+ uploadFile.message = error ?? props.errorText
|
|
|
+ }).finally(() => {
|
|
|
+ uploadFile.loading = false
|
|
|
+ })
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+const deleteFile = (index: number) => {
|
|
|
+ uploadFiles.value.splice(index, 1)
|
|
|
+}
|
|
|
+
|
|
|
+const base64ToFile = (base64Data: string, fileName: string) => {
|
|
|
+ const base64Content = base64Data.split(',')[1] // 截取数据部分
|
|
|
+
|
|
|
+ const byteString = atob(base64Content)
|
|
|
+ const arrayBuffer = new ArrayBuffer(byteString.length)
|
|
|
+ const uint8Array = new Uint8Array(arrayBuffer)
|
|
|
+
|
|
|
+ for (let i = 0; i < byteString.length; i++) {
|
|
|
+ uint8Array[i] = byteString.charCodeAt(i)
|
|
|
+ }
|
|
|
+
|
|
|
+ return new File([arrayBuffer], fileName)
|
|
|
+}
|
|
|
+</script>
|
|
|
+
|
|
|
+<style lang="less">
|
|
|
+@import './index.less';
|
|
|
+</style>
|