zlwu пре 1 година
родитељ
комит
bc70052bf8

+ 134 - 0
docs/变更管理/code-adjust-record-2025-01-27-hall-reservation-async-image-loading.md

@@ -0,0 +1,134 @@
+# 报告厅预约页面异步图片加载功能实现
+
+## 变更概述
+- **变更时间**: 2025-01-27
+- **变更类型**: 功能优化
+- **变更范围**: 报告厅预约管理页面
+- **变更文件**: `web/src/views/hq/bgtgl/hallReservation/index.vue`
+
+## 问题修复 (2025-01-27 更新)
+- **修复时间**: 2025-01-27
+- **修复问题**: 异步图片加载状态显示问题
+- **问题描述**: 
+  1. 图片加载过程中显示"暂无图片"而不是加载状态
+  2. 图片加载完成后loading效果没有正确移除
+  3. 用户体验不佳,状态反馈不准确
+
+## 变更内容
+
+### 1. 异步图片加载功能实现
+参考 `ImageUpload` 组件的实现,为报告厅预约页面添加了异步blob URL加载模式:
+
+#### 新增功能
+- **异步图片加载**: 使用blob URL方式加载图片,支持认证请求
+- **图片缓存机制**: 避免重复加载相同图片
+- **内存管理**: 组件卸载时自动清理blob URL,防止内存泄漏
+- **加载状态显示**: 图片加载过程中显示加载动画
+- **错误处理**: 完善的图片加载错误处理机制
+
+### 2. 加载状态优化 (2025-01-27 修复)
+#### 修复内容
+- **加载状态管理**: 为每个报告厅添加独立的加载状态标记
+- **状态显示优化**: 
+  - 加载中:显示对应数量的旋转loading图标占位符
+  - 有图片:使用ImagePreview组件显示实际图片
+  - 无图片:显示"暂无图片"提示
+- **异步处理改进**: 使用Promise.then()替代await,避免阻塞UI
+- **用户体验提升**: 准确的状态反馈,避免状态混乱
+- **组件复用**: 集成ImagePreview组件,提供更好的图片预览功能
+
+#### 核心改进
+```typescript
+// 加载状态管理
+(hall as any).imageLoading = true;  // 设置加载状态
+(hall as any).imageUrls = [];       // 清空图片列表
+
+// 新增:占位符管理
+const imageCount = getImageCount(hall.hallImage);
+(hall as any).imagePlaceholders = Array(imageCount).fill(null).map((_, index) => `placeholder_${index}`);
+
+// 异步加载完成后更新状态
+getHallImagesAsync(hall.hallImage).then(imageUrls => {
+  (hall as any).imageUrls = imageUrls;
+  (hall as any).imageLoading = false;  // 清除加载状态
+});
+```
+
+#### 核心函数
+```typescript
+// 通过认证请求获取图片数据并转换为blob URL
+const getImageBlobUrl = async (imageUrl: string): Promise<string>
+
+// 通过ossid获取图片URL
+const getImageUrlByOssId = async (ossId: string): Promise<string>
+
+// 处理报告厅图片 - 异步blob URL加载模式
+const getHallImagesAsync = async (imageUrl: string): Promise<string[]>
+
+// 获取图片数量
+const getImageCount = (imageUrl: string): number
+
+// 获取报告厅图片占位符数组
+const getHallImagePlaceholders = (hall: HallInfoVO): string[]
+```
+
+### 3. 模板优化
+- **下拉框图片显示**: 报告厅选择下拉框中显示异步加载的图片
+- **加载状态指示**: 图片加载时显示对应数量的旋转加载图标占位符
+- **错误处理**: 图片加载失败时的友好提示
+- **状态管理**: 三种状态的准确显示(加载中、有图片、无图片)
+- **组件集成**: 使用ImagePreview组件提供更好的图片预览功能
+
+### 4. 样式优化
+- **加载动画**: 添加了CSS旋转动画效果
+- **图片容器**: 优化了图片容器的布局和样式
+- **响应式设计**: 保持原有的响应式布局特性
+- **加载占位符**: 新增加载状态的占位符样式
+- **组件样式**: 为ImagePreview组件在下拉框中的显示优化样式
+
+## 技术实现细节
+
+### 1. 图片加载流程
+1. 设置初始加载状态 (imageLoading = true)
+2. 解析图片数量,创建对应数量的占位符 (imagePlaceholders)
+3. 检查图片URL缓存
+4. 通过request工具发起认证请求
+5. 验证响应数据格式
+6. 创建blob URL并缓存
+7. 更新图片列表并清除加载状态 (imageLoading = false)
+8. 更新UI显示(占位符替换为实际图片)
+
+### 2. 内存管理
+- 使用Map结构缓存blob URL
+- 组件卸载时自动清理所有blob URL
+- 避免内存泄漏问题
+
+### 3. 错误处理
+- 网络请求超时处理
+- 图片格式验证
+- 加载失败降级处理
+- 加载状态重置处理
+
+## 变更影响
+- **性能提升**: 异步加载减少页面阻塞
+- **用户体验**: 更好的加载状态反馈,准确的状态显示
+- **安全性**: 支持认证的图片请求
+- **兼容性**: 保持与现有功能的兼容
+- **状态管理**: 完善的加载状态管理机制
+
+## 测试建议
+1. 测试不同网络环境下的图片加载
+2. 验证图片缓存机制是否正常工作
+3. 检查内存泄漏问题
+4. 测试图片加载失败的处理
+5. **验证加载状态显示**: 确保加载中、有图片、无图片三种状态正确显示
+6. **测试异步加载**: 验证图片异步加载过程中状态切换是否正常
+
+## 相关文件
+- `web/src/components/ImageUpload/index.vue` - 参考实现
+- `web/src/components/ImagePreview/index.vue` - 图片预览组件
+- `web/src/api/system/oss/index.ts` - OSS API接口
+- `web/src/utils/request.ts` - 请求工具
+
+## 备注
+本次变更参考了ImageUpload组件的成熟实现,确保了代码质量和功能稳定性。 

