aabbcc/src/views/photoGallery.vue
2024-06-17 16:48:19 +08:00

400 lines
11 KiB
Vue

<template>
<div class="photo-gallery">
<header>
<navigationBar />
</header>
<div class="upload-button">
<button @click="openModal">添加照片</button>
</div>
<div v-if="showModal" class="modal" @click.self="closeModal">
<div class="modal-content">
<span class="close" @click="closeModal">&times;</span>
<form @submit.prevent="addPhoto">
<div class="form-group">
<label for="photo-upload">上传照片</label>
<input type="file" id="photo-upload" @change="onFileChange" required />
</div>
<div class="form-group">
<label for="photo-description">照片描述</label>
<input type="text" id="photo-description" v-model="newPhoto.alt" placeholder="照片描述" required />
</div>
<button type="submit">上传照片</button>
</form>
</div>
</div>
<div class="gallery">
<div class="pointer" :style="pointerStyle"></div>
<div
class="gallery-item"
v-for="(item, index) in photos"
:key="index"
:class="item.sizeClass"
@mouseover="onMouseOver($event, item.sizeClass)"
@mouseleave="onMouseLeave"
@click="openImageViewer(item.src)"
ref="photoItems"
>
<img :data-src="item.src" :alt="item.alt" class="lazy" />
</div>
</div>
<div v-if="showImageViewer" class="image-viewer" @click.self="closeImageViewer">
<div class="image-viewer-content">
<span class="close" @click="closeImageViewer">&times;</span>
<img :src="currentImage" />
</div>
</div>
<musIc />
</div>
</template>
<script>
export default {
name: 'photoGallery',
data () {
return {
// 初始照片列表
photos: [
{ src: require('@/assets/images/789.jpg'), alt: 'Photo 1', sizeClass: 'medium' },
{ src: require('@/assets/images/789.jpg'), alt: 'Photo 2', sizeClass: 'large' },
{ src: require('@/assets/images/789.jpg'), alt: 'Photo 3', sizeClass: 'small' },
{ src: require('@/assets/images/789.jpg'), alt: 'Photo 4', sizeClass: 'medium' },
{ src: require('@/assets/images/789.jpg'), alt: 'Photo 5', sizeClass: 'large' },
{ src: require('@/assets/images/789.jpg'), alt: 'Photo 6', sizeClass: 'small' },
{ src: require('@/assets/images/789.jpg'), alt: 'Photo 7', sizeClass: 'medium' },
{ src: require('@/assets/images/789.jpg'), alt: 'Photo 8', sizeClass: 'large' },
{ src: require('@/assets/images/789.jpg'), alt: 'Photo 9', sizeClass: 'small' },
{ src: require('@/assets/images/789.jpg'), alt: 'Photo 10', sizeClass: 'medium' },
{ src: require('@/assets/images/789.jpg'), alt: 'Photo 11', sizeClass: 'large' },
{ src: require('@/assets/images/789.jpg'), alt: 'Photo 12', sizeClass: 'small' },
{ src: require('@/assets/images/789.jpg'), alt: 'Photo 13', sizeClass: 'medium' },
{ src: require('@/assets/images/789.jpg'), alt: 'Photo 14', sizeClass: 'large' },
{ src: require('@/assets/images/789.jpg'), alt: 'Photo 15', sizeClass: 'small' },
{ src: require('@/assets/images/789.jpg'), alt: 'Photo 16', sizeClass: 'medium' },
{ src: require('@/assets/images/789.jpg'), alt: 'Photo 17', sizeClass: 'large' },
{ src: require('@/assets/images/789.jpg'), alt: 'Photo 18', sizeClass: 'small' },
{ src: require('@/assets/images/789.jpg'), alt: 'Photo 19', sizeClass: 'medium' },
{ src: require('@/assets/images/789.jpg'), alt: 'Photo 20', sizeClass: 'large' },
{ src: require('@/assets/images/789.jpg'), alt: 'Photo 21', sizeClass: 'small' },
{ src: require('@/assets/images/789.jpg'), alt: 'Photo 22', sizeClass: 'medium' },
{ src: require('@/assets/images/789.jpg'), alt: 'Photo 23', sizeClass: 'large' },
{ src: require('@/assets/images/789.jpg'), alt: 'Photo 24', sizeClass: 'small' },
{ src: require('@/assets/images/789.jpg'), alt: 'Photo 25', sizeClass: 'medium' },
{ src: require('@/assets/images/789.jpg'), alt: 'Photo 26', sizeClass: 'large' },
{ src: require('@/assets/images/789.jpg'), alt: 'Photo 27', sizeClass: 'small' }
],
newPhoto: {
src: '', // 新照片的URL
alt: '' // 新照片的描述
},
newPhotoFile: null, // 新照片的文件对象
showModal: false, // 控制上传照片模态框的显示
showImageViewer: false, // 控制图片查看器的显示
currentImage: '', // 当前显示的大图的URL
pointerStyle: {
display: 'none', // 控制边框指示器的显示
top: '0px',
left: '0px',
width: '0px',
height: '0px',
borderColor: '' // 边框指示器的颜色
}
}
},
methods: {
// 加载图片方法
loadImages () {
const images = this.$refs.photoItems // 获取所有照片项的引用
const options = {
root: null,
rootMargin: '0px',
threshold: 0.1 // 触发观察器的阈值
}
// 创建IntersectionObserver对象
const observer = new IntersectionObserver((entries, observer) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target.querySelector('img')
img.src = img.getAttribute('data-src') // 加载图片
observer.unobserve(entry.target) // 停止观察已加载的图片
}
})
}, options)
images.forEach(image => {
observer.observe(image) // 观察每个图片项
})
},
// 处理文件变化
onFileChange (e) {
const file = e.target.files[0]
if (file) {
this.newPhotoFile = URL.createObjectURL(file) // 创建文件的临时URL
}
},
// 添加新照片
addPhoto () {
if (this.newPhotoFile && this.newPhoto.alt) {
const sizeClasses = ['small', 'medium', 'large']
const sizeClass = sizeClasses[Math.floor(Math.random() * sizeClasses.length)]
this.photos.push({
src: this.newPhotoFile,
alt: this.newPhoto.alt,
sizeClass: sizeClass
})
this.newPhotoFile = null // 清空文件对象
this.newPhoto.alt = '' // 清空描述
this.showModal = false // 关闭模态框
document.body.classList.remove('no-scroll') // 恢复页面滚动
this.$nextTick(() => {
this.loadImages() // 重新加载图片
})
}
},
// 打开模态框
openModal () {
this.showModal = true
document.body.classList.add('no-scroll') // 禁用页面滚动
},
// 关闭模态框
closeModal () {
this.showModal = false
document.body.classList.remove('no-scroll') // 恢复页面滚动
},
// 打开图片查看器
openImageViewer (src) {
this.currentImage = src
this.showImageViewer = true
document.body.classList.add('no-scroll') // 禁用页面滚动
},
// 关闭图片查看器
closeImageViewer () {
this.showImageViewer = false
document.body.classList.remove('no-scroll') // 恢复页面滚动
},
// 鼠标悬停时显示边框指示器
onMouseOver (event, sizeClass) {
const galleryItem = event.currentTarget
const rect = galleryItem.getBoundingClientRect()
const scrollTop = window.pageYOffset || document.documentElement.scrollTop
const scrollLeft = window.pageXOffset || document.documentElement.scrollLeft
// 生成随机颜色
const randomColor = '#' + Math.floor(Math.random() * 16777215).toString(16)
this.pointerStyle = {
display: 'block',
top: `${rect.top + scrollTop - 85}px`,
left: `${rect.left + scrollLeft - 105}px`,
width: `${rect.width + 30}px`,
height: `${rect.height + 30}px`,
borderColor: randomColor // 设置随机颜色
}
},
// 鼠标离开时隐藏边框指示器
onMouseLeave () {
this.pointerStyle.display = 'none'
}
},
// 组件挂载后加载图片
mounted () {
this.loadImages()
}
}
</script>
<style lang="less" scoped>
.photo-gallery {
max-width: 1200px;
margin: 30px auto;
padding: 20px;
}
header {
margin-bottom: 20px;
}
.upload-button {
position: fixed;
bottom: 20px;
right: 20px;
}
.upload-button button {
background: #007bff;
color: white;
border: none;
padding: 10px 20px;
font-size: 16px;
border-radius: 50px;
cursor: pointer;
transition: background 0.3s;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
}
.upload-button button:hover {
background: #0056b3;
}
.modal {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
display: flex;
justify-content: center;
align-items: center;
z-index: 10000; /* Ensure modal is on top */
}
.modal-content {
background: #fff;
padding: 20px;
border-radius: 12px;
position: relative;
width: 400px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
animation: fadeIn 0.3s;
z-index: 10001; /* Ensure modal content is on top */
}
@keyframes fadeIn {
from {
opacity: 0;
transform: scale(0.9);
}
to {
opacity: 1;
transform: scale(1);
}
}
.close {
position: absolute;
top: 10px;
right: 10px;
font-size: 20px;
cursor: pointer;
}
.form-group {
margin-bottom: 15px;
}
.form-group label {
display: block;
margin-bottom: 5px;
font-weight: bold;
}
.form-group input[type="file"] {
display: block;
width: 100%;
}
.form-group input[type="text"] {
display: block;
width: calc(100% - 20px);
padding: 8px 10px;
font-size: 14px;
border: 1px solid #ddd;
border-radius: 4px;
}
button {
background: #007bff;
color: white;
border: none;
padding: 10px 20px;
font-size: 16px;
border-radius: 4px;
cursor: pointer;
transition: background 0.3s;
}
button:hover {
background: #0056b3;
}
.gallery {
column-count: 4;
column-gap: 16px;
position: relative;
}
.pointer {
position: absolute;
border: 2px solid;
}
.gallery-item {
break-inside: avoid;
background: #fff;
border-radius: 8px;
overflow: hidden;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
margin-bottom: 16px;
transition: transform 0.3s ease, box-shadow 0.3s ease;
}
.gallery-item:hover {
transform: scale(1.05);
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
}
.gallery-item img {
width: 100%;
height: auto;
display: block;
}
.gallery-item.small img {
height: 150px;
}
.gallery-item.medium img {
height: 200px;
}
.gallery-item.large img {
height: 250px;
}
.image-viewer {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.8);
display: flex;
justify-content: center;
align-items: center;
z-index: 10000;
}
.image-viewer-content {
position: relative;
max-width: 95%;
max-height: 95%;
z-index: 10001;
}
.image-viewer img {
width: 100%;
height: auto;
display: block;
}
.image-viewer .close {
position: absolute;
top: 10px;
right: 10px;
font-size: 20px;
color: white;
cursor: pointer;
}
</style>