Bladeren bron

feat(photography): 添加图片预览功能

- 在 processImage.vue 和 shot.vue 中集成 ImagePreview 组件
- 移除原有的 el-image 预览属性,改用自定义预览逻辑
- 实现点击图片打开大图预览功能
- 添加预览图片列表获取和索引计算方法
- 创建独立的 PreviewImage 组件支持图片预览
- 预览时显示图片对应的操作名称(action_name)
- 修复按钮点击事件冒泡问题
panqiuyao 2 weken geleden
bovenliggende
commit
65a4dbcb44

+ 95 - 0
frontend/src/components/PreviewImage/index.vue

@@ -0,0 +1,95 @@
+<template>
+  <Teleport to="body">
+    <el-image-viewer
+      v-if="visible"
+      :url-list="urlList"
+      :initial-index="initialIndex"
+      hide-on-click-modal
+      :infinite="false"
+      :teleported="false"
+      @close="handleClose"
+      @switch="handleSwitch"
+    />
+    <div v-if="visible && currentActionName" class="preview-action-name">
+      {{ currentActionName }}
+    </div>
+  </Teleport>
+</template>
+
+<script setup lang="ts">
+import { computed, ref, watch } from 'vue'
+
+export interface PreviewItem {
+  url: string
+  action_name?: string
+}
+
+const props = withDefaults(
+  defineProps<{
+    visible: boolean
+    list: PreviewItem[]
+    initialIndex?: number
+  }>(),
+  {
+    list: () => [],
+    initialIndex: 0,
+  }
+)
+
+const emit = defineEmits<{
+  (e: 'close'): void
+  (e: 'switch', index: number): void
+}>()
+
+const currentIndex = ref(props.initialIndex ?? 0)
+
+const urlList = computed(() => props.list.map((item) => item.url))
+const currentActionName = computed(
+  () => props.list[currentIndex.value]?.action_name || ''
+)
+
+const handleClose = () => emit('close')
+const handleSwitch = (index: number) => {
+  currentIndex.value = index
+  emit('switch', index)
+}
+
+watch(
+  () => props.visible,
+  (val) => {
+    if (val) {
+      currentIndex.value = props.initialIndex ?? 0
+    }
+  }
+)
+
+watch(
+  () => props.initialIndex,
+  (val) => {
+    if (props.visible && typeof val === 'number') {
+      currentIndex.value = val
+    }
+  }
+)
+</script>
+
+<style lang="scss" scoped>
+.preview-action-name {
+  position: fixed;
+  top: 30px;
+  left: 50%;
+  transform: translateX(-50%);
+  background: rgba(41, 87, 255, 0.9);
+  color: #fff;
+  padding: 8px 24px;
+  border-radius: 20px;
+  font-size: 16px;
+  font-weight: 500;
+  z-index: 3000;
+  box-shadow: 0 2px 8px rgba(41, 87, 255, 0.4);
+  pointer-events: none;
+  user-select: none;
+  max-width: 80vw;
+  text-align: center;
+}
+</style>

+ 28 - 8
frontend/src/views/Photography/processImage.vue

@@ -7,6 +7,13 @@
 
   <hardware-check/>
 
+  <ImagePreview
+    :visible="previewVisible"
+    :list="previewList"
+    :initial-index="previewInitialIndex"
+    @close="previewVisible = false"
+  />
+
   <div class="photography-page flex-col bg-F5F6F7 ">
     <div class="main-container page—wrap max-w-full">
       <div class="history-section flex-col koutu-section">
@@ -74,13 +81,10 @@
                   <el-image
                     v-if="image.PhotoRecord.image_path"
                     :src="thumbnailMap[image.PhotoRecord.image_path] || getFilePath(image.PhotoRecord.image_path)"
-                    :preview-src-list="getPreviewImageList(item)"
-                    hide-on-click-modal
-                    :initial-index="getPreviewIndex(item, index)"
                     class="preview-image"
                     fit="contain"
-                    :preview-teleported="true"
                     lazy
+                    @click="openPreview(item, index)"
                   >
                     <template #placeholder>
                       <span class="tag">{{ image.action_name }}</span>
@@ -161,6 +165,7 @@ import { onMounted, onBeforeUnmount, ref, computed, nextTick, watch } from 'vue'
 import HardwareCheck from '@/components/check/index.vue'
 import usePhotography from './mixin/usePhotography'
 import { useThumbnails } from './composables/useThumbnails'
+import ImagePreview, { PreviewItem } from '@/components/PreviewImage/index.vue'
 import generate from '@/utils/menus/generate'
 import { ElMessageBox } from 'element-plus'
 import client from "@/stores/modules/client";
@@ -313,16 +318,19 @@ const deleteSelected = async () => {
   }
 }
 