+ 2 - 0
web/src/components/ImagePreview/index.vue

@@ -257,6 +257,8 @@ watch(
 
     try {
       if (props.isOssId) {
+        // console.log('ossid模式');
+        // console.log(newSrc);
         // ossid模式:通过接口获取真实URL,然后转换为认证的blob URL
         const ossIds = newSrc.split(',').filter((id) => id.trim());
 

+ 14 - 12
web/src/views/hq/bgtgl/hallAudit/index.vue

@@ -216,11 +216,12 @@
         <el-form-item label="报告厅图片">
           <div class="view-hall-images">
             <div class="image-gallery" v-if="viewHallImages.length > 0">
-              <div v-for="(image, index) in viewHallImages" :key="index" class="image-item" @click="previewImage(image, index)">
-                <img :src="image" class="gallery-image" @error="handleImageError" />
+              <div v-for="(image, index) in viewHallImages" :key="index" class="image-item">
+                <!-- <img :src="image" class="gallery-image" @error="handleImageError" />
                 <div class="image-overlay">
                   <el-icon class="preview-icon"><ZoomIn /></el-icon>
-                </div>
+                </div> -->
+                <image-preview :src="image" :width="120" :height="90" :isOssId="true" />
               </div>
             </div>
             <div v-else class="no-images-placeholder">
@@ -283,11 +284,12 @@
                 </div>
                 <div class="hall-images" v-if="getHallImagesByName(hallName).length > 0">
                   <div class="image-gallery">
-                    <div v-for="(image, index) in getHallImagesByName(hallName)" :key="index" class="image-item" @click="previewImage(image, index)">
-                      <img :src="image" class="gallery-image" @error="handleImageError" />
+                    <div v-for="(image, index) in getHallImagesByName(hallName)" :key="index" class="image-item">
+                      <!-- <img :src="image" class="gallery-image" @error="handleImageError" />
                       <div class="image-overlay">
                         <el-icon class="preview-icon"><ZoomIn /></el-icon>
-                      </div>
+                      </div> -->
+                      <image-preview :src="image" :width="120" :height="90" :isOssId="true" />
                     </div>
                   </div>
                 </div>
@@ -560,14 +562,14 @@ const getHallImages = async (ossIds: string): Promise<string[]> => {
     console.log('处理OSS ID:', ossIds, 'ID数组:', idArray);
 
     // 调用API获取真实的图片URL
-    const response = await listByIds(idArray.join(','));
-    const ossList: OssVO[] = response.data || [];
+    // const response = await listByIds(idArray.join(','));
+    // const ossList: OssVO[] = response.data || [];
 
-    // 提取URL列表
-    const urls = ossList.map((oss) => oss.url).filter((url) => url);
+    // // 提取URL列表
+    // const urls = ossList.map((oss) => oss.url).filter((url) => url);
 
-    console.log('获取到的图片URL:', urls);
-    return urls;
+    // console.log('获取到的图片URL:', urls);
+    return idArray;
   } catch (error) {
     console.error('获取图片URL失败:', error);
     return [];

+ 39 - 25
web/src/views/hq/bgtgl/hallReservation/index.vue

@@ -210,15 +210,18 @@
                   {{ displayHallDescription(hall.hallDescription) }}
                 </div>
                 <div class="hall-images">
-                  <template v-if="hall.imageUrls && hall.imageUrls.length > 0">
-                    <img
+                  <template v-if="hall.hallImage && hall.hallImage.length > 0">
+                    <!-- <img
                       v-for="(image, index) in hall.imageUrls"
                       :key="index"
                       :src="image"
                       class="hall-image"
                       @error="handleImageError"
                       @load="handleImageLoad"
-                    />
+                    /> -->
+                    <div v-for="(image, index) in hall.hallImage" :key="index">
+                      <image-preview :src="image" :width="40" :height="40" :isOssId="true" />
+                    </div>
                   </template>
                   <span v-else class="no-image-text">暂无图片</span>
                 </div>
@@ -230,11 +233,12 @@
         <el-form-item label="报告厅图片">
           <div class="selected-hall-images">
             <div class="image-gallery" v-if="selectedHallImages.length > 0">
-              <div v-for="(image, index) in selectedHallImages" :key="index" class="image-item" @click="previewImage(image, index)">
-                <img :src="image" class="gallery-image" @error="handleImageError" />
+              <div v-for="(image, index) in selectedHallImages" :key="index">
+                <!-- <img :src="image" class="gallery-image" @error="handleImageError" />
                 <div class="image-overlay">
                   <el-icon class="preview-icon"><ZoomIn /></el-icon>
-                </div>
+                </div> -->
+                <image-preview :src="image" :width="120" :height="90" :isOssId="true" />
               </div>
             </div>
             <div v-else class="no-images-placeholder">
@@ -299,10 +303,11 @@
           <div class="view-hall-images">
             <div class="image-gallery" v-if="viewHallImages.length > 0">
               <div v-for="(image, index) in viewHallImages" :key="index" class="image-item" @click="previewImage(image, index)">
-                <img :src="image" class="gallery-image" @error="handleImageError" />
+                <!-- <img :src="image" class="gallery-image" @error="handleImageError" />
                 <div class="image-overlay">
                   <el-icon class="preview-icon"><ZoomIn /></el-icon>
-                </div>
+                </div> -->
+                <image-preview :src="image" :width="120" :height="90" :isOssId="true" />
               </div>
             </div>
             <div v-else class="no-images-placeholder">
@@ -525,21 +530,29 @@ const getHallList = async () => {
       hallStatus: '1'
     }); // 获取正常状态的报告厅
     hallList.value = res.rows || [];
-
-    // 为每个报告厅获取图片URL
-    for (const hall of hallList.value) {
+    // for (const hall of hallList.value) {
+    //   if (hall.hallImage) {
+    //     try {
+    //       const imageUrls = await getHallImages(hall.hallImage);
+    //       hall.imageUrls = imageUrls;
+    //     } catch (error) {
+    //       console.error(`获取报告厅 ${hall.hallName} 图片失败:`, error);
+    //       hall.imageUrls = [];
+    //     }
+    //   } else {
+    //     hall.imageUrls = [];
+    //   }
+    // }
+    //hall.hallImage.split(',')
+    hallList.value.forEach((hall) => {
+      //加一些判断
       if (hall.hallImage) {
-        try {
-          const imageUrls = await getHallImages(hall.hallImage);
-          hall.imageUrls = imageUrls;
-        } catch (error) {
-          console.error(`获取报告厅 ${hall.hallName} 图片失败:`, error);
-          hall.imageUrls = [];
-        }
+        hall.hallImage = hall.hallImage.split(',');
       } else {
-        hall.imageUrls = [];
+        hall.hallImage = [];
       }
-    }
+    });
+    // 为每个报告厅获取图片URL
 
     // 调试信息:检查每个报告厅的图片信息
     console.log('报告厅列表数据:', hallList.value);
@@ -562,7 +575,7 @@ const handleHallChange = (hallName: string) => {
   if (selectedHall) {
     form.value.hallId = selectedHall.id;
     // 更新选中的报告厅图片
-    selectedHallImages.value = selectedHall.imageUrls || [];
+    selectedHallImages.value = selectedHall.hallImage || [];
   } else {
     selectedHallImages.value = [];
   }
@@ -796,8 +809,8 @@ const handleView = async (row: HallReservationVO) => {
   // 查找对应的报告厅并显示图片
   if (viewForm.value.hallName) {
     const selectedHall = hallList.value.find((hall) => hall.hallName === viewForm.value.hallName);
-    if (selectedHall && selectedHall.imageUrls) {
-      viewHallImages.value = selectedHall.imageUrls;
+    if (selectedHall && selectedHall.hallImage) {
+      viewHallImages.value = selectedHall.hallImage;
     } else {
       viewHallImages.value = [];
     }
@@ -840,8 +853,9 @@ const handleUpdate = async (row?: HallReservationVO) => {
   // 如果有报告厅名称,找到对应的报告厅并显示图片
   if (form.value.hallName) {
     const selectedHall = hallList.value.find((hall) => hall.hallName === form.value.hallName);
-    if (selectedHall && selectedHall.imageUrls) {
-      selectedHallImages.value = selectedHall.imageUrls;
+    console.log('selectedHall', selectedHall);
+    if (selectedHall && selectedHall.hallImage) {
+      selectedHallImages.value = selectedHall.hallImage;
     }
   }