src/components/FYImageSelectDialog.vue
@@ -1,62 +1,85 @@
<template>
  <el-dialog
    v-model="anyPhotoDialog"
    width="66%"
    title="任意图片"
    :model-value="dialogVisible"
    @opened="handleOpen"
    @closed="handleClose"
    top="5vh"
    width="68%"
    destroy-on-close
    :close-on-press-escape="false"
  >
    <div class="main">
      <el-row justify="end" class="btns" v-if="!readonly">
        <el-text size="small" type="info" class="m-r-8"
          >最多选择{{ maxSelect }}张图片</el-text
        >
        <el-button
          size="small"
          type="primary"
          @click="sendSelectedImg(true)"
          :disabled="selectedImgUrlList.length == 0"
          >确定</el-button
        >
        <el-button size="small" type="primary" @click="sendSelectedImg(false)"
          >取消</el-button
        >
      </el-row>
    <!-- <div class="main"> -->
    <el-row justify="end" v-if="!readonly">
      <el-text size="small" type="info" class="m-r-8"
        >最多选择{{ maxSelect }}张图片</el-text
      >
      <el-button
        size="small"
        type="primary"
        @click="handleSubmit"
        :disabled="selectedImgUrlList.length == 0"
        >确定</el-button
      >
      <el-button size="small" type="primary" @click="handleCancel"
        >取消</el-button
      >
    </el-row>
      <div class="center">
        <el-tabs v-if="typeList.length > 0" v-model="activeId" type="card">
          <el-tab-pane
            v-for="item in typeList"
            :key="item.typeId"
            :label="
              item.typeName + ' (' + typeImgMap.get(activeId).length + ')'
            "
            :name="item.typeId"
          >
          </el-tab-pane>
        </el-tabs>
        <el-empty v-if="isEmpty" description="暂无记录" />
        <el-scrollbar class="imgs">
    <div class="center">
      <el-tabs v-if="typeList.length > 0" v-model="activeId" type="card">
        <el-tab-pane
          v-for="item in typeList"
          :key="item.typeId"
          :label="
            item.typeName + ' (' + typeImgMap.get(item.typeId).length + ')'
          "
          :name="item.typeId"
        >
        </el-tab-pane>
      </el-tabs>
      <el-scrollbar height="70vh">
        <div
          v-if="typeImgMap.get(activeId) && typeImgMap.get(activeId).length > 0"
          class="imgs"
        >
          <el-image
            v-loading="img.loading"
            v-for="(img, i) in typeImgMap.get(activeId)"
            :key="i"
            :class="[img.isSelect ? 'selected' : 'noActive', 'image']"
            fit="cover"
            :src="img.url"
            lazy
            :preview-src-list="readonly ? typeImgMap.get(activeId).map((v) => v.url) : []"
            :initial-index="i"
            @click="onSelect(img, i)"
            @load="onOneImgLoadSuccess(img)"
            @error="onOneImgLoadError(img)"
          />
        </el-scrollbar>
      </div>
        </div>
        <el-row v-else justify="space-between">
          <el-empty description="暂无记录" />
        </el-row>
      </el-scrollbar>
    </div>
    <!-- </div> -->
  </el-dialog>
