Showing
4 changed files
with
259 additions
and
226 deletions
| ... | @@ -202,6 +202,10 @@ const _sfc_main = create({ | ... | @@ -202,6 +202,10 @@ const _sfc_main = create({ |
| 202 | type: Array, | 202 | type: Array, |
| 203 | default: () => ['image', 'video', 'mix'], | 203 | default: () => ['image', 'video', 'mix'], |
| 204 | }, | 204 | }, |
| 205 | + imageType: { | ||
| 206 | + type: Array, | ||
| 207 | + default: () => [], | ||
| 208 | + }, | ||
| 205 | camera: { | 209 | camera: { |
| 206 | type: String, | 210 | type: String, |
| 207 | default: 'back', | 211 | default: 'back', |
| ... | @@ -248,6 +252,7 @@ const _sfc_main = create({ | ... | @@ -248,6 +252,7 @@ const _sfc_main = create({ |
| 248 | 'delete', | 252 | 'delete', |
| 249 | 'update:fileList', | 253 | 'update:fileList', |
| 250 | 'file-item-click', | 254 | 'file-item-click', |
| 255 | + 'image-type-error', | ||
| 251 | ], | 256 | ], |
| 252 | setup(props, { emit }) { | 257 | setup(props, { emit }) { |
| 253 | const fileList = reactive(props.fileList) | 258 | const fileList = reactive(props.fileList) |
| ... | @@ -475,9 +480,18 @@ const _sfc_main = create({ | ... | @@ -475,9 +480,18 @@ const _sfc_main = create({ |
| 475 | const maximize = props.maximize * 1 | 480 | const maximize = props.maximize * 1 |
| 476 | const oversizes = new Array() | 481 | const oversizes = new Array() |
| 477 | files = files.filter((file) => { | 482 | files = files.filter((file) => { |
| 483 | + if (Taro.getEnv() != 'WEAPP') { | ||
| 484 | + file.type = file.originalFileObj.name.split('.').pop() | ||
| 485 | + } else { | ||
| 486 | + file.type = file.tempFilePath.split('.').pop() | ||
| 487 | + } | ||
| 478 | if (file.size > maximize) { | 488 | if (file.size > maximize) { |
| 479 | oversizes.push(file) | 489 | oversizes.push(file) |
| 480 | return false | 490 | return false |
| 491 | + } else if (!props.imageType.includes(file.type)) { | ||
| 492 | + // 控制文件类型上传 | ||
| 493 | + emit('image-type-error', file.type) | ||
| 494 | + return false | ||
| 481 | } else { | 495 | } else { |
| 482 | return true | 496 | return true |
| 483 | } | 497 | } | ... | ... |
| 1 | <!-- | 1 | <!-- |
| 2 | * @Date: 2022-08-31 16:16:49 | 2 | * @Date: 2022-08-31 16:16:49 |
| 3 | * @LastEditors: hookehuyr hookehuyr@gmail.com | 3 | * @LastEditors: hookehuyr hookehuyr@gmail.com |
| 4 | - * @LastEditTime: 2023-04-12 10:59:48 | 4 | + * @LastEditTime: 2023-04-12 14:19:07 |
| 5 | * @FilePath: /custom_form/src/components/FileUploaderField/index.vue | 5 | * @FilePath: /custom_form/src/components/FileUploaderField/index.vue |
| 6 | * @Description: 文件上传控件 | 6 | * @Description: 文件上传控件 |
| 7 | --> | 7 | --> |
| ... | @@ -20,19 +20,6 @@ | ... | @@ -20,19 +20,6 @@ |
| 20 | v-html="item.component_props.note" | 20 | v-html="item.component_props.note" |
| 21 | style="font-size: 13px; margin-left: 1rem; color: gray; padding-bottom: 0.5rem; padding-top: 0.25rem; white-space: pre-wrap;" | 21 | style="font-size: 13px; margin-left: 1rem; color: gray; padding-bottom: 0.5rem; padding-top: 0.25rem; white-space: pre-wrap;" |
| 22 | /> | 22 | /> |
| 23 | - <!-- <div> | ||
| 24 | - <p | ||
| 25 | - v-for="(file, index) in fileList" | ||
| 26 | - :key="index" | ||
| 27 | - style="padding-left: 1rem; margin-bottom: 0.5rem" | ||
| 28 | - > | ||
| 29 | - <p style="font-size: 1rem; word-break: break-all; margin-right: 0.75rem;"> | ||
| 30 | - <span>{{ index + 1 }}. {{ file.name }} {{ (file.size / 1024 / 1024).toFixed(2) }}MB</span> | ||
| 31 | - | ||
| 32 | - <span style="color: #e32525; font-size: 0.85rem" @click="beforeDelete(file)">移除</span> | ||
| 33 | - </p> | ||
| 34 | - </p> | ||
| 35 | - </div> --> | ||
| 36 | <div style="padding: 1rem; padding-top: 0.5rem;"> | 23 | <div style="padding: 1rem; padding-top: 0.5rem;"> |
| 37 | <nut-uploader | 24 | <nut-uploader |
| 38 | :name="item.name" | 25 | :name="item.name" |
| ... | @@ -49,7 +36,7 @@ | ... | @@ -49,7 +36,7 @@ |
| 49 | @failure="uploadFailure" | 36 | @failure="uploadFailure" |
| 50 | @delete="onDelete" | 37 | @delete="onDelete" |
| 51 | @change="onChange" | 38 | @change="onChange" |
| 52 | - @file-item-click="fileItemClick" | 39 | + @image-type-error="imageTypeError" |
| 53 | > | 40 | > |
| 54 | <nut-button shape="square" type="primary"> | 41 | <nut-button shape="square" type="primary"> |
| 55 | <template #icon> | 42 | <template #icon> |
| ... | @@ -96,9 +83,12 @@ const HideShow = computed(() => { | ... | @@ -96,9 +83,12 @@ const HideShow = computed(() => { |
| 96 | 83 | ||
| 97 | const emit = defineEmits(["active"]); | 84 | const emit = defineEmits(["active"]); |
| 98 | 85 | ||
| 86 | +// // 固定类型限制 | ||
| 87 | +// const imageTypes = ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'psd', 'tif']; | ||
| 88 | + | ||
| 99 | // // 文件类型中文页面显示 | 89 | // // 文件类型中文页面显示 |
| 100 | // const type_text = computed(() => { | 90 | // const type_text = computed(() => { |
| 101 | -// return props.item.component_props.file_type; | 91 | +// return imageTypes.join('/'); |
| 102 | // }); | 92 | // }); |
| 103 | 93 | ||
| 104 | // 上传文件集合 | 94 | // 上传文件集合 |
| ... | @@ -121,12 +111,18 @@ const onOversize = (files) => { | ... | @@ -121,12 +111,18 @@ const onOversize = (files) => { |
| 121 | toast_type.value = 'warn'; | 111 | toast_type.value = 'warn'; |
| 122 | }; | 112 | }; |
| 123 | 113 | ||
| 114 | +const onStart = (options) => { | ||
| 115 | + // console.warn(options); | ||
| 116 | +} | ||
| 117 | + | ||
| 124 | // 自定义上传逻辑 | 118 | // 自定义上传逻辑 |
| 125 | const beforeXhrUpload = async (xhr, options) => { | 119 | const beforeXhrUpload = async (xhr, options) => { |
| 120 | + const imgObj = defaultFileList.value[defaultFileList.value.length - 1]; | ||
| 121 | + // 判断上传文件格式 | ||
| 122 | + imgObj.type = imgObj.type ? imgObj.type : imgObj.name.split(".").pop(); | ||
| 126 | // H5环境 | 123 | // H5环境 |
| 127 | if (process.env.TARO_ENV === 'h5') { | 124 | if (process.env.TARO_ENV === 'h5') { |
| 128 | // 把本地路径转换成file实体 | 125 | // 把本地路径转换成file实体 |
| 129 | - const imgObj = defaultFileList.value[defaultFileList.value.length - 1]; | ||
| 130 | const imgBlob = await fetch(imgObj.url).then(r => r.blob()); | 126 | const imgBlob = await fetch(imgObj.url).then(r => r.blob()); |
| 131 | const imgFile = new File([imgBlob], imgObj.name , { type: imgObj.type }); | 127 | const imgFile = new File([imgBlob], imgObj.name , { type: imgObj.type }); |
| 132 | // 上传返回file数据结构 | 128 | // 上传返回file数据结构 |
| ... | @@ -146,7 +142,6 @@ const beforeXhrUpload = async (xhr, options) => { | ... | @@ -146,7 +142,6 @@ const beforeXhrUpload = async (xhr, options) => { |
| 146 | loading.value = false; | 142 | loading.value = false; |
| 147 | } | 143 | } |
| 148 | } else { | 144 | } else { |
| 149 | - const imgObj = defaultFileList.value[defaultFileList.value.length - 1]; | ||
| 150 | const fs = Taro.getFileSystemManager() | 145 | const fs = Taro.getFileSystemManager() |
| 151 | fs.getFileInfo({ | 146 | fs.getFileInfo({ |
| 152 | filePath: imgObj.url, | 147 | filePath: imgObj.url, |
| ... | @@ -250,9 +245,12 @@ const onDelete = ({ file }) => { | ... | @@ -250,9 +245,12 @@ const onDelete = ({ file }) => { |
| 250 | // 完整数据回调到表单上 | 245 | // 完整数据回调到表单上 |
| 251 | emit("active", props.item.value); | 246 | emit("active", props.item.value); |
| 252 | } | 247 | } |
| 253 | -// 上传成功,点击队列项回调 | 248 | + |
| 254 | -const fileItemClick = (fileItem) => { | 249 | +// |
| 255 | - console.warn(fileItem); | 250 | +const imageTypeError = (file) => { |
| 251 | + toast_msg.value = '请上传指定格式' | ||
| 252 | + toast_show.value = true; | ||
| 253 | + toast_type.value = 'warn'; | ||
| 256 | } | 254 | } |
| 257 | 255 | ||
| 258 | const onChange = ({ fileList }) => { | 256 | const onChange = ({ fileList }) => { |
| ... | @@ -379,7 +377,7 @@ defineExpose({ validFileUploader }); | ... | @@ -379,7 +377,7 @@ defineExpose({ validFileUploader }); |
| 379 | } | 377 | } |
| 380 | 378 | ||
| 381 | .type-text { | 379 | .type-text { |
| 382 | - font-size: 0.9rem; | 380 | + font-size: 13px; |
| 383 | margin-left: 1rem; | 381 | margin-left: 1rem; |
| 384 | padding-bottom: 1rem; | 382 | padding-bottom: 1rem; |
| 385 | color: gray; | 383 | color: gray; | ... | ... |
| 1 | <!-- | 1 | <!-- |
| 2 | * @Date: 2022-08-31 16:16:49 | 2 | * @Date: 2022-08-31 16:16:49 |
| 3 | * @LastEditors: hookehuyr hookehuyr@gmail.com | 3 | * @LastEditors: hookehuyr hookehuyr@gmail.com |
| 4 | - * @LastEditTime: 2023-01-17 16:44:32 | 4 | + * @LastEditTime: 2023-04-12 15:01:26 |
| 5 | - * @FilePath: /data-table/src/components/ImageUploaderField/index.vue | 5 | + * @FilePath: /custom_form/src/components/ImageUploaderField/index.vue |
| 6 | * @Description: 图片上传控件 | 6 | * @Description: 图片上传控件 |
| 7 | --> | 7 | --> |
| 8 | <template> | 8 | <template> |
| 9 | <div v-if="HideShow" class="image-uploader-field"> | 9 | <div v-if="HideShow" class="image-uploader-field"> |
| 10 | <div class="label"> | 10 | <div class="label"> |
| 11 | - <span v-if="item.component_props.required"> *</span> | 11 | + <text v-if="item.component_props.required"> *</text> |
| 12 | {{ item.component_props.label }} | 12 | {{ item.component_props.label }} |
| 13 | </div> | 13 | </div> |
| 14 | - <div | 14 | + <div style="font-size: 12px; color: red; margin-left: 20px;"> |
| 15 | - v-if="item.component_props.note" | 15 | + <text>最大图片数为 {{ item.component_props.max_count }} 张</text>, |
| 16 | - v-html="item.component_props.note" | 16 | + <text>单个图片最大体积 {{ item.component_props.max_size }} MB</text> |
| 17 | - style="font-size: 0.9rem; margin-left: 1rem; color: gray; padding-bottom: 0.5rem; padding-top: 0.25rem; white-space: pre;" | 17 | + </div> |
| 18 | - /> | 18 | + <div v-if="item.component_props.note" v-html="item.component_props.note" |
| 19 | - <div style="padding: 1rem"> | 19 | + style="font-size: 13px; margin-left: 1rem; color: gray; padding-bottom: 0.5rem; padding-top: 0.25rem; white-space: pre-wrap;" /> |
| 20 | - <van-uploader | 20 | + <div style="padding: 1rem; padding-top: 0.5rem;"> |
| 21 | + <nut-uploader | ||
| 21 | :name="item.name" | 22 | :name="item.name" |
| 22 | - upload-icon="add" | 23 | + v-model:file-list="defaultFileList" |
| 23 | - :before-read="beforeRead" | 24 | + :maximum="item.component_props.max_count" |
| 24 | - :after-read="afterRead" | 25 | + :multiple="item.component_props.max_count > 1" |
| 25 | - :before-delete="beforeDelete" | 26 | + :size-type="['compressed']" |
| 26 | - v-model="fileList" | 27 | + :image-type="imageTypes" |
| 27 | - :multiple="item.component_props.max_size > 1" | 28 | + :maximize="max_size" |
| 28 | - /> | 29 | + :before-xhr-upload="beforeXhrUpload" |
| 30 | + @oversize="onOversize" | ||
| 31 | + @success="uploadSuccess" | ||
| 32 | + @failure="uploadFailure" | ||
| 33 | + @delete="onDelete" | ||
| 34 | + @change="onChange" | ||
| 35 | + @image-type-error="imageTypeError"> | ||
| 36 | + </nut-uploader> | ||
| 29 | </div> | 37 | </div> |
| 30 | - <div class="type-text">上传类型: {{ type_text }}</div> | 38 | + <div class="type-text">上传格式:{{ type_text }}</div> |
| 31 | - <div | 39 | + <div v-if="show_error" style="padding: 5px 20px; color: red; font-size: 12px;"> |
| 32 | - v-if="show_empty" | 40 | + {{ error_msg }} |
| 33 | - class="van-field__error-message" | ||
| 34 | - style="padding: 0 1rem 1rem 1rem" | ||
| 35 | - > | ||
| 36 | - 图片上传不能为空 | ||
| 37 | </div> | 41 | </div> |
| 38 | - <van-divider /> | 42 | + <nut-divider :style="{ color: '#ebedf0' }" /> |
| 43 | + <nut-overlay v-model:visible="loading"> | ||
| 44 | + <div class="wrapper" style="color: white; font-size: 15px;"> | ||
| 45 | + <Loading /> | ||
| 46 | + 上传中... | ||
| 47 | + </div> | ||
| 48 | + </nut-overlay> | ||
| 49 | + <nut-toast :msg="toast_msg" v-model:visible="toast_show" :type="toast_type" /> | ||
| 39 | </div> | 50 | </div> |
| 40 | - | ||
| 41 | - <van-overlay :show="loading"> | ||
| 42 | - <div class="wrapper" @click.stop> | ||
| 43 | - <van-loading vertical color="#FFFFFF">上传中...</van-loading> | ||
| 44 | - </div> | ||
| 45 | - </van-overlay> | ||
| 46 | </template> | 51 | </template> |
| 47 | 52 | ||
| 48 | <script setup> | 53 | <script setup> |
| 49 | -/** | 54 | +import { ref, computed, watch, onMounted, reactive } from "vue"; |
| 50 | - * 图片上传 | ||
| 51 | - * @param name[String] 组件名称 | ||
| 52 | - * @param image_type[Array] 图片上传类型 | ||
| 53 | - * @param multiple[Boolean] 图片多选 | ||
| 54 | - */ | ||
| 55 | -import { showSuccessToast, showFailToast, showToast } from "vant"; | ||
| 56 | -import _ from "lodash"; | ||
| 57 | -import { v4 as uuidv4 } from "uuid"; | ||
| 58 | import { qiniuTokenAPI, qiniuUploadAPI, saveFileAPI } from "@/api/common"; | 55 | import { qiniuTokenAPI, qiniuUploadAPI, saveFileAPI } from "@/api/common"; |
| 59 | import BMF from "browser-md5-file"; | 56 | import BMF from "browser-md5-file"; |
| 60 | -import { useRoute } from "vue-router"; | 57 | +import { getUrlParams } from "@/utils/tools"; |
| 61 | -import axios from "axios"; | 58 | +import { Uploader, Loading } from '@nutui/icons-vue-taro'; |
| 62 | -import { getEtag } from "@/utils/qetag.js"; // 生成hash值 | 59 | +import Taro from '@tarojs/taro' |
| 63 | 60 | ||
| 64 | -const $route = useRoute(); | ||
| 65 | const props = defineProps({ | 61 | const props = defineProps({ |
| 66 | item: Object, | 62 | item: Object, |
| 67 | }); | 63 | }); |
| ... | @@ -69,156 +65,204 @@ const props = defineProps({ | ... | @@ -69,156 +65,204 @@ const props = defineProps({ |
| 69 | const HideShow = computed(() => { | 65 | const HideShow = computed(() => { |
| 70 | return !props.item.component_props.disabled | 66 | return !props.item.component_props.disabled |
| 71 | }) | 67 | }) |
| 68 | + | ||
| 72 | const emit = defineEmits(["active"]); | 69 | const emit = defineEmits(["active"]); |
| 73 | -const show_empty = ref(false); | ||
| 74 | 70 | ||
| 75 | // 固定类型限制 | 71 | // 固定类型限制 |
| 76 | -const imageTypes = "jpg/jpeg/png/gif/bmp/psd/tif"; | 72 | +const imageTypes = ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'psd', 'tif']; |
| 77 | 73 | ||
| 78 | // 文件类型中文页面显示 | 74 | // 文件类型中文页面显示 |
| 79 | const type_text = computed(() => { | 75 | const type_text = computed(() => { |
| 80 | - // return props.item.component_props.image_type; | 76 | + return imageTypes.join('/'); |
| 81 | - return imageTypes; | ||
| 82 | }); | 77 | }); |
| 83 | -// 上传图片集合 | ||
| 84 | -const fileList = ref([ | ||
| 85 | - // { url: "https://fastly.jsdelivr.net/npm/@vant/assets/leaf.jpeg" }, | ||
| 86 | - // Uploader 根据文件后缀来判断是否为图片文件 | ||
| 87 | - // 如果图片 URL 中不包含类型信息,可以添加 isImage 标记来声明 | ||
| 88 | - // { url: 'https://cloud-image', isImage: true }, | ||
| 89 | -]); | ||
| 90 | 78 | ||
| 91 | -// 上传前置处理 | 79 | +// 上传文件集合 |
| 92 | -const beforeRead = (file) => { | 80 | +const fileList = ref([]); |
| 93 | - // 类型限制 | 81 | +const defaultFileList = ref([]) |
| 94 | - // const image_types = _.map( | ||
| 95 | - // props.item.component_props.image_type.split("/"), | ||
| 96 | - // (item) => `image/${item}` | ||
| 97 | - // ); | ||
| 98 | - const image_types = _.map(imageTypes.split("/"), (item) => `image/${item}`); | ||
| 99 | 82 | ||
| 100 | - let flag = true; | 83 | +// 上传文件体积 |
| 101 | - if (_.isArray(file)) { | 84 | +const max_size = computed(() => { |
| 102 | - // 多张图片 | 85 | + return props.item.component_props.max_size * 1024 * 1024 |
| 103 | - const types = _.difference(_.uniq(_.map(file, (item) => item.type)), image_types); // 数组返回不能上传的类型 | 86 | +}) |
| 104 | - if (types.length) { | 87 | + |
| 105 | - flag = false; | 88 | +const toast_msg = ref(''); |
| 106 | - showFailToast("请上传指定格式图片"); | 89 | +const toast_show = ref(false); |
| 107 | - } | 90 | +const toast_type = ref('success'); |
| 108 | - if (fileList.value.length + file.length > props.item.component_props.max_count) { | 91 | + |
| 109 | - // 数量限制 | 92 | +// 超过体积大小回调 |
| 110 | - flag = false; | 93 | +const onOversize = (files) => { |
| 111 | - showToast(`最大上传数量为${props.item.component_props.max_count}张`); | 94 | + toast_msg.value = `最大图片体积为${props.item.component_props.max_size}MB` |
| 112 | - } | 95 | + toast_show.value = true; |
| 113 | - } else { | 96 | + toast_type.value = 'warn'; |
| 114 | - if (!_.includes(image_types, file.type)) { | ||
| 115 | - showFailToast("请上传指定格式图片"); | ||
| 116 | - flag = false; | ||
| 117 | - } | ||
| 118 | - if (fileList.value.length + 1 > props.item.component_props.max_count) { | ||
| 119 | - // 数量限制 | ||
| 120 | - flag = false; | ||
| 121 | - showToast(`最大上传数量为${props.item.component_props.max_count}张`); | ||
| 122 | - } | ||
| 123 | - if ((file.size / 1024 / 1024).toFixed(2) > props.item.component_props.max_size) { | ||
| 124 | - // 体积限制 | ||
| 125 | - flag = false; | ||
| 126 | - showToast( | ||
| 127 | - `最大文件体积为${props.item.component_props.max_size}MB` | ||
| 128 | - ); | ||
| 129 | - } | ||
| 130 | - } | ||
| 131 | - return flag; | ||
| 132 | }; | 97 | }; |
| 133 | 98 | ||
| 134 | -// 文件读取完成后的回调函数 | 99 | +const onStart = (options) => { |
| 135 | -const afterRead = async (files) => { | 100 | + // console.warn(options); |
| 136 | - if (Array.isArray(files)) { | 101 | +} |
| 137 | - // 多张图片上传files是一个数组 | 102 | + |
| 138 | - muliUpload(files); | 103 | +// 自定义上传逻辑 |
| 139 | - } else { | 104 | +const beforeXhrUpload = async (xhr, options) => { |
| 140 | - const imgUrl = await handleUpload(files); | 105 | + const imgObj = defaultFileList.value[defaultFileList.value.length - 1]; |
| 106 | + // 判断上传文件格式 | ||
| 107 | + imgObj.type = imgObj.type ? imgObj.type : imgObj.name.split(".").pop(); | ||
| 108 | + // H5环境 | ||
| 109 | + if (process.env.TARO_ENV === 'h5') { | ||
| 110 | + // 把本地路径转换成file实体 | ||
| 111 | + const imgBlob = await fetch(imgObj.url).then(r => r.blob()); | ||
| 112 | + const imgFile = new File([imgBlob], imgObj.name, { type: imgObj.type }); | ||
| 113 | + // 上传返回file数据结构 | ||
| 114 | + const resImgObj = await handleUpload(imgFile); | ||
| 141 | // 上传失败提示 | 115 | // 上传失败提示 |
| 142 | - if (!imgUrl.src) { | 116 | + if (!resImgObj) { |
| 143 | - files.status = "failed"; | 117 | + options.onFailure?.(resImgObj, options); |
| 144 | - files.message = "上传失败"; | ||
| 145 | loading.value = false; | 118 | loading.value = false; |
| 146 | } else { | 119 | } else { |
| 147 | - files.status = ""; | 120 | + defaultFileList.value[defaultFileList.value.length - 1]['url'] = resImgObj.src; |
| 148 | - files.message = ""; | 121 | + defaultFileList.value[defaultFileList.value.length - 1]['type'] = 'image'; |
| 149 | fileList.value.push({ | 122 | fileList.value.push({ |
| 150 | - // meta_id: imgUrl.meta_id, | 123 | + name: imgFile.name, |
| 151 | - name: files.file.name, | 124 | + url: resImgObj.src, |
| 152 | - url: imgUrl.src, | 125 | + size: imgFile.size |
| 153 | - // isImage: true, | ||
| 154 | }); | 126 | }); |
| 127 | + options.onSuccess?.(resImgObj, options); | ||
| 155 | loading.value = false; | 128 | loading.value = false; |
| 156 | } | 129 | } |
| 130 | + } else { | ||
| 131 | + const fs = Taro.getFileSystemManager() | ||
| 132 | + fs.getFileInfo({ | ||
| 133 | + filePath: imgObj.url, | ||
| 134 | + success: async (res) => { | ||
| 135 | + const file_info = res; | ||
| 136 | + let suffix = /\.[^\.]+$/.exec(imgObj.name); // 获取后缀 | ||
| 137 | + // 获取七牛token | ||
| 138 | + const filename = imgObj.name; // 真实文件名 | ||
| 139 | + const getToken = await qiniuTokenAPI({ | ||
| 140 | + name: filename, | ||
| 141 | + hash: file_info.digest, | ||
| 142 | + }); | ||
| 143 | + // 文件上传七牛云 | ||
| 144 | + // 第一次上传 | ||
| 145 | + if (getToken.token) { | ||
| 146 | + loading.value = true; | ||
| 147 | + // 自拍图片上传七牛服务器 | ||
| 148 | + Taro.uploadFile({ | ||
| 149 | + url: 'https://up.qbox.me', | ||
| 150 | + filePath: imgObj.url, | ||
| 151 | + name: `file`, | ||
| 152 | + formData: { | ||
| 153 | + token: getToken.token, | ||
| 154 | + key: `uploadForm/${formCode}/${file_info.digest}${suffix}` | ||
| 155 | + }, | ||
| 156 | + }) | ||
| 157 | + .then(async (res) => { | ||
| 158 | + res.data = JSON.parse(res.data); | ||
| 159 | + if (res.data.filekey) { | ||
| 160 | + // 保存文件 | ||
| 161 | + const { data } = await saveFileAPI({ | ||
| 162 | + name: filename, | ||
| 163 | + filekey: res.data.filekey, | ||
| 164 | + hash: file_info.digest, | ||
| 165 | + }); | ||
| 166 | + defaultFileList.value[defaultFileList.value.length - 1]['url'] = data.src; | ||
| 167 | + defaultFileList.value[defaultFileList.value.length - 1]['type'] = 'image'; | ||
| 168 | + // 加入上传成功队列 | ||
| 169 | + fileList.value.push({ | ||
| 170 | + name: filename, | ||
| 171 | + url: data.src, | ||
| 172 | + size: file_info.size | ||
| 173 | + }); | ||
| 174 | + options.onSuccess?.(data, options); | ||
| 175 | + loading.value = false; | ||
| 176 | + } | ||
| 177 | + }) | ||
| 178 | + .catch((error) => { | ||
| 179 | + console.error(error) | ||
| 180 | + options.onFailure?.(error, options); | ||
| 181 | + loading.value = false; | ||
| 182 | + }) | ||
| 183 | + } | ||
| 184 | + // 重复上传 | ||
| 185 | + if (getToken.data) { | ||
| 186 | + // 加入上传成功队列 | ||
| 187 | + fileList.value.push({ | ||
| 188 | + name: filename, | ||
| 189 | + url: getToken.data.src, | ||
| 190 | + size: file_info.size | ||
| 191 | + }); | ||
| 192 | + options.onSuccess?.(getToken.data, options); | ||
| 193 | + } | ||
| 194 | + } | ||
| 195 | + }) | ||
| 157 | } | 196 | } |
| 158 | - // 过滤非包含URL的图片 | 197 | +} |
| 159 | - fileList.value = fileList.value.filter((item) => { | 198 | + |
| 160 | - if (item.url) return item; | 199 | +// 上传成功回调 |
| 161 | - }); | 200 | +const uploadSuccess = async ({ data, fileItem, option, responseText }) => { |
| 162 | props.item.value = { | 201 | props.item.value = { |
| 163 | - key: "image_uploader", | 202 | + key: "file_uploader", |
| 164 | filed_name: props.item.key, | 203 | filed_name: props.item.key, |
| 165 | - // value: fileList.value.map((item) => item.url), | ||
| 166 | value: fileList.value, | 204 | value: fileList.value, |
| 167 | }; | 205 | }; |
| 168 | - show_empty.value = false; | ||
| 169 | // 完整数据回调到表单上 | 206 | // 完整数据回调到表单上 |
| 170 | emit("active", props.item.value); | 207 | emit("active", props.item.value); |
| 208 | + // 校验数据 | ||
| 209 | + validImageUploader(); | ||
| 210 | +}; | ||
| 211 | + | ||
| 212 | +// 上传失败回调 | ||
| 213 | +const uploadFailure = async ({ data, fileItem, option, responseText }) => { | ||
| 214 | + // 真实上传失败才会提示 | ||
| 215 | + if (data) { | ||
| 216 | + console.error("上传失败", "fail"); | ||
| 217 | + toast_msg.value = '上传失败,请重新尝试!' | ||
| 218 | + toast_show.value = true; | ||
| 219 | + toast_type.value = 'fail'; | ||
| 220 | + } | ||
| 171 | }; | 221 | }; |
| 172 | 222 | ||
| 173 | -// 文件删除前的回调函数 | 223 | +// 删除上传队列回调 |
| 174 | -const beforeDelete = (files) => { | 224 | +const onDelete = ({ file }) => { |
| 175 | fileList.value = fileList.value.filter((item) => { | 225 | fileList.value = fileList.value.filter((item) => { |
| 176 | - if (item.url !== files.url) return item; | 226 | + if (item.url !== file.url) return item; |
| 177 | }); | 227 | }); |
| 178 | props.item.value = { | 228 | props.item.value = { |
| 179 | - key: "image_uploader", | 229 | + key: "file_uploader", |
| 180 | filed_name: props.item.key, | 230 | filed_name: props.item.key, |
| 181 | - // value: fileList.value.map((item) => item.url), | ||
| 182 | value: fileList.value, | 231 | value: fileList.value, |
| 183 | }; | 232 | }; |
| 184 | // 完整数据回调到表单上 | 233 | // 完整数据回调到表单上 |
| 185 | emit("active", props.item.value); | 234 | emit("active", props.item.value); |
| 186 | -}; | 235 | +} |
| 187 | 236 | ||
| 188 | -/********** 上传七牛云获取图片地址 ***********/ | 237 | +// |
| 189 | -const loading = ref(false); | 238 | +const imageTypeError = (file) => { |
| 190 | -const formCode = $route.query.code; // 表单code | 239 | + toast_msg.value = '请上传指定格式' |
| 191 | -// const uuid = () => { | 240 | + toast_show.value = true; |
| 192 | -// let s = []; | 241 | + toast_type.value = 'warn'; |
| 193 | -// let hexDigits = "0123456789abcdef"; | 242 | +} |
| 194 | -// for (var i = 0; i < 36; i++) { | ||
| 195 | -// s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1); | ||
| 196 | -// } | ||
| 197 | -// s[14] = "4"; | ||
| 198 | -// s[19] = hexDigits.substr((s[19] & 0x3) | 0x8, 1); | ||
| 199 | -// s[8] = s[13] = s[18] = s[23] = "-"; | ||
| 200 | 243 | ||
| 201 | -// var uuid = s.join(""); | 244 | +const onChange = ({ fileList }) => { |
| 202 | -// return uuid; | 245 | +} |
| 203 | -// }; | ||
| 204 | 246 | ||
| 205 | -// 上传图片返回图片URL | 247 | +/********** 上传七牛云获取文件地址 ***********/ |
| 206 | -const handleUpload = async (files) => { | 248 | +const loading = ref(false); |
| 249 | +const formCode = getUrlParams(location.href) ? getUrlParams(location.href).code : ''; // 表单code | ||
| 250 | + | ||
| 251 | +// 上传文件返回文件URL | ||
| 252 | +const handleUpload = async (file) => { | ||
| 207 | loading.value = true; | 253 | loading.value = true; |
| 208 | - // 获取HASH值 | ||
| 209 | - // const hash = getEtag(files.content); | ||
| 210 | return new Promise((resolve, reject) => { | 254 | return new Promise((resolve, reject) => { |
| 211 | // 获取MD5值 | 255 | // 获取MD5值 |
| 212 | const bmf = new BMF(); | 256 | const bmf = new BMF(); |
| 213 | bmf.md5( | 257 | bmf.md5( |
| 214 | - files.file, | 258 | + file, |
| 215 | async (err, md5) => { | 259 | async (err, md5) => { |
| 216 | if (err) { | 260 | if (err) { |
| 217 | console.log(err); | 261 | console.log(err); |
| 218 | reject(err); | 262 | reject(err); |
| 219 | } | 263 | } |
| 220 | // 获取七牛token | 264 | // 获取七牛token |
| 221 | - const filename = files.file.name; // 真实文件名 | 265 | + const filename = file.name; // 真实文件名 |
| 222 | const getToken = await qiniuTokenAPI({ | 266 | const getToken = await qiniuTokenAPI({ |
| 223 | name: filename, | 267 | name: filename, |
| 224 | hash: md5, | 268 | hash: md5, |
| ... | @@ -227,10 +271,8 @@ const handleUpload = async (files) => { | ... | @@ -227,10 +271,8 @@ const handleUpload = async (files) => { |
| 227 | let imgUrl = ""; | 271 | let imgUrl = ""; |
| 228 | // 第一次上传 | 272 | // 第一次上传 |
| 229 | if (getToken.token) { | 273 | if (getToken.token) { |
| 230 | - files.status = "uploading"; | 274 | + // 返回数据库真实文件地址 |
| 231 | - files.message = "上传中..."; | 275 | + imgUrl = await uploadQiniu(file, getToken.token, filename, md5); |
| 232 | - // 返回数据库真实图片地址 | ||
| 233 | - imgUrl = await uploadQiniu(files.file, getToken.token, filename, md5); | ||
| 234 | } | 276 | } |
| 235 | // 重复上传 | 277 | // 重复上传 |
| 236 | if (getToken.data) { | 278 | if (getToken.data) { |
| ... | @@ -245,39 +287,7 @@ const handleUpload = async (files) => { | ... | @@ -245,39 +287,7 @@ const handleUpload = async (files) => { |
| 245 | }); | 287 | }); |
| 246 | }; | 288 | }; |
| 247 | 289 | ||
| 248 | -// 多选图片上传遍历 | 290 | +// 生成数据库真实文件地址 |
| 249 | -var muliUpload = async (files) => { | ||
| 250 | - for (let item of files) { | ||
| 251 | - const res = await handleUpload(item); | ||
| 252 | - // 上传失败提示 | ||
| 253 | - if (!res.src) { | ||
| 254 | - item.status = "failed"; | ||
| 255 | - item.message = "上传失败"; | ||
| 256 | - loading.value = false; | ||
| 257 | - } else { | ||
| 258 | - item.status = ""; | ||
| 259 | - item.message = ""; | ||
| 260 | - fileList.value.push({ | ||
| 261 | - // meta_id: res.meta_id, | ||
| 262 | - name: item.file.name, | ||
| 263 | - url: res.src, | ||
| 264 | - // isImage: true, | ||
| 265 | - }); | ||
| 266 | - loading.value = false; | ||
| 267 | - } | ||
| 268 | - } | ||
| 269 | -}; | ||
| 270 | - | ||
| 271 | - | ||
| 272 | - | ||
| 273 | -const getType = (file, name) => { | ||
| 274 | - var index1 = name.lastIndexOf("."); | ||
| 275 | - var index2 = file.length; | ||
| 276 | - var type = file.substring(index1, index2).toUpperCase(); | ||
| 277 | - return type; | ||
| 278 | -} | ||
| 279 | - | ||
| 280 | -// 生成数据库真实图片地址 | ||
| 281 | const uploadQiniu = async (file, token, name, md5) => { | 291 | const uploadQiniu = async (file, token, name, md5) => { |
| 282 | let suffix = /\.[^\.]+$/.exec(name); // 获取后缀 | 292 | let suffix = /\.[^\.]+$/.exec(name); // 获取后缀 |
| 283 | // let affix = uuidv4(); | 293 | // let affix = uuidv4(); |
| ... | @@ -289,27 +299,32 @@ const uploadQiniu = async (file, token, name, md5) => { | ... | @@ -289,27 +299,32 @@ const uploadQiniu = async (file, token, name, md5) => { |
| 289 | let config = { | 299 | let config = { |
| 290 | headers: { "Content-Type": "multipart/form-data" }, | 300 | headers: { "Content-Type": "multipart/form-data" }, |
| 291 | }; | 301 | }; |
| 292 | - // 自拍图片上传七牛服务器 | 302 | + // 自拍文件上传七牛服务器 |
| 293 | let qiniuUploadUrl; | 303 | let qiniuUploadUrl; |
| 294 | if (window.location.protocol === 'https:') { | 304 | if (window.location.protocol === 'https:') { |
| 295 | - qiniuUploadUrl = 'https://up.qbox.me'; | 305 | + qiniuUploadUrl = 'https://up.qbox.me'; |
| 296 | } else { | 306 | } else { |
| 297 | - qiniuUploadUrl = 'http://upload.qiniu.com'; | 307 | + qiniuUploadUrl = 'http://upload.qiniu.com'; |
| 298 | } | 308 | } |
| 299 | - const { filekey, hash, image_info } = await qiniuUploadAPI( | 309 | + const uploadData = await qiniuUploadAPI( |
| 300 | qiniuUploadUrl, | 310 | qiniuUploadUrl, |
| 301 | formData, | 311 | formData, |
| 302 | config | 312 | config |
| 303 | ); | 313 | ); |
| 304 | - if (filekey) { | 314 | + // 上传失败处理 |
| 305 | - // 保存图片 | 315 | + if (!uploadData) { |
| 316 | + loading.value = false; | ||
| 317 | + return false; | ||
| 318 | + } | ||
| 319 | + if (uploadData.filekey) { | ||
| 320 | + // 保存文件 | ||
| 306 | const { data } = await saveFileAPI({ | 321 | const { data } = await saveFileAPI({ |
| 307 | name, | 322 | name, |
| 308 | - filekey, | 323 | + filekey: uploadData.filekey, |
| 309 | hash: md5, | 324 | hash: md5, |
| 310 | // format: image_info.format, | 325 | // format: image_info.format, |
| 311 | - height: image_info.height, | 326 | + // height: image_info.height, |
| 312 | - width: image_info.width, | 327 | + // width: image_info.width, |
| 313 | }); | 328 | }); |
| 314 | return data; | 329 | return data; |
| 315 | } | 330 | } |
| ... | @@ -317,34 +332,40 @@ const uploadQiniu = async (file, token, name, md5) => { | ... | @@ -317,34 +332,40 @@ const uploadQiniu = async (file, token, name, md5) => { |
| 317 | 332 | ||
| 318 | /****************** END *******************/ | 333 | /****************** END *******************/ |
| 319 | 334 | ||
| 335 | +const show_error = ref(false); | ||
| 336 | +const error_msg = ref(''); | ||
| 337 | + | ||
| 320 | // 校验模块 | 338 | // 校验模块 |
| 321 | const validImageUploader = () => { | 339 | const validImageUploader = () => { |
| 322 | - // 必填项 未上传图片 | 340 | + // 必填项 未上传文件 |
| 323 | if (props.item.component_props.required && !fileList.value.length) { | 341 | if (props.item.component_props.required && !fileList.value.length) { |
| 324 | - show_empty.value = true; | 342 | + show_error.value = true; |
| 343 | + error_msg.value = '必填项不能为空' | ||
| 325 | } else { | 344 | } else { |
| 326 | - show_empty.value = false; | 345 | + show_error.value = false; |
| 346 | + error_msg.value = '' | ||
| 327 | } | 347 | } |
| 328 | - return !show_empty.value; | 348 | + return !show_error.value; |
| 329 | }; | 349 | }; |
| 330 | 350 | ||
| 331 | defineExpose({ validImageUploader }); | 351 | defineExpose({ validImageUploader }); |
| 332 | </script> | 352 | </script> |
| 333 | 353 | ||
| 334 | -<style lang="less" scoped> | 354 | +<style lang="less"> |
| 335 | .image-uploader-field { | 355 | .image-uploader-field { |
| 336 | .label { | 356 | .label { |
| 337 | - padding: 1rem 1rem 0 1rem; | 357 | + margin-left: 1rem; |
| 338 | - font-size: 0.9rem; | 358 | + padding-bottom: 20px; |
| 359 | + font-size: 26px; | ||
| 339 | font-weight: bold; | 360 | font-weight: bold; |
| 340 | 361 | ||
| 341 | - span { | 362 | + text { |
| 342 | color: red; | 363 | color: red; |
| 343 | } | 364 | } |
| 344 | } | 365 | } |
| 345 | 366 | ||
| 346 | .type-text { | 367 | .type-text { |
| 347 | - font-size: 0.9rem; | 368 | + font-size: 22px; |
| 348 | margin-left: 1rem; | 369 | margin-left: 1rem; |
| 349 | padding-bottom: 1rem; | 370 | padding-bottom: 1rem; |
| 350 | color: gray; | 371 | color: gray; | ... | ... |
| ... | @@ -8,7 +8,7 @@ import AreaPickerField from '@/components/AreaPickerField/index.vue' | ... | @@ -8,7 +8,7 @@ import AreaPickerField from '@/components/AreaPickerField/index.vue' |
| 8 | import DatePickerField from '@/components/DatePickerField/index.vue' | 8 | import DatePickerField from '@/components/DatePickerField/index.vue' |
| 9 | import TimePickerField from '@/components/TimePickerField/index.vue' | 9 | import TimePickerField from '@/components/TimePickerField/index.vue' |
| 10 | import DateTimePickerField from '@/components/DateTimePickerField/index.vue' | 10 | import DateTimePickerField from '@/components/DateTimePickerField/index.vue' |
| 11 | -// import ImageUploaderField from '@/components/ImageUploaderField/index.vue' | 11 | +import ImageUploaderField from '@/components/ImageUploaderField/index.vue' |
| 12 | import FileUploaderField from '@/components/FileUploaderField/index.vue' | 12 | import FileUploaderField from '@/components/FileUploaderField/index.vue' |
| 13 | import PhoneField from '@/components/PhoneField/index.vue' | 13 | import PhoneField from '@/components/PhoneField/index.vue' |
| 14 | import EmailField from '@/components/EmailField/index.vue' | 14 | import EmailField from '@/components/EmailField/index.vue' |
| ... | @@ -105,9 +105,9 @@ export function createComponentType(data) { | ... | @@ -105,9 +105,9 @@ export function createComponentType(data) { |
| 105 | if (item.component_props.tag === 'datetime') { | 105 | if (item.component_props.tag === 'datetime') { |
| 106 | item.component = DateTimePickerField | 106 | item.component = DateTimePickerField |
| 107 | } | 107 | } |
| 108 | - // if (item.component_props.tag === 'image_uploader') { | 108 | + if (item.component_props.tag === 'image_uploader') { |
| 109 | - // item.component = ImageUploaderField | 109 | + item.component = ImageUploaderField |
| 110 | - // } | 110 | + } |
| 111 | if (item.component_props.tag === 'file_uploader') { | 111 | if (item.component_props.tag === 'file_uploader') { |
| 112 | item.component = FileUploaderField | 112 | item.component = FileUploaderField |
| 113 | } | 113 | } | ... | ... |
-
Please register or login to post a comment