upload.vue 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318
  1. <template>
  2. <div class="upload-wrap">
  3. <div class="tips tips-text" v-if="tipText?.length > 0">
  4. <span>{{ tipText }}</span>
  5. </div>
  6. <div class="tips" v-if="!textShow">
  7. <el-icon class="icon" size="16">
  8. <Warning />
  9. </el-icon>
  10. <span>上传照片可精准预测最佳病虫防治时间</span>
  11. </div>
  12. <div class="upload-content">
  13. <img v-if="exampleImg" @click="showExample" class="example" src="@/assets/img/home/example-4.png" alt="" />
  14. <div class="example-list-wrapper" v-if="exampleList.length > 0">
  15. <div class="example-list">
  16. <div class="image-item-wrapper" v-for="example in exampleList" :key="example" @click="showExample(example)">
  17. <img class="image-item" :src="example" alt="" />
  18. </div>
  19. </div>
  20. </div>
  21. <uploader class="uploader" :class="{ 'uploader-list': exampleImg }" v-model="fileList"
  22. :multiple="props.maxCount > 1" :max-count="props.maxCount" :after-read="afterRead" @delete="deleteImg">
  23. <template v-if="exampleImg">
  24. <slot v-if="!fileList.length"></slot>
  25. <img class="plus" v-else src="@/assets/img/home/plus.png" alt="">
  26. </template>
  27. <img class="plus" v-else src="@/assets/img/home/plus.png" alt="">
  28. </uploader>
  29. </div>
  30. </div>
  31. <!-- 示例照片 -->
  32. <popup v-model:show="showExamplePopup" overlay-class="example-overlay" class="example-popup">
  33. <div class="example-content">
  34. <!-- <img src="@/assets/img/home/example-4.png" alt="" /> -->
  35. <img class="example-img"
  36. :src="exampleImgData || 'https://birdseye-img-ali-cdn.sysuimars.com/birdseye-look-mini/94379/1768801082504.png'"
  37. alt="" />
  38. <div class="example-tips">
  39. 拍摄要求:请采集代表农场作物物候期的照片,请采集代表农场作物物候期的照片。
  40. </div>
  41. </div>
  42. </popup>
  43. </template>
  44. <script setup>
  45. import { onMounted, ref } from "vue";
  46. import { Uploader, Popup } from "vant";
  47. import eventBus from "@/api/eventBus";
  48. import { base_img_url2 } from "@/api/config";
  49. import { getFileExt } from "@/utils/util";
  50. import { ElMessage } from "element-plus";
  51. import UploadFile from "@/utils/upliadFile";
  52. import 'vant/lib/uploader/style';
  53. import { useStore } from "vuex";
  54. const props = defineProps({
  55. tipText: {
  56. type: String,
  57. default: ''
  58. },
  59. fullPath: {
  60. type: Boolean,
  61. default: true
  62. },
  63. textShow: {
  64. type: Boolean,
  65. default: true
  66. },
  67. exampleImg: {
  68. type: Boolean,
  69. default: false
  70. },
  71. maxCount: {
  72. type: Number,
  73. default: 3
  74. },
  75. exampleList: {
  76. type: Array,
  77. default: () => []
  78. }
  79. })
  80. const store = useStore();
  81. const miniUserId = store.state.home.miniUserId;
  82. const emit = defineEmits(['handleUpload'])
  83. //上传照片
  84. const fileList = ref([]);
  85. const fileArr = ref([])
  86. const imgArr = ref([])
  87. const uploadFileObj = new UploadFile();
  88. const showExamplePopup = ref(false);
  89. const exampleImgData = ref(null);
  90. const showExample = (example) => {
  91. exampleImgData.value = example;
  92. showExamplePopup.value = true;
  93. };
  94. const afterRead = async (files) => {
  95. if (!Array.isArray(files)) {
  96. files = [files];
  97. }
  98. // 如果 maxCount 为 1,先清空之前的图片数组
  99. if (props.maxCount === 1) {
  100. fileArr.value = [];
  101. imgArr.value = [];
  102. }
  103. for (let file of files) {
  104. // 将文件上传至服务器
  105. let fileVal = file.file;
  106. file.status = "uploading";
  107. file.message = "上传中...";
  108. let ext = getFileExt(fileVal.name);
  109. let key = `birdseye-look-mini/${miniUserId}/${new Date().getTime()}.${ext}`;
  110. let resFilename = await uploadFileObj.put(key, fileVal)
  111. file.status = "done";
  112. file.message = "";
  113. if (resFilename) {
  114. fileArr.value.push(props.fullPath ? base_img_url2 + resFilename : resFilename)
  115. imgArr.value.push(resFilename)
  116. eventBus.emit('upload:change', fileArr.value)
  117. eventBus.emit('upload:changeArr', imgArr.value)
  118. emit('handleUpload', { imgArr: imgArr.value, fileList: fileArr.value })
  119. } else {
  120. fileList.value.pop()
  121. file.status = 'failed';
  122. file.message = '上传失败';
  123. ElMessage.error('图片上传失败,请稍后再试!')
  124. }
  125. }
  126. };
  127. const deleteImg = (file, e) => {
  128. fileArr.value.splice(e.index, 1)
  129. imgArr.value.splice(e.index, 1)
  130. eventBus.emit('upload:change', fileArr.value)
  131. eventBus.emit('upload:changeArr', imgArr.value)
  132. emit('handleUpload', { imgArr: imgArr.value, fileList: fileArr.value })
  133. }
  134. function uploadReset() {
  135. fileList.value = []
  136. fileArr.value = []
  137. imgArr.value = []
  138. }
  139. defineExpose({
  140. uploadReset
  141. })
  142. onMounted(() => {
  143. eventBus.off('upload:reset', uploadReset)
  144. eventBus.on('upload:reset', uploadReset)
  145. })
  146. </script>
  147. <style lang="scss" scoped>
  148. .upload-wrap {
  149. position: relative;
  150. .upload-content {
  151. display: flex;
  152. .example-list-wrapper {
  153. display: inline-block;
  154. }
  155. .example-list {
  156. display: flex;
  157. align-items: center;
  158. .image-item-wrapper {
  159. position: relative;
  160. margin-right: 6px;
  161. &::after {
  162. content: '示例';
  163. position: absolute;
  164. top: 0;
  165. left: 0;
  166. background: rgba(0, 0, 0, 0.7);
  167. color: #F2F4F5;
  168. padding: 2px 6px;
  169. border-radius: 8px 0 2px 0;
  170. font-size: 10px;
  171. z-index: 1;
  172. }
  173. }
  174. .image-item {
  175. width: calc((100vw - 68px) / 4);
  176. height: calc((100vw - 68px) / 4);
  177. border-radius: 8px;
  178. object-fit: cover;
  179. }
  180. }
  181. }
  182. .tips {
  183. display: flex;
  184. align-items: center;
  185. color: #2199F8;
  186. background: #E9F5FF;
  187. border-radius: 4px;
  188. padding: 2px 10px;
  189. box-sizing: border-box;
  190. margin-bottom: 10px;
  191. .icon {
  192. margin-right: 2px;
  193. }
  194. }
  195. .tips-text {
  196. background: linear-gradient(240deg, #FFFFFF 22%, rgba(33, 153, 248, 0.2) 100%);
  197. border-radius: 20px 0 0 20px;
  198. }
  199. ::v-deep {
  200. .van-uploader__input-wrapper {
  201. text-align: center;
  202. }
  203. .el-select__wrapper:hover {
  204. box-shadow: 0 0 0 1px #dcdfe6 inset;
  205. }
  206. .avatar-uploader .el-upload {
  207. width: 100%;
  208. border: 1px dashed #dddddd;
  209. border-radius: 6px;
  210. cursor: pointer;
  211. position: relative;
  212. overflow: hidden;
  213. }
  214. .el-icon.avatar-uploader-icon {
  215. font-size: 28px;
  216. color: #8c939d;
  217. width: 100%;
  218. height: 128px;
  219. text-align: center;
  220. background: #f6f6f6;
  221. }
  222. }
  223. .uploader {
  224. .plus,
  225. .example {
  226. width: calc((100vw - 68px) / 4);
  227. height: calc((100vw - 68px) / 4);
  228. }
  229. ::v-deep {
  230. .van-uploader__wrapper {
  231. --van-uploader-size: 76.7px;
  232. --van-padding-xs: 6px;
  233. }
  234. }
  235. }
  236. .uploader-list {
  237. ::v-deep {
  238. .van-uploader__wrapper {
  239. >div:first-child {
  240. margin-left: calc((100vw - 68px) / 4 + 8px);
  241. }
  242. }
  243. .van-uploader__preview-image {
  244. width: calc((100vw - 68px) / 4);
  245. height: calc((100vw - 68px) / 4);
  246. }
  247. }
  248. }
  249. .example {
  250. width: calc((100vw - 68px) / 4);
  251. height: calc((100vw - 68px) / 4);
  252. margin: 0 12px 8px 0;
  253. position: absolute;
  254. z-index: 2;
  255. top: 0;
  256. left: 0;
  257. }
  258. }
  259. .example-popup {
  260. width: 100%;
  261. border-radius: 0;
  262. background: none;
  263. max-width: 100%;
  264. .example-content {
  265. text-align: center;
  266. .example-img {
  267. width: 100%;
  268. }
  269. }
  270. .example-tips {
  271. margin: 16px 12px 6px 12px;
  272. background: #3d3d3d;
  273. padding: 8px 10px;
  274. border-radius: 4px;
  275. backdrop-filter: blur(4px);
  276. color: #fff;
  277. font-size: 14px;
  278. line-height: 21px;
  279. text-align: left;
  280. }
  281. }
  282. </style>