| | |
| | | <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-row justify="end"> |
| | | <el-text v-if="onContextMenu != undefined" size="small" type="info">{{ |
| | | `(${contextMenuStr})` |
| | | }}</el-text> |
| | | <div 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)" |
| | | @click="handleSubmit" |
| | | :disabled="selectedImgUrlList.length == 0" |
| | | >确定</el-button |
| | | > |
| | | <el-button size="small" type="primary" @click="sendSelectedImg(false)" |
| | | <el-button size="small" type="primary" @click="handleCancel" |
| | | >取消</el-button |
| | | > |
| | | </el-row> |
| | | </div> |
| | | </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) : [] |
| | | " |
| | | crossOrigin="Anonymous" |
| | | :initial-index="i" |
| | | @contextmenu="(e) => showContextMenu(e, 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> |
| | | </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: { |
| | | type: Boolean, |
| | | default: false |
| | | }, |
| | | defaultFile: { |
| | | type: Array, |
| | | default: () => [] |
| | | }, |
| | | // 图片可选数量,当传入数字时,代表图片数量 |
| | | maxSelect: { |
| | | type: Number, |
| | | default: 3 |
| | | }, |
| | | // 图片右键点击事件 |
| | | onContextMenu: { |
| | | type: Function |
| | | }, |
| | | contextMenuStr: { |
| | | type: String, |
| | | default: '右键点击图片触发额外操作' |
| | | } |
| | | }); |
| | | |
| | | const emit = defineEmits(['submit', 'cancel', 'update:dialogVisible']); |
| | | |
| | | const activeId = ref(''); |
| | | const typeImgMap = ref(new Map()); |
| | | const selectedImgList = ref([]); |
| | | |
| | | watch(typeImgMap, (newMap, oldMap) => { |
| | | if (newMap.get(activeId.value) == undefined) { |
| | | return; |
| | | 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() { |
| | | 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); |
| | | } |
| | | |
| | | // 图片右键点击时间 |
| | | function showContextMenu(event, index) { |
| | | if (props.onContextMenu) { |
| | | event.preventDefault(); |
| | | props.onContextMenu(event, activeId.value, index); |
| | | } |
| | | } |
| | | |
| | | watch( |
| | | () => props.typeList, |
| | | (nV, oV) => { |
| | | if (nV != oV && nV.length > 0) { |
| | | activeId.value = nV[0].typeId; |
| | | } |
| | | }, |
| | | { 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> |