</template>
<script setup>
import { ref, watch } from 'vue';
import { ref, watch, computed, onMounted, onUnmounted } from 'vue';
const props = defineProps({
  dialogVisible: Boolean,
  /**
   * 图片分类
   * 结构{ typeId, typeName }
   */
  typeList: {
    type: Array,
    default: () => []
  },
  typeImgMap: {
    type: Array,
    default: () => new Map()
  },
  // 是否以只读的形式查看当前页面
  readonly: {
@@ -74,26 +97,228 @@
  }
});
const activeId = ref('');
const typeImgMap = ref(new Map());
const selectedImgList = ref([]);
const emit = defineEmits(['submit', 'cancel', 'update:dialogVisible']);
watch(typeImgMap, (newMap, oldMap) => {
  if (newMap.get(activeId.value) == undefined) {
    return;
const activeId = ref('');
// const typeImgMap = ref(new Map());
const selectedImgUrlList = ref([]);
let loadedImgCount = ref(0);
// 加载状态
const loading = computed(() => {
  if (activeId.value == '') {
    return false;
  }
  newMap.get(activeId.value).forEach(
    (i) => {
      if (i.isSelect == true) {
        return;
      }
      props.defaultFile.forEach((imgItem) => {
        if (imgItem.url == i.url) {
          i.isSelect = true;
        }
      });
    },
    { deep: true }
  // 保证最开始是加载状态,三分之一加载之后停止展示加载状态
  return !(
    props.typeImgMap.get(activeId.value).length / 3 <=
    loadedImgCount.value
  );
});
function onOneImgLoadError(img) {
  img.loading = false;
  loadedImgCount.value++;
}
function onOneImgLoadSuccess(img) {
  img.loading = false;
  loadedImgCount.value++;
}
watch(
  () => activeId.value,
  (nV, oV) => {
    loadedImgCount.value = 0;
  },
  { immediate: true }
);
function onSelect(img, i) {
  if (props.readonly) {
    return;
  }
  const imgList = selectedImgUrlList.value;
  const index = imgList.indexOf(img);
  if (index == -1) {
    if (props.maxSelect == 1) {
      img.isSelect = true;
      imgList.push(img);
      if (imgList.length > 1) {
        imgList.splice(0, 1).forEach((e) => {
          e.isSelect = false;
        });
      }
    } else if (props.maxSelect > 1) {
      if (imgList.length < props.maxSelect) {
        img.isSelect = true;
        imgList.push(img);
      }
    }
  } else {
    imgList.splice(index, 1);
    img.isSelect = false;
  }
}
function handleOpen() {
  // if (props.typeImgMap.get(activeId.value) == undefined) {
  //     return;
  //   }
  //   props.typeImgMap.get(activeId.value).forEach((i) => {
  //     if (i.isSelect == true) {
  //       return;
  //     }
  //     props.defaultFile.forEach((imgItem) => {
  //       if (imgItem.url == i.url) {
  //         i.isSelect = true;
  //         selectedImgUrlList.value.push(i);
  //       }
  //     });
  //   });
  emit('update:dialogVisible', true);
}
function handleClose() {
  selectedImgUrlList.value.forEach((item) => (item.isSelect = false));
  selectedImgUrlList.value = [];
  emit('update:dialogVisible', false);
}
function handleSubmit() {
  emit('submit', selectedImgUrlList.value);
  emit('update:dialogVisible', false);
}
function handleCancel() {
  emit('cancel');
  emit('update:dialogVisible', false);
}
watch(
  () => props.typeList,
  (nV, oV) => {
    if (nV != oV && nV.length > 0) {
      activeId.value = nV[0].typeId;
    }
  },
  { immediate: true }
);
// watch(
//   () => props.defaultFile,
//   (nV, oV) => {
//     if (props.typeImgMap.get(activeId.value) == undefined) {
//       return;
//     }
//     props.typeImgMap.get(activeId.value).forEach((i) => {
//       if (i.isSelect == true) {
//         return;
//       }
//       nV.forEach((imgItem) => {
//         if (imgItem.url == i.url) {
//           i.isSelect = true;
//           selectedImgUrlList.value.push(i);
//         }
//       });
//     });
//   },
//   { deep: true, immediate: true }
// );
// watch(
//   () => props.typeImgMap,
//   (newMap, oldMap) => {
//     if (newMap.get(activeId.value) == undefined) {
//       return;
//     }
//     newMap.get(activeId.value).forEach((i) => {
//       if (i.isSelect == true) {
//         return;
//       }
//       props.defaultFile.forEach((imgItem) => {
//         if (imgItem.url == i.url) {
//           i.isSelect = true;
//           selectedImgUrlList.value.push(i);
//         }
//       });
//     });
//   },
//   { immediate: true }
// );
</script>
<style scoped>
.center {
  display: flex;
  flex-direction: column;
  align-items: center;
}
.text {
  padding: 20px;
}
.main {
  /* 使父元素居中 */
  /* margin: 0 auto;  */
  /* width: 100%; */
}
.imgs {
  width: 100%;
  /* border-style:solid;
    border-radius: 1px; */
  /* height: 100%; */
  flex-grow: 1 !important;
  overflow-y: auto !important;
  /* 内容的内边距 */
  display: flex !important;
  flex-wrap: wrap !important;
  /* overflow: hidden; */
}
.image {
  margin: 5px;
  height: 250px;
  width: 240px;
  border-radius: 4px;
}
.selected {
  margin: 3px;
  color: #4abe84;
  box-shadow: 0 2px 7px 0 rgba(85, 110, 97, 0.35);
  border: 2px solid rgba(74, 190, 132, 1);
}
.selected:before {
  content: '';
  position: absolute;
  right: 0;
  bottom: 0;
  border: 17px solid #4abe84;
  border-top-color: transparent;
  border-left-color: transparent;
}
.selected:after {
  content: '';
  width: 5px;
  height: 12px;
  position: absolute;
  right: 6px;
  bottom: 6px;
  border: 2px solid #fff;
  border-top-color: transparent;
  border-left-color: transparent;
  transform: rotate(45deg);
}
.noActive {
  /* padding: 5px; */
}
.blurry {
  filter: blur(3px);
}
.filters {
  display: flex;
  padding: 5px;
}
::v-deep .el-dialog__body {
  padding: 10px calc(var(--el-dialog-padding-primary) + 10px) !important;
}
</style>