| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312 |
- <template>
- <div class="upload-wrap">
- <div class="tips tips-text" v-if="tipText?.length > 0">
- <span>{{ tipText }}</span>
- </div>
- <div class="tips" v-if="!textShow">
- <el-icon class="icon" size="16">
- <Warning />
- </el-icon>
- <span>上传照片可精准预测最佳病虫防治时间</span>
- </div>
- <div class="upload-content">
- <img v-if="exampleImg" @click="showExample" class="example" src="@/assets/img/home/example-4.png" alt="" />
- <uploader class="uploader" :class="{ 'uploader-list': exampleImg }" v-model="fileList"
- :multiple="props.maxCount > 1" :max-count="props.maxCount" :after-read="afterRead" @delete="deleteImg">
- <template v-if="exampleImg">
- <slot v-if="!fileList.length"></slot>
- <img class="plus" v-else src="@/assets/img/home/plus.png" alt="">
- </template>
- <img class="plus" v-else src="@/assets/img/home/plus.png" alt="">
- </uploader>
- </div>
- </div>
- <!-- 示例照片 -->
- <popup v-model:show="showExamplePopup" overlay-class="example-overlay" class="example-popup">
- <div class="example-content">
- <!-- <img src="@/assets/img/home/example-4.png" alt="" /> -->
- <img class="example-img"
- :src="exampleImgData || 'https://birdseye-img-ali-cdn.sysuimars.com/birdseye-look-mini/94379/1768801082504.png'"
- alt="" />
- <div class="example-tips">
- 拍摄要求:请采集代表农场作物物候期的照片,请采集代表农场作物物候期的照片。
- </div>
- </div>
- </popup>
- </template>
- <script setup>
- import { onMounted, ref, watch } from "vue";
- import { Uploader, Popup } from "vant";
- import eventBus from "@/api/eventBus";
- import { base_img_url2 } from "@/api/config";
- import { getFileExt } from "@/utils/util";
- import { ElMessage } from "element-plus";
- import UploadFile from "@/utils/upliadFile";
- import 'vant/lib/uploader/style';
- import { useStore } from "vuex";
- const props = defineProps({
- tipText: {
- type: String,
- default: ''
- },
- fullPath: {
- type: Boolean,
- default: true
- },
- textShow: {
- type: Boolean,
- default: true
- },
- exampleImg: {
- type: Boolean,
- default: false
- },
- maxCount: {
- type: Number,
- default: 3
- },
- // 外部传入已上传的图片(兼容回显,不影响原有用法)
- // 数组内容为后端保存的相对路径,与 emit 出去的 imgArr 一致
- initImgArr: {
- type: Array,
- default: () => []
- }
- })
- const store = useStore();
- const miniUserId = store.state.home.miniUserId;
- const emit = defineEmits(['handleUpload'])
- //上传照片
- const fileList = ref([]);
- const fileArr = ref([])
- const imgArr = ref([])
- const uploadFileObj = new UploadFile();
- const showExamplePopup = ref(false);
- const exampleImgData = ref(null);
- const showExample = (example) => {
- exampleImgData.value = example;
- showExamplePopup.value = true;
- };
- const afterRead = async (files) => {
- if (!Array.isArray(files)) {
- files = [files];
- }
- // 如果 maxCount 为 1,先清空之前的图片数组
- if (props.maxCount === 1) {
- fileArr.value = [];
- imgArr.value = [];
- }
- for (let file of files) {
- // 将文件上传至服务器
- let fileVal = file.file;
- file.status = "uploading";
- file.message = "上传中...";
- let ext = getFileExt(fileVal.name);
- let key = `birdseye-look-mini/${miniUserId}/${new Date().getTime()}.${ext}`;
- let resFilename = await uploadFileObj.put(key, fileVal)
- file.status = "done";
- file.message = "";
- if (resFilename) {
- fileArr.value.push(props.fullPath ? base_img_url2 + resFilename : resFilename)
- imgArr.value.push(resFilename)
- eventBus.emit('upload:change', fileArr.value)
- eventBus.emit('upload:changeArr', imgArr.value)
- emit('handleUpload', { imgArr: imgArr.value, fileList: fileArr.value })
- } else {
- fileList.value.pop()
- file.status = 'failed';
- file.message = '上传失败';
- ElMessage.error('图片上传失败,请稍后再试!')
- }
- }
- };
- const deleteImg = (file, e) => {
- fileArr.value.splice(e.index, 1)
- imgArr.value.splice(e.index, 1)
- eventBus.emit('upload:change', fileArr.value)
- eventBus.emit('upload:changeArr', imgArr.value)
- emit('handleUpload', { imgArr: imgArr.value, fileList: fileArr.value })
- }
- // 外部传入的已上传图片回显处理(只做补充,不改变原有上传逻辑)
- watch(
- () => props.initImgArr,
- (val) => {
- if (!val || val.length === 0) {
- return;
- }
- // 重置本地状态,使用外部传入的图片重新构建
- fileList.value = [];
- fileArr.value = [];
- imgArr.value = [];
- val.forEach((item) => {
- // item 为后端相对路径,和组件内部 imgArr 一致
- const url = props.fullPath ? base_img_url2 + item : item;
- fileArr.value.push(url);
- imgArr.value.push(item);
- // Vant Uploader 预览所需结构
- fileList.value.push({
- url,
- isImage: true,
- });
- });
- // 同步给外部一次,保证父组件拿到的 imgArr/fileList 跟展示一致
- eventBus.emit('upload:change', fileArr.value);
- eventBus.emit('upload:changeArr', imgArr.value);
- emit('handleUpload', { imgArr: imgArr.value, fileList: fileArr.value });
- },
- { immediate: true ,deep: true}
- );
- function uploadReset() {
- fileList.value = []
- fileArr.value = []
- imgArr.value = []
- }
- defineExpose({
- uploadReset
- })
- onMounted(() => {
- eventBus.off('upload:reset', uploadReset)
- eventBus.on('upload:reset', uploadReset)
- })
- </script>
- <style lang="scss" scoped>
- .upload-wrap {
- position: relative;
- .upload-content {
- display: flex;
- }
- .tips {
- display: flex;
- align-items: center;
- color: #2199F8;
- background: #E9F5FF;
- border-radius: 4px;
- padding: 2px 10px;
- box-sizing: border-box;
- margin-bottom: 10px;
- .icon {
- margin-right: 2px;
- }
- }
- .tips-text {
- background: linear-gradient(240deg, #FFFFFF 22%, rgba(33, 153, 248, 0.2) 100%);
- border-radius: 20px 0 0 20px;
- }
- ::v-deep {
- .van-uploader__input-wrapper {
- text-align: center;
- }
- .el-select__wrapper:hover {
- box-shadow: 0 0 0 1px #dcdfe6 inset;
- }
- .avatar-uploader .el-upload {
- width: 100%;
- border: 1px dashed #dddddd;
- border-radius: 6px;
- cursor: pointer;
- position: relative;
- overflow: hidden;
- }
- .el-icon.avatar-uploader-icon {
- font-size: 28px;
- color: #8c939d;
- width: 100%;
- height: 128px;
- text-align: center;
- background: #f6f6f6;
- }
- }
- .uploader {
- .plus,
- .example {
- width: calc((100vw - 68px) / 4);
- height: calc((100vw - 68px) / 4);
- }
- ::v-deep {
- .van-uploader__wrapper {
- --van-uploader-size: 76.7px;
- --van-padding-xs: 6px;
- }
- }
- }
- .uploader-list {
- ::v-deep {
- .van-uploader__wrapper {
- >div:first-child {
- margin-left: calc((100vw - 68px) / 4 + 8px);
- }
- }
- .van-uploader__preview-image {
- width: calc((100vw - 68px) / 4);
- height: calc((100vw - 68px) / 4);
- }
- }
- }
- .example {
- width: calc((100vw - 68px) / 4);
- height: calc((100vw - 68px) / 4);
- margin: 0 12px 8px 0;
- position: absolute;
- z-index: 2;
- top: 0;
- left: 0;
- }
- }
- .example-popup {
- width: 100%;
- border-radius: 0;
- background: none;
- max-width: 100%;
- .example-content {
- text-align: center;
- .example-img {
- width: 100%;
- }
- }
- .example-tips {
- margin: 16px 12px 6px 12px;
- background: #3d3d3d;
- padding: 8px 10px;
- border-radius: 4px;
- backdrop-filter: blur(4px);
- color: #fff;
- font-size: 14px;
- line-height: 21px;
- text-align: left;
- }
- }
- </style>
|