shot.vue 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256
  1. <template>
  2. <headerBar :title="$t('router.photoShot')" showUser :menu="menu" />
  3. <hardware-check/>
  4. <div class="photography-page flex-col">
  5. <div class="main-container">
  6. <div class="content-wrapper flex-col">
  7. <div class="step-section">
  8. <div class="step-number flex-col"><span class="text_22">1</span></div>
  9. <div class="step-one flex-col">
  10. <div class="step-header flex-row">
  11. <span class="step-title">{{ $t('photoShot.step1') }}</span>
  12. </div>
  13. <div class="step-content">
  14. <div class="input-container flex-row">
  15. <el-input class="input-item" ref="goodsArtNo" clearable v-model="goods_art_no" :placeholder="$t('photoShot.inputGoodsPlaceholder')"> </el-input>
  16. </div>
  17. <div class="auto-method flex-row justify-between">
  18. <img class="step-icon" referrerpolicy="no-referrer" src="@/assets/images/Photography/step1-icon.png" />
  19. <div class="text-method-tag flex-col mar-left-10"><span class="text_4">{{ $t('photoShot.autoGet') }}</span></div>
  20. <span class="method-description mar-left-10">{{ $t('photoShot.scanTip') }}</span>
  21. </div>
  22. </div>
  23. </div>
  24. </div>
  25. <div class="step-section">
  26. <div class="step-number flex-col"><span class="text_22">2</span></div>
  27. <div class="step-two flex-col justify-between">
  28. <span class="step-title">{{ $t('photoShot.step2') }}</span>
  29. <div class="shooting-container flex-col">
  30. <div class="shooting-tips flex-row justify-between">
  31. <img class="info-icon" referrerpolicy="no-referrer" src="@/assets/images/Photography/info-icon.png" />
  32. <span class="tips-text">{{ $t('photoShot.remoteTip') }}</span>
  33. </div>
  34. <div class="wifi mar-top-20">
  35. <img referrerpolicy="no-referrer" src="@/assets/images/Photography/wifi.png" style="width: 60px" />
  36. </div>
  37. <div class="remote-control-wrap">
  38. <RemoteControl
  39. @onRemoteControl="onRemoteControl"
  40. :canStop="runLoading || takePictureLoading"
  41. />
  42. </div>
  43. </div>
  44. </div>
  45. </div>
  46. </div>
  47. <div class="last-photo" v-show="showlastPhoto && (lastPhoto as any)?.file_path" v-key="(lastPhoto as any)?.file_path">
  48. <el-button class="close-btn" type="danger" icon="Close" circle @click="closeLastPhoto" />
  49. <el-image :src="getFilePath((lastPhoto as any)?.file_path || '')" fit="contain" ></el-image>
  50. </div>
  51. <div class="history-section flex-col koutu-section">
  52. <div class="search-bar">
  53. <el-input
  54. v-model="searchGoodsArtNo"
  55. :placeholder="$t('photoShot.searchPlaceholder')"
  56. clearable
  57. style="width: 300px"
  58. @keyup.enter="handleSearch"
  59. >
  60. <template #append>
  61. <el-button @click="handleSearch">{{ $t('common.search') }}</el-button>
  62. </template>
  63. </el-input>
  64. </div>
  65. <div class="history-warp" ref="containerRef">
  66. <div v-if="!goodsList.length" class="fs-14 c-666 mar-top-50">
  67. {{ loading ? $t('photoShot.loading') : $t('photoShot.noData')}}
  68. </div>
  69. <div v-else class="history-item" v-for="item in goodsList" :key="item.goods_art_no" >
  70. <div class="history-item-header">
  71. <div class="history-item-left">
  72. <el-checkbox
  73. :model-value="selectedGoods.has(item.goods_art_no)"
  74. @change="toggleGoods(item.goods_art_no)"
  75. class="goods-checkbox"
  76. />
  77. <span class="goods-art-no">{{ item.goods_art_no }}</span>
  78. <div class="history-item-meta ">
  79. <span class="action-time flex left">
  80. <img src="@/assets/images/processImage.vue/riq.png" />
  81. {{ getTime(item.action_time) }}</span>
  82. <span class="image-count mar-left-10 flex left">
  83. <img src="@/assets/images/processImage.vue/tup.png" />
  84. {{ item.items?.length || 0 }} {{ $t('photoShot.imageCount') }}</span>
  85. <span v-if="!item.syncConfig" class="mar-left-10">{{ $t('message.cannotReadConfig') }}</span>
  86. </div>
  87. </div>
  88. <div class="history-item-right">
  89. <el-dropdown :disabled="runLoading || takePictureLoading" trigger="click">
  90. <el-button :disabled="runLoading || takePictureLoading" size="small" plain>{{ $t('photoShot.advancedGenerate') }}</el-button>
  91. <template #dropdown>
  92. <el-dropdown-menu>
  93. <el-dropdown-item
  94. v-for="menuItem in generate.children"
  95. @click.native="onGenerateCLick(menuItem, item)">{{ menuItem.name }}</el-dropdown-item>
  96. </el-dropdown-menu>
  97. </template>
  98. </el-dropdown>
  99. <el-button size="small" v-if="item.syncConfig" class="mar-left-10" :disabled="runLoading || takePictureLoading" type="primary" @click="reTakePictureNos(item.goods_art_no,item)" plain v-log="{ describe: { action: 'retake goods', goods_art_no: item.goods_art_no } }">{{ $t('photoShot.retake') }}</el-button>
  100. <el-button style="color: #FF4C00" size="small" class="mar-left-10" :disabled="runLoading || takePictureLoading" @click="delGoods({goods_art_nos:[item.goods_art_no]})" v-log="{ describe: { action: 'delete goods', goods_art_no: item.goods_art_no } }">{{ $t('photoShot.delete') }}</el-button>
  101. </div>
  102. </div>
  103. <div class="history-item-images" >
  104. <div
  105. v-for="(image, index) in item.items"
  106. :key="image.action_id || image.action_name"
  107. class="history-item_image"
  108. v-loading="!image.PhotoRecord.image_path && runAction.goods_art_no == item.goods_art_no"
  109. >
  110. <span class="tag" v-if="!image.PhotoRecord.image_path">{{ image.action_name }}</span>
  111. <el-image
  112. v-if="image.PhotoRecord.image_path"
  113. :src="thumbnailMap[image.PhotoRecord.image_path] || getFilePath(image.PhotoRecord.image_path)"
  114. :preview-src-list="getPreviewImageList(item)"
  115. hide-on-click-modal
  116. :initial-index="getPreviewIndex(item, index)"
  117. class="preview-image"
  118. fit="contain"
  119. :preview-teleported="true"
  120. lazy
  121. >
  122. <template #placeholder>
  123. <span class="tag">{{ image.action_name }}</span>
  124. </template>
  125. <template #error>
  126. <div class="image-slot">
  127. <span class="tag">{{ image.action_name }}</span>
  128. </div>
  129. </template>
  130. </el-image>
  131. <div v-else class="image-placeholder">
  132. <span class="tag">{{ image.action_name }}</span>
  133. </div>
  134. </div>
  135. </div>
  136. </div>
  137. </div>
  138. </div>
  139. </div>
  140. </div>
  141. </template>
  142. <script setup lang="ts">
  143. import { ref, onMounted, onBeforeUnmount } from 'vue'
  144. import { useI18n } from 'vue-i18n'
  145. import { ElMessageBox } from 'element-plus'
  146. import RemoteControl from '@/views/RemoteControl/index.vue'
  147. import headerBar from '@/components/header-bar/index.vue'
  148. import hardwareCheck from '@/components/check/index.vue'
  149. import usePhotography from './mixin/usePhotography'
  150. const { t } = useI18n()
  151. const {
  152. loading, runLoading, takePictureLoading, goodsList, pageSize, currentPage,
  153. totalPages, goods_art_no_tpl, goods_art_no, runAction, lastPhoto,
  154. showlastPhoto, goodsArtNo, searchGoodsArtNo, menu, generate,
  155. getTime, getFilePath, getPhotoRecords, delGoods, importDirs, deleteAllGoods,
  156. reTakePictureNos, onRemoteControl, initEventListeners, cleanupEventListeners,
  157. } = usePhotography()
  158. const containerRef = ref<HTMLElement | null>(null)
  159. const selectedGoods = ref<Set<string>>(new Set())
  160. const thumbnailMap = ref<Record<string, string>>({})
  161. const toggleGoods = (goodsArtNo: string) => {
  162. if (selectedGoods.value.has(goodsArtNo)) {
  163. selectedGoods.value.delete(goodsArtNo)
  164. } else {
  165. selectedGoods.value.add(goodsArtNo)
  166. }
  167. selectedGoods.value = new Set(selectedGoods.value)
  168. }
  169. const handleSearch = () => {
  170. getPhotoRecords({ goods_art_no: searchGoodsArtNo.value })
  171. }
  172. const closeLastPhoto = () => {
  173. showlastPhoto.value = false
  174. }
  175. const handleDeleteAll = async () => {
  176. try {
  177. await ElMessageBox.confirm(t('message.confirmDeleteAll'), t('common.tips'), {
  178. confirmButtonText: t('common.confirm'),
  179. cancelButtonText: t('common.cancel'),
  180. })
  181. deleteAllGoods()
  182. } catch (e) {}
  183. }
  184. const getPreviewImageList = (item: any) => {
  185. if (!item || !item.items) return []
  186. return item.items.filter((img: any) => img.PhotoRecord?.image_path).map((img: any) => getFilePath(img.PhotoRecord.image_path))
  187. }
  188. const getPreviewIndex = (item: any, currentIndex: number) => {
  189. if (!item || !item.items) return 0
  190. let previewIndex = 0
  191. for (let i = 0; i <= currentIndex; i++) {
  192. if (item.items[i]?.PhotoRecord?.image_path) {
  193. if (i === currentIndex) break
  194. previewIndex++
  195. }
  196. }
  197. return previewIndex
  198. }
  199. onMounted(async () => {
  200. initEventListeners()
  201. await getPhotoRecords()
  202. })
  203. onBeforeUnmount(() => {
  204. cleanupEventListeners()
  205. })
  206. </script>
  207. <style lang="scss">
  208. .koutu-image-popper {
  209. width: calc(100vw - 470px) !important;
  210. right: 70px !important;
  211. top: 100px !important;
  212. height: calc(100vh - 170px) !important;
  213. transform: translate(0px, 0px) !important;
  214. .el-image { width: 100%; height: 100%; display: block; .el-image__inner { width: 100%; height: 100%; display: block; } }
  215. }
  216. </style>
  217. <style scoped lang="scss">
  218. .photography-page { position: relative; }
  219. .main-container { position: relative; display: flex; }
  220. .history-section { width: 100%; min-height: calc(100vh - 30px); display: flex; flex-direction: column; padding: 20px; }
  221. .history-warp { flex: 1; }
  222. .history-item { background: #FFFFFF; box-shadow: 0px 2px 4px 0px rgba(23,33,71,0.1); border-radius: 10px; border: 1px solid #D9DEE6; margin-bottom: 20px; }
  223. .history-item-header { display: flex; justify-content: space-between; align-items: center; padding: 12px 16px; border-bottom: 1px solid #f0f0f0; }
  224. .history-item-left { display: flex; align-items: center; gap: 12px; }
  225. .goods-art-no { font-weight: 600; font-size: 16px; color: #333; }
  226. .history-item-meta { display: flex; align-items: center; gap: 4px; font-size: 12px; color: #8C92A7; }
  227. .history-item-right { display: flex; align-items: center; gap: 8px; }
  228. .history-item-images { display: flex; flex-wrap: wrap; padding: 12px 16px; gap: 12px; }
  229. .history-item_image { width: 120px; height: 120px; border-radius: 8px; overflow: hidden; background: #f5f5f5; display: flex; align-items: center; justify-content: center; position: relative; }
  230. .preview-image { width: 120px; height: 120px; }
  231. .image-placeholder { width: 120px; height: 120px; display: flex; align-items: center; justify-content: center; background: #f0f0f0; }
  232. .tag { position: absolute; bottom: 4px; left: 4px; background: rgba(0,0,0,0.5); color: #fff; font-size: 10px; padding: 2px 6px; border-radius: 4px; }
  233. .image-count { display: flex; align-items: center; gap: 4px; }
  234. .last-photo { position: fixed; top: 50px; right: 20px; z-index: 9999; width: 300px; background: #fff; border-radius: 8px; box-shadow: 0 4px 12px rgba(0,0,0,0.15); padding: 8px; }
  235. .close-btn { position: absolute; top: 8px; right: 8px; z-index: 1; }
  236. ::v-deep .el-checkbox__input { transform: scale(1.4); }
  237. .search-bar { margin-bottom: 15px; display: flex; justify-content: flex-start; }
  238. </style>