-// 获取预览图片列表(只包含有图片路径的,保持原始顺序)
-const getPreviewImageList = (item: any) => {
+// 获取预览图片列表(只包含有图片路径的,保持原始顺序),携带 action_name 用于大图预览展示
+const getPreviewImageList = (item: any): PreviewItem[] => {
   if (!item || !item.items) return []
   return item.items
     .filter((img: any) => img.PhotoRecord?.image_path)
-    .map((img: any) => getFilePath(img.PhotoRecord.image_path))
+    .map((img: any) => ({
+      url: getFilePath(img.PhotoRecord.image_path),
+      action_name: img.action_name,
+    }))
 }
 
 // 获取当前图片在预览列表中的索引
-const getPreviewIndex = (item: any, currentIndex: number) => {
+const getPreviewIndex = (item: any, currentIndex: number): number => {
   if (!item || !item.items) return 0
   // 计算当前图片在过滤后的预览列表中的索引
   let previewIndex = 0
@@ -335,6 +343,18 @@ const getPreviewIndex = (item: any, currentIndex: number) => {
   return previewIndex
 }
 
+// 大图预览状态
+const previewVisible = ref(false)
+const previewList = ref<PreviewItem[]>([])
+const previewInitialIndex = ref(0)
+
+// 打开大图预览
+const openPreview = (item: any, index: number) => {
+  previewList.value = getPreviewImageList(item)
+  previewInitialIndex.value = getPreviewIndex(item, index)
+  previewVisible.value = true
+}
+
 // 打开目录选择器并导入图片
 const handleImportImage = () => {
   clientStore.ipc.removeAllListeners(icpList.utils.openDirectory);

+ 29 - 10
frontend/src/views/Photography/shot.vue

@@ -8,6 +8,13 @@
 
   <hardware-check @confirm="onHardwareCheckConfirm"/>
 
+  <ImagePreview
+    :visible="previewVisible"
+    :list="previewList"
+    :initial-index="previewInitialIndex"
+    @close="previewVisible = false"
+  />
+
   <div class="photography-page flex-col">
     <div class="main-container">
       <div class="content-wrapper flex-col">
@@ -131,15 +138,11 @@
                   v-loading="!image.PhotoRecord.image_path && runAction.goods_art_no == item.goods_art_no"
                 >
 <!--                  <span class="tag" v-if="!image.PhotoRecord.image_path">{{ image.action_name }}</span>-->
-                  <div class="el-image_view" >
+                  <div class="el-image_view" @click="image.PhotoRecord.image_path && openPreview(item, index)">
                     <el-image
                       :src="thumbnailMap[image.PhotoRecord.image_path] || getFilePath(image.PhotoRecord.image_path)"
-                      :preview-src-list="getPreviewImageList(item)"
-                      hide-on-click-modal
-                      :initial-index="getPreviewIndex(item, index)"
                       class="preview-image"
                       fit="contain"
-                      :preview-teleported="true"
                       lazy
                     >
                       <template #placeholder>
@@ -151,7 +154,7 @@
                         </div>
                       </template>
                     </el-image>
-                    <el-button v-if="image.action_name && !runLoading && !takePictureLoading" :disabled="runLoading || takePictureLoading" class="reset-button" @click="reTakePicture(image.PhotoRecord)" v-log="{ describe: { action: '重拍单张图片', goods_art_no: image.PhotoRecord.goods_art_no, action_name: image.action_name } }">重拍</el-button>
+                    <el-button v-if="image.action_name && !runLoading && !takePictureLoading" :disabled="runLoading || takePictureLoading" class="reset-button" @click.stop="reTakePicture(image.PhotoRecord)" v-log="{ describe: { action: '重拍单张图片', goods_art_no: image.PhotoRecord.goods_art_no, action_name: image.action_name } }">重拍</el-button>
                   </div>
                 </div>
               </div>
@@ -222,6 +225,7 @@ import HardwareCheck from '@/components/check/index.vue'
 import RemoteControl from '@/views/RemoteControl/index.vue'
 import usePhotography from './mixin/usePhotography'
 import { useThumbnails } from './composables/useThumbnails'
+import ImagePreview, { PreviewItem } from '@/components/PreviewImage/index.vue'
 import generate from '@/utils/menus/generate'
 import { ElMessage, ElMessageBox } from 'element-plus'
 
@@ -374,16 +378,19 @@ const onHardwareCheckConfirm = () => {
   }
 }
 
-// 获取预览图片列表(只包含有图片路径的,保持原始顺序)
-const getPreviewImageList = (item: any) => {
+// 获取预览图片列表(只包含有图片路径的,保持原始顺序),携带 action_name 用于大图预览展示
+const getPreviewImageList = (item: any): PreviewItem[] => {
   if (!item || !item.items) return []
   return item.items
     .filter((img: any) => img.PhotoRecord?.image_path)
-    .map((img: any) => getFilePath(img.PhotoRecord.image_path))
+    .map((img: any) => ({
+      url: getFilePath(img.PhotoRecord.image_path),
+      action_name: img.action_name,
+    }))
 }
 
 // 获取当前图片在预览列表中的索引
-const getPreviewIndex = (item: any, currentIndex: number) => {
+const getPreviewIndex = (item: any, currentIndex: number): number => {
   if (!item || !item.items) return 0
   // 计算当前图片在过滤后的预览列表中的索引
   let previewIndex = 0
@@ -396,6 +403,18 @@ const getPreviewIndex = (item: any, currentIndex: number) => {
   return previewIndex
 }
 
+// 大图预览状态
+const previewVisible = ref(false)
+const previewList = ref<PreviewItem[]>([])
+const previewInitialIndex = ref(0)
+
+// 打开大图预览
+const openPreview = (item: any, index: number) => {
+  previewList.value = getPreviewImageList(item)
+  previewInitialIndex.value = getPreviewIndex(item, index)
+  previewVisible.value = true
+}
+
 onMounted(async () => {
   await getPhotoRecords()
   initEventListeners()