400 lines
11 KiB
Vue
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">×</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">×</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>
|