xiaocao 7 mesi fa
parent
commit
a46f0641c2
1 ha cambiato i file con 360 aggiunte e 52 eliminazioni
  1. 360 52
      src/pages/work/projectImage/projectImage.vue

+ 360 - 52
src/pages/work/projectImage/projectImage.vue

@@ -1,43 +1,80 @@
 <template>
-	<view>
-		<scroll-view
-				:scroll-with-animation="true"
-				class="scroll-view"
-				scroll-y
-				@scrolltolower="loadMore"
-		>
-			<uni-grid :column="2" :highlight="true" :show-border="false" :square="true">
-				<uni-grid-item v-for="(item, index) in imageList" :key="index"
-				               class="custom-grid-item"
-				>
-					<image
-							:src="item.url"
-							class="project-image"
-							mode="aspectFill"
-							@click="previewImage(index)"
-					/>
-				</uni-grid-item>
-			</uni-grid>
-			<!--		<image-->
-			<!--				v-for="(item, index) in imageList"-->
-			<!--				:key="index"-->
-			<!--				:src="item.url"-->
-			<!--				class="project-image"-->
-			<!--				mode="aspectFill"-->
-			<!--				@click="previewImage(index)"-->
-			<!--		/>-->
+	<view class="container">
+		<scroll-view :scroll-with-animation="true" class="scroll-view" scroll-y @scrolltolower="loadMore">
+			<!-- 无附件时的提示 -->
+			<view v-if="imageList.length === 0" class="no-attachment">
+				<uni-icons color="#CCCCCC" size="50" type="image"></uni-icons>
+				<text class="no-attachment-text">暂无附件</text>
+			</view>
+
+			<view v-else class="gallery-container">
+				<view :class="{ 'loading': loading }" class="gallery-grid">
+					<view v-for="(item, index) in imageList" :key="index" :class="{ 'fade-in': imageLoaded[index] }"
+						class="gallery-item">
+						<!-- 图片类型 -->
+						<view v-if="isImageFile(item.url)" class="media-wrapper">
+							<image :src="item.url || ''" class="media-content" mode="aspectFill"
+								@click="previewImage(index)" @error="handleImageError(index)"
+								@load="handleImageLoad(index)" />
+							<view v-if="!imageLoaded[index]" class="loading-overlay">
+								<view class="loading-spinner">
+									<uni-icons color="#FFFFFF" size="24" type="spinner-cycle"></uni-icons>
+								</view>
+							</view>
+						</view>
+
+						<!-- 视频类型 -->
+						<view v-else-if="isVideoFile(item.url)" class="media-wrapper">
+							<video :src="item.url || ''" class="media-content" @click="previewVideo(index)" />
+							<view class="video-overlay" @click="previewVideo(index)">
+								<uni-icons color="#FFFFFF" size="30" type="videocam-filled"></uni-icons>
+							</view>
+						</view>
+
+						<!-- 其他类型 -->
+						<view v-else class="unsupported-file">
+							<uni-icons color="#999999" size="30" type="file"></uni-icons>
+							<text class="unsupported-text">不支持的文件类型</text>
+						</view>
+					</view>
+				</view>
+			</view>
+
+			<!-- 加载提示 -->
+			<view v-if="loading" class="loading-tip">
+				<view class="loading-dot"></view>
+				<view class="loading-dot"></view>
+				<view class="loading-dot"></view>
+			</view>
+
+			<!-- 加载完成/无更多数据提示 -->
+			<view v-if="noMore && imageList.length > 0" class="no-more">
+				<text>没有更多内容了</text>
+			</view>
 		</scroll-view>
+
+		<!-- 视频弹窗播放器 -->
+		<view v-if="showVideoPlayer" class="video-modal">
+			<view class="video-modal-mask" @click="closeVideoPlayer"></view>
+			<view class="video-modal-content">
+				<video :src="currentVideoUrl" controls autoplay class="video-modal-player" />
+				<view class="video-modal-close" @click="closeVideoPlayer">
+					<uni-icons color="#fff" size="28" type="close"></uni-icons>
+				</view>
+			</view>
+		</view>
 	</view>
 </template>
 
 <script setup>
-import {onLoad} from '@dcloudio/uni-app'
-import {ref, reactive} from 'vue'
-import {getOaProjectIndexImg} from "@/api/oa/project";
+import { onLoad } from '@dcloudio/uni-app'
+import { ref, reactive } from 'vue'
+import { getOaProjectIndexImg } from "@/api/oa/project";
 import config from "@/config";
 
 const imageList = ref([])
 const loading = ref(false)
+const imageLoaded = ref([])
 const noMore = ref(false)
 const projectData = reactive({
 	projectId: '',
@@ -45,10 +82,11 @@ const projectData = reactive({
 	pageNum: 1,
 	pageSize: 10,
 })
+const showVideoPlayer = ref(false)
+const currentVideoUrl = ref('')
 
 onLoad((options) => {
 	console.log('接收到的参数:', options)
-	// projectId: "1893214899153747969", projectName: "南通奥特莱斯", timestamp: "1752287330777"
 	projectData.projectId = options.projectId || ''
 	projectData.name = decodeURIComponent(options.projectName || '')
 
@@ -61,6 +99,20 @@ onLoad((options) => {
 	getProjectImage()
 })
 
+// 图片加载处理
+const handleImageLoad = (index) => {
+	imageLoaded.value[index] = true
+}
+
+const handleImageError = (index) => {
+	console.error(`图片加载失败:${index},图片地址:${imageList.value[index].url}`)
+	// 可以在这里添加图片加载失败的处理,比如显示默认图片
+	uni.showToast({
+		title: '图片加载失败',
+		icon: 'none'
+	})
+}
+
 async function getProjectImage() {
 	if (loading.value || noMore.value) return
 
@@ -68,19 +120,18 @@ async function getProjectImage() {
 
 	try {
 		const res = await getOaProjectIndexImg(projectData)
-
 		if (res?.accessories) {
 			const newImages = res.accessories.split(',').filter(url => url.trim())
-					.map(url => {
-						let imageUrl = url.trim();
-						if (!imageUrl.startsWith('http')) {
-							imageUrl = `${config.baseUrl}${config.apiPrefix}${imageUrl}`;
-						}
-						// #ifdef MP-WEIXIN
-						imageUrl = imageUrl.replace(/^http:/, 'https:');
-						// #endif
-						return {url: imageUrl};
-					})
+				.map(url => {
+					let imageUrl = url.trim();
+					if (!imageUrl.startsWith('http')) {
+						imageUrl = `${config.baseUrl}${config.apiPrefix}${imageUrl}`;
+					}
+					// #ifdef MP-WEIXIN
+					imageUrl = imageUrl.replace(/^http:/, 'https:');
+					// #endif
+					return { url: imageUrl };
+				})
 
 			if (newImages.length === 0) {
 				noMore.value = true
@@ -107,41 +158,298 @@ function loadMore() {
 	}
 }
 
+// 判断文件类型
+const isVideoFile = (fileName) => {
+	return /\.(mp4|webm|ogg|mov|avi|wmv|flv|mkv)$/i.test(fileName)
+}
+
+// 判断是否是图片文件
+const isImageFile = (fileName) => {
+	return /\.(jpg|jpeg|png|gif|bmp|webp|svg)$/i.test(fileName)
+}
+
 // 图片预览
 function previewImage(current) {
 	uni.previewImage({
 		current,
 		urls: imageList.value.map(item => item.url),
 		// #ifdef MP-WEIXIN
-		// 微信小程序专属配置
 		indicator: 'number'
 		// #endif
 	})
 }
 
+// 预览视频
+const previewVideo = (index) => {
+	currentVideoUrl.value = imageList.value[index].url
+	showVideoPlayer.value = true
+}
+const closeVideoPlayer = () => {
+	showVideoPlayer.value = false
+	currentVideoUrl.value = ''
+}
 </script>
 
 <style scoped>
+.container {
+	background-color: #f5f5f5;
+	min-height: 100vh;
+}
+
 .scroll-view {
 	height: 100vh;
-	padding-bottom: 100rpx;
+	padding: 20rpx;
+}
+
+.gallery-container {
+	padding: 10rpx;
+}
+
+.gallery-grid {
+	display: grid;
+	grid-template-columns: repeat(2, 1fr);
+	gap: 20rpx;
+	transition: opacity 0.3s ease;
+}
+
+.gallery-grid.loading {
+	opacity: 0.7;
+}
+
+.gallery-item {
+	position: relative;
+	border-radius: 12rpx;
+	overflow: hidden;
+	background: #ffffff;
+	box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.1);
+	transform: translateY(0);
+	transition: all 0.3s ease;
 }
 
-/* 修改网格项的内边距 */
-:deep(.custom-grid-item .uni-grid-item__box) {
-	padding: 20rpx !important;
+.gallery-item.fade-in {
+	animation: fadeIn 0.5s ease forwards;
+}
+
+.gallery-item:active {
+	transform: translateY(2rpx);
+	box-shadow: 0 1rpx 5rpx rgba(0, 0, 0, 0.1);
+}
+
+.media-wrapper {
+	position: relative;
+	width: 100%;
+	padding-bottom: 100%;
+	/* 1:1 宽高比 */
+}
+
+.media-content {
+	position: absolute;
+	top: 0;
+	left: 0;
+	width: 100%;
+	height: 100%;
+	object-fit: cover;
+}
+
+.loading-overlay {
+	position: absolute;
+	top: 0;
+	left: 0;
+	right: 0;
+	bottom: 0;
+	background: rgba(0, 0, 0, 0.5);
+	display: flex;
+	justify-content: center;
+	align-items: center;
+	backdrop-filter: blur(2px);
+}
+
+.loading-spinner {
+	animation: spin 1s linear infinite;
+}
+
+.video-overlay {
+	position: absolute;
+	top: 0;
+	left: 0;
+	right: 0;
+	bottom: 0;
+	background: rgba(0, 0, 0, 0.3);
+	display: flex;
+	justify-content: center;
+	align-items: center;
+}
+
+.unsupported-file {
+	height: 100%;
+	display: flex;
+	flex-direction: column;
+	justify-content: center;
+	align-items: center;
+	padding: 40rpx;
+	background: #f8f8f8;
+}
+
+.unsupported-text {
+	margin-top: 20rpx;
+	font-size: 24rpx;
+	color: #999999;
+}
+
+.no-attachment {
+	display: flex;
+	flex-direction: column;
+	align-items: center;
+	justify-content: center;
+	height: 400rpx;
+	background: #ffffff;
+	border-radius: 12rpx;
+	margin: 20rpx;
+}
+
+.no-attachment-text {
+	margin-top: 20rpx;
+	color: #999999;
+	font-size: 28rpx;
+}
+
+.loading-tip {
+	display: flex;
+	justify-content: center;
+	align-items: center;
+	padding: 40rpx;
+	gap: 10rpx;
+}
+
+.loading-dot {
+	width: 16rpx;
+	height: 16rpx;
+	background-color: #409eff;
+	border-radius: 50%;
+	animation: bounce 0.5s ease infinite;
+}
+
+.loading-dot:nth-child(2) {
+	animation-delay: 0.1s;
+}
+
+.loading-dot:nth-child(3) {
+	animation-delay: 0.2s;
+}
+
+.no-more {
+	text-align: center;
+	padding: 30rpx;
+	color: #999999;
+	font-size: 24rpx;
+}
+
+@keyframes fadeIn {
+	from {
+		opacity: 0;
+		transform: translateY(20rpx);
+	}
+
+	to {
+		opacity: 1;
+		transform: translateY(0);
+	}
+}
+
+@keyframes spin {
+	from {
+		transform: rotate(0deg);
+	}
+
+	to {
+		transform: rotate(360deg);
+	}
+}
+
+@keyframes bounce {
+
+	0%,
+	100% {
+		transform: translateY(0);
+	}
+
+	50% {
+		transform: translateY(-5rpx);
+	}
+}
+
+/* 深色模式适配 */
+@media (prefers-color-scheme: dark) {
+	.container {
+		background-color: #1a1a1a;
+	}
+
+	.gallery-item {
+		background: #2a2a2a;
+		box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.2);
+	}
+
+	.unsupported-file {
+		background: #2a2a2a;
+	}
+
+	.no-attachment {
+		background: #2a2a2a;
+	}
+}
+
+.video-modal {
+	position: fixed;
+	z-index: 9999;
+	top: 0;
+	left: 0;
+	width: 100vw;
+	height: 100vh;
+	display: flex;
+	align-items: center;
+	justify-content: center;
+}
+
+.video-modal-mask {
+	position: absolute;
+	top: 0;
+	left: 0;
+	width: 100vw;
+	height: 100vh;
+	background: rgba(0, 0, 0, 0.6);
+}
+
+.video-modal-content {
+	position: relative;
+	width: 80vw;
+	max-width: 600px;
+	height: 80vh;
+	max-height: 80vh;
+	background: #000;
+	border-radius: 16rpx;
+	display: flex;
+	align-items: center;
+	justify-content: center;
+	box-shadow: 0 8rpx 32rpx rgba(0, 0, 0, 0.3);
 }
 
-/* 调整图片大小 */
-.project-image {
+.video-modal-player {
 	width: 100%;
 	height: 100%;
-	border-radius: 10rpx;
+	border-radius: 16rpx;
+	background: #000;
 }
 
-.loading-more {
+.video-modal-close {
+	position: absolute;
+	top: 10rpx;
+	right: 10rpx;
+	z-index: 2;
+	background: rgba(0, 0, 0, 0.4);
+	border-radius: 50%;
+	padding: 8rpx;
 	display: flex;
+	align-items: center;
 	justify-content: center;
-	padding: 20rpx 0;
 }
 </style>