shot.vue 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968
  1. <template>
  2. <headerBar
  3. title="拍摄商品"
  4. showUser
  5. showDeviceStatus
  6. :menu="menu"
  7. />
  8. <hardware-check/>
  9. <div class="photography-page flex-col">
  10. <div class="main-container">
  11. <div class="content-wrapper flex-col">
  12. <div class="step-section">
  13. <div class="step-number flex-col"><span class="text_22">1</span></div>
  14. <div class="step-one flex-col">
  15. <div class="step-header flex-row">
  16. <span class="step-title">第一步:获取商品货号</span>
  17. </div>
  18. <div class="step-content">
  19. <div class="input-container flex-row">
  20. <el-input class="input-item" ref="goodsArtNo" clearable v-model="goods_art_no" placeholder="请输入货号"> </el-input>
  21. </div>
  22. <div class="auto-method flex-row justify-between">
  23. <img class="step-icon" referrerpolicy="no-referrer" src="@/assets/images/Photography/step1-icon.png" />
  24. <div class="text-method-tag flex-col mar-left-10"><span class="text_4">自动获取</span></div>
  25. <span class="method-description mar-left-10">用遥控器扫描商品资料二维码</span>
  26. </div>
  27. </div>
  28. </div>
  29. </div>
  30. <div class="step-section">
  31. <div class="step-number flex-col"><span class="text_22">2</span></div>
  32. <div class="step-two flex-col justify-between">
  33. <span class="step-title">第二步:启动拍摄(根据按遥控器左右键启动)</span>
  34. <div class="shooting-container flex-col">
  35. <div class="shooting-tips flex-row justify-between">
  36. <img class="info-icon" referrerpolicy="no-referrer" src="@/assets/images/Photography/info-icon.png" />
  37. <span class="tips-text">遥控左右按键可启动拍摄,中间按钮可在拍摄5张主图后解锁,用于拍摄自定义图</span>
  38. </div>
  39. <div class="wifi mar-top-20">
  40. <img referrerpolicy="no-referrer"
  41. src="@/assets/images/Photography/wifi.png" style="width: 60px" />
  42. </div>
  43. <div class="remote-control-wrap">
  44. <RemoteControl
  45. @onRemoteControl="onRemoteControl"
  46. :canStop="runLoading || takePictureLoading"
  47. />
  48. </div>
  49. </div>
  50. </div>
  51. </div>
  52. </div>
  53. <div class="last-photo" v-show="showlastPhoto && (lastPhoto as any)?.file_path" v-key="(lastPhoto as any)?.file_path">
  54. <el-button
  55. class="close-btn"
  56. icon="Close"
  57. circle
  58. @click="closeLastPhoto"
  59. />
  60. <div class="last-photo-content">
  61. <div class="action-name-tag" v-if="(lastPhoto as any)?.action_name">{{ (lastPhoto as any)?.action_name }}</div>
  62. <el-image :src="getFilePath((lastPhoto as any)?.file_path || '')" fit="contain" ></el-image>
  63. </div>
  64. </div>
  65. <div class="history-section flex-col koutu-section">
  66. <div class="search-bar">
  67. <el-input
  68. v-model="searchGoodsArtNo"
  69. placeholder="搜索货号"
  70. clearable
  71. style="width: 300px"
  72. @keyup.enter="handleSearch"
  73. >
  74. <template #append>
  75. <el-button @click="handleSearch">搜索</el-button>
  76. </template>
  77. </el-input>
  78. </div>
  79. <div class="history-warp" ref="containerRef">
  80. <div v-if="!goodsList.length" class="fs-14 c-666 mar-top-50">
  81. {{ loading ? '数据正在加载中,请稍候...' : '暂无数据,请先进行拍摄'}}
  82. </div>
  83. <div v-else class="history-item" v-for="item in goodsList" :key="item.goods_art_no" >
  84. <div class="history-item-header">
  85. <div class="history-item-left">
  86. <el-checkbox
  87. :model-value="selectedGoods.has(item.goods_art_no)"
  88. @change="toggleGoods(item.goods_art_no)"
  89. class="goods-checkbox"
  90. />
  91. <span class="goods-art-no">{{ item.goods_art_no }}</span>
  92. <div class="history-item-meta ">
  93. <span class="action-time flex left">
  94. <img src="@/assets/images/processImage.vue/riq.png" />
  95. {{ getTime(item.action_time) }}</span>
  96. <span class="image-count mar-left-10 flex left">
  97. <img src="@/assets/images/processImage.vue/tup.png" />
  98. {{ item.items?.length || 0 }}张图片</span>
  99. <span v-if="!item.syncConfig" class="mar-left-10">无法读取到该商品拍摄配置,如需重新生成请删除该商品并重新拍摄</span>
  100. </div>
  101. </div>
  102. <div class="history-item-right">
  103. <el-dropdown :disabled="runLoading || takePictureLoading" trigger="click">
  104. <el-button :disabled="runLoading || takePictureLoading" size="small" plain>高级生成</el-button>
  105. <template #dropdown>
  106. <el-dropdown-menu>
  107. <el-dropdown-item
  108. v-for="menu in generate.children"
  109. @click.native="onGenerateCLick(menu,item)">{{ menu.name }}</el-dropdown-item>
  110. </el-dropdown-menu>
  111. </template>
  112. </el-dropdown>
  113. <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: '重拍货号', goods_art_no: item.goods_art_no } }">重拍</el-button>
  114. <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: '删除货号', goods_art_no: item.goods_art_no } }">删除</el-button>
  115. </div>
  116. </div>
  117. <div class="history-item-images" >
  118. <div
  119. v-for="(image, index) in item.items"
  120. :key="image.action_id || image.action_name"
  121. class="history-item_image"
  122. v-loading="!image.PhotoRecord.image_path && runAction.goods_art_no == item.goods_art_no"
  123. >
  124. <!-- <span class="tag" v-if="!image.PhotoRecord.image_path">{{ image.action_name }}</span>-->
  125. <div class="el-image_view" >
  126. <el-image
  127. :src="thumbnailMap[image.PhotoRecord.image_path] || getFilePath(image.PhotoRecord.image_path)"
  128. :preview-src-list="getPreviewImageList(item)"
  129. hide-on-click-modal
  130. :initial-index="getPreviewIndex(item, index)"
  131. class="preview-image"
  132. fit="contain"
  133. :preview-teleported="true"
  134. lazy
  135. >
  136. <template #placeholder>
  137. <span class="tag">{{ image.action_name }}</span>
  138. </template>
  139. <template #error>
  140. <div class="image-slot">
  141. <span class="tag">{{ image.action_name }}</span>
  142. </div>
  143. </template>
  144. </el-image>
  145. <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>
  146. </div>
  147. </div>
  148. </div>
  149. </div>
  150. </div>
  151. <div class="footer-controls">
  152. <div class="footer-left">
  153. <el-checkbox
  154. :model-value="isSelectAll"
  155. :indeterminate="isIndeterminate"
  156. @change="toggleSelectAll"
  157. class="select-all-checkbox"
  158. >
  159. 全选
  160. </el-checkbox>
  161. <span class="image-count-text">
  162. 已选择 <span style="color: #2957FF">{{ selectedImageCount }}</span> 张图片 共 <span style="color: #2957FF">{{ totalImageCount }}</span> 张图片
  163. </span>
  164. </div>
  165. <div class="footer-right">
  166. <div class="pagination-container" style="padding: 10px 0; text-align: center;">
  167. <el-pagination
  168. :page-count="totalPages"
  169. :current-page="currentPage"
  170. @current-change="(page) => getPhotoRecords({ page })"
  171. layout="prev, pager, next"
  172. :page-size="pageSize"
  173. />
  174. </div>
  175. <el-button
  176. :disabled="selectedGoods.size === 0 || runLoading || takePictureLoading"
  177. @click="deleteSelected"
  178. v-log="{ describe: { action: '删除选中货号' } }"
  179. >
  180. 删除
  181. </el-button>
  182. <el-button
  183. :disabled="!goodsList.length || runLoading || takePictureLoading"
  184. @click="handleDeleteAll"
  185. style="color: #FF4C00"
  186. v-log="{ describe: { action: '删除所有货号' } }"
  187. >
  188. 删除所有
  189. </el-button>
  190. <el-button
  191. type="primary"
  192. :disabled="!goodsList.length || runLoading || takePictureLoading"
  193. @click="openPhotographyDetail()"
  194. v-log="{ describe: { action: '点击开始生成' } }"
  195. >
  196. <img src="@/assets/images/processImage.vue/sc.png" />
  197. 开始生成
  198. <img src="@/assets/images/processImage.vue/go.png" class="go"/>
  199. </el-button>
  200. </div>
  201. </div>
  202. </div>
  203. </div>
  204. </div>
  205. </template>
  206. <script setup lang="ts">
  207. import headerBar from '@/components/header-bar/index.vue'
  208. import { onMounted, onBeforeUnmount, ref, computed, watch, nextTick } from 'vue'
  209. import HardwareCheck from '@/components/check/index.vue'
  210. // @ts-ignore
  211. import RemoteControl from '@/views/RemoteControl/index.vue'
  212. import usePhotography from './mixin/usePhotography'
  213. import { useThumbnails } from './composables/useThumbnails'
  214. import generate from '@/utils/menus/generate'
  215. import { ElMessageBox } from 'element-plus'
  216. const {
  217. loading,
  218. runLoading,
  219. takePictureLoading,
  220. goodsList,
  221. goods_art_no,
  222. runAction,
  223. lastPhoto,
  224. showlastPhoto,
  225. goodsArtNo,
  226. searchGoodsArtNo,
  227. menu,
  228. getTime,
  229. getFilePath,
  230. getPhotoRecords,
  231. delGoods,
  232. del,
  233. deleteAllGoods,
  234. reTakePicture,
  235. reTakePictureNos,
  236. onRemoteControl,
  237. openPhotographyDetail,
  238. onGenerateCLick,
  239. initEventListeners,
  240. cleanupEventListeners,
  241. pageSize,
  242. currentPage,
  243. totalPages,
  244. } = usePhotography()
  245. // 搜索货号
  246. const handleSearch = () => {
  247. getPhotoRecords({ page: 1, goods_art_no: searchGoodsArtNo.value })
  248. }
  249. // 关闭最后拍摄的照片预览
  250. const closeLastPhoto = () => {
  251. showlastPhoto.value = false
  252. }
  253. // 选中的货号列表
  254. const selectedGoods = ref<Set<string>>(new Set())
  255. // thumbnails
  256. const containerRef = ref<HTMLElement | null>(null)
  257. const { thumbnailMap, observe, stop } = useThumbnails(getFilePath)
  258. // 全选状态
  259. const isSelectAll = computed(() => {
  260. return goodsList.value.length > 0 && selectedGoods.value.size === goodsList.value.length
  261. })
  262. // 是否半选状态
  263. const isIndeterminate = computed(() => {
  264. return selectedGoods.value.size > 0 && selectedGoods.value.size < goodsList.value.length
  265. })
  266. // 切换单个货号的选中状态
  267. const toggleGoods = (goodsArtNo: string) => {
  268. if (selectedGoods.value.has(goodsArtNo)) {
  269. selectedGoods.value.delete(goodsArtNo)
  270. } else {
  271. selectedGoods.value.add(goodsArtNo)
  272. }
  273. }
  274. // 全选/取消全选
  275. const toggleSelectAll = () => {
  276. if (isSelectAll.value) {
  277. selectedGoods.value.clear()
  278. } else {
  279. goodsList.value.forEach((item: any) => {
  280. selectedGoods.value.add(item.goods_art_no)
  281. })
  282. }
  283. }
  284. // 计算已选择的图片数量
  285. const selectedImageCount = computed(() => {
  286. let count = 0
  287. goodsList.value.forEach((item: any) => {
  288. if (selectedGoods.value.has(item.goods_art_no)) {
  289. count += item.items?.length || 0
  290. }
  291. })
  292. return count
  293. })
  294. // 计算总图片数量
  295. const totalImageCount = computed(() => {
  296. let count = 0
  297. goodsList.value.forEach((item: any) => {
  298. count += item.items?.length || 0
  299. })
  300. return count
  301. })
  302. // 删除选中的货号
  303. const deleteSelected = async () => {
  304. if (selectedGoods.value.size === 0) {
  305. return
  306. }
  307. try {
  308. await ElMessageBox.confirm(
  309. `确定要删除选中的 ${selectedGoods.value.size} 个货号的拍摄数据吗?`,
  310. '提示',
  311. {
  312. confirmButtonText: '确定',
  313. cancelButtonText: '取消',
  314. }
  315. )
  316. const goodsArtNos = Array.from(selectedGoods.value)
  317. await del({ goods_art_nos: goodsArtNos })
  318. // 删除成功后清空选中状态
  319. selectedGoods.value.clear()
  320. } catch (e) {
  321. // 用户取消
  322. }
  323. }
  324. // 删除所有货号
  325. const handleDeleteAll = async () => {
  326. try {
  327. await ElMessageBox.confirm(
  328. '确定要删除所有货号的拍摄数据吗?',
  329. '提示',
  330. {
  331. confirmButtonText: '确定',
  332. cancelButtonText: '取消',
  333. }
  334. )
  335. deleteAllGoods()
  336. } catch (e) {
  337. // 用户取消
  338. }
  339. }
  340. // 获取预览图片列表(只包含有图片路径的,保持原始顺序)
  341. const getPreviewImageList = (item: any) => {
  342. if (!item || !item.items) return []
  343. return item.items
  344. .filter((img: any) => img.PhotoRecord?.image_path)
  345. .map((img: any) => getFilePath(img.PhotoRecord.image_path))
  346. }
  347. // 获取当前图片在预览列表中的索引
  348. const getPreviewIndex = (item: any, currentIndex: number) => {
  349. if (!item || !item.items) return 0
  350. // 计算当前图片在过滤后的预览列表中的索引
  351. let previewIndex = 0
  352. for (let i = 0; i <= currentIndex; i++) {
  353. if (item.items[i]?.PhotoRecord?.image_path) {
  354. if (i === currentIndex) break
  355. previewIndex++
  356. }
  357. }
  358. return previewIndex
  359. }
  360. onMounted(async () => {
  361. await getPhotoRecords()
  362. initEventListeners()
  363. nextTick(() => {
  364. observe(containerRef, goodsList)
  365. })
  366. })
  367. onBeforeUnmount(() => {
  368. cleanupEventListeners()
  369. stop()
  370. })
  371. watch(goodsList, () => {
  372. nextTick(() => observe(containerRef, goodsList))
  373. })
  374. </script>
  375. <style lang="scss">
  376. .koutu-image-popper {
  377. width: calc(100vw - 470px) !important;
  378. right: 70px !important;
  379. top: 100px !important;
  380. height: calc(100vh - 170px) !important;
  381. transform: translate(0px, 0px) !important;
  382. .el-image {
  383. width: 100%;
  384. height:100%;
  385. display: block;
  386. .el-image__inner {
  387. width: 100%;
  388. height:100%;
  389. display: block;
  390. }
  391. }
  392. }
  393. </style>
  394. <style scoped lang="scss">
  395. .photography-page {
  396. background-color: rgba(255, 255, 255, 1);
  397. position: relative;
  398. .main-container {
  399. position: relative;
  400. display: flex;
  401. .content-wrapper {
  402. flex-grow: 1 ;
  403. position: relative;
  404. top: 0;
  405. left: 0;
  406. right: 0;
  407. bottom: 0;
  408. width: 510px;
  409. padding: 0 50px;
  410. height: calc(100vh - 30px);
  411. margin: auto;
  412. justify-content: flex-start;
  413. flex-direction: column;
  414. overflow: hidden;
  415. border-right:1px solid rgba(0,0,0,.1);
  416. .step-section {
  417. display: flex;
  418. margin-bottom: 20px;
  419. &:last-child {
  420. margin-bottom: 0;
  421. }
  422. }
  423. .step-number {
  424. background-color: rgba(22, 119, 255, 1);
  425. border-radius: 50%;
  426. height: 32px;
  427. margin-top: 51px;
  428. width: 32px;
  429. .text_22 {
  430. width: 6px;
  431. height: 22px;
  432. overflow-wrap: break-word;
  433. color: rgba(255, 255, 255, 1);
  434. font-size: 14px;
  435. font-weight: NaN;
  436. text-align: right;
  437. white-space: nowrap;
  438. line-height: 22px;
  439. margin: 5px 0 0 13px;
  440. }
  441. }
  442. .step-one {
  443. width: 350px;
  444. height: auto;
  445. margin: 55px 0 0 5px;
  446. .step-header {
  447. width: 391px;
  448. height: 24px;
  449. margin-left: 3px;
  450. .step-title {
  451. width: 160px;
  452. height: 24px;
  453. overflow-wrap: break-word;
  454. color: rgba(0, 0, 0, 0.85);
  455. font-size: 16px;
  456. font-family: PingFangSC-Medium;
  457. font-weight: 500;
  458. text-align: left;
  459. white-space: nowrap;
  460. line-height: 24px;
  461. }
  462. .step-icon {
  463. width: 32px;
  464. height: 20px;
  465. margin-top: 4px;
  466. }
  467. .step-divider {
  468. width: 191px;
  469. height: 1px;
  470. margin: 13px 0 0 8px;
  471. }
  472. }
  473. .step-content {
  474. width: 350px;
  475. .input-container {
  476. width: calc(100% - 20px );
  477. height: 36px;
  478. margin: 10px 10px 0;
  479. .input-item {
  480. :deep(.el-input__inner){
  481. height: 36px;
  482. line-height: 36px;
  483. }
  484. }
  485. }
  486. .auto-method {
  487. width: 253px;
  488. height: 24px;
  489. margin: 28px 0 0 14px;
  490. .text-method-tag {
  491. background-color: rgba(0, 174, 30, 1);
  492. height: 24px;
  493. width: 65px;
  494. .text_4 {
  495. width: 56px;
  496. height: 20px;
  497. overflow-wrap: break-word;
  498. color: rgba(255, 255, 255, 1);
  499. font-size: 14px;
  500. font-family: PingFangSC-Semibold;
  501. font-weight: 600;
  502. text-align: left;
  503. white-space: nowrap;
  504. line-height: 20px;
  505. margin: 2px 0 0 4px;
  506. }
  507. }
  508. .method-description {
  509. width: 182px;
  510. height: 20px;
  511. overflow-wrap: break-word;
  512. color: rgba(71, 71, 71, 1);
  513. font-size: 14px;
  514. font-family: PingFangSC-Semibold;
  515. font-weight: 600;
  516. text-align: left;
  517. white-space: nowrap;
  518. line-height: 20px;
  519. margin-top: 2px;
  520. }
  521. }
  522. }
  523. }
  524. .step-two {
  525. width: 350px;
  526. height: auto;
  527. margin: 55px 0 0 5px;
  528. .step-title {
  529. width: 350px;
  530. height: 24px;
  531. overflow-wrap: break-word;
  532. color: rgba(0, 0, 0, 0.85);
  533. font-size: 16px;
  534. font-family: PingFangSC-Medium;
  535. font-weight: 500;
  536. text-align: left;
  537. white-space: nowrap;
  538. line-height: 24px;
  539. }
  540. .shooting-container {
  541. width: 353px;
  542. height: auto;
  543. .remote-control-wrap {
  544. width: 353px;
  545. height: 300px;
  546. }
  547. .shooting-tips {
  548. width: 325px;
  549. height: 40px;
  550. margin: 12px 0 0 15px;
  551. .info-icon {
  552. width: 16px;
  553. height: 16px;
  554. margin-top: 2px;
  555. }
  556. .tips-text {
  557. width: 302px;
  558. height: 40px;
  559. overflow-wrap: break-word;
  560. color: rgba(255, 76, 0, 1);
  561. font-size: 14px;
  562. font-weight: NaN;
  563. text-align: left;
  564. line-height: 20px;
  565. }
  566. }
  567. }
  568. }
  569. }
  570. }
  571. }
  572. .history-section {
  573. width: calc(100vw - 510px);
  574. height: calc(100vh - 30px);
  575. display: flex;
  576. flex-direction: column;
  577. padding: 20px;
  578. overflow-y: auto;
  579. background:#F5F6F7;
  580. .search-bar {
  581. margin-bottom: 15px;
  582. display: flex;
  583. justify-content: flex-start;
  584. }
  585. ::v-deep {
  586. .el-checkbox__input {
  587. transform: scale(1.4);
  588. }
  589. }
  590. .history-warp {
  591. flex: 1;
  592. .history-item {
  593. background: #FFFFFF;
  594. box-shadow: 0px 2px 4px 0px rgba(23,33,71,0.1);
  595. border-radius: 10px;
  596. border: 1px solid #D9DEE6;
  597. margin-bottom: 20px;
  598. .history-item-header {
  599. display: flex;
  600. justify-content: space-between;
  601. align-items: center;
  602. height: 40px;
  603. padding: 0 10px;
  604. background: linear-gradient( 90deg, #F4ECFF 0%, #DFEDFF 100%);
  605. border-radius: 10px 10px 0px 0px;
  606. .history-item-left {
  607. display: flex;
  608. align-items: center;
  609. gap: 10px;
  610. .goods-checkbox {
  611. margin-right: 0;
  612. }
  613. .goods-art-no {
  614. font-size: 16px;
  615. font-weight: 500;
  616. color: #333;
  617. }
  618. }
  619. .history-item-right {
  620. display: flex;
  621. align-items: center;
  622. ::v-deep {
  623. .el-button { height: 30px; line-height: 30px;}
  624. }
  625. }
  626. }
  627. .history-item-meta {
  628. display: flex;
  629. justify-content: space-between;
  630. align-items: center;
  631. font-size: 12px;
  632. color: #666;
  633. img {
  634. height: 14px;
  635. margin-right: 2px;
  636. }
  637. .action-time {
  638. color: #666;
  639. }
  640. .image-count {
  641. color: #666;
  642. }
  643. }
  644. .history-item-images {
  645. display: grid;
  646. grid-template-columns: repeat(5, 1fr);
  647. gap: 10px;
  648. padding: 15px;
  649. border-top: 1px solid #f0f0f0;
  650. overflow-x: auto;
  651. // 如果图片数量超过5个,允许横向滚动
  652. @media (min-width: 1200px) {
  653. grid-template-columns: repeat(5, 1fr);
  654. }
  655. // 响应式:小屏幕时每行3个
  656. @media (max-width: 768px) {
  657. grid-template-columns: repeat(3, 1fr);
  658. }
  659. }
  660. .history-item_image_wrap {
  661. padding-bottom: 0;
  662. border-bottom: none;
  663. }
  664. .history-item_image {
  665. position: relative;
  666. width: 100%;
  667. aspect-ratio: 1;
  668. background: #F7F7F7;
  669. border-radius: 10px;
  670. overflow: hidden;
  671. cursor: pointer;
  672. border: 1px solid #D9DEE6;
  673. transition: all 0.3s;
  674. .tag {
  675. color: #bbb;
  676. position: absolute;
  677. left: 0;
  678. right: 0;
  679. top: 50%;
  680. margin-top: -10px;
  681. line-height: 20px;
  682. text-align: center;
  683. font-size: 12px;
  684. z-index: 1;
  685. pointer-events: none;
  686. }
  687. .preview-image {
  688. width: 100%;
  689. height: 100%;
  690. :deep(.el-image__inner) {
  691. width: 100%;
  692. height: 100%;
  693. object-fit: cover;
  694. }
  695. }
  696. .image-placeholder {
  697. width: 100%;
  698. height: 100%;
  699. display: flex;
  700. align-items: center;
  701. justify-content: center;
  702. background: #F7F7F7;
  703. }
  704. .image-slot {
  705. width: 100%;
  706. height: 100%;
  707. display: flex;
  708. align-items: center;
  709. justify-content: center;
  710. background: #F7F7F7;
  711. }
  712. &:hover {
  713. border-color: #409eff;
  714. transform: scale(1.02);
  715. box-shadow: 0 2px 8px rgba(64, 158, 255, 0.2);
  716. }
  717. &.el-loading-parent--relative{
  718. ::v-deep {
  719. .el-loading-mask { display: none}
  720. }
  721. }
  722. }
  723. .el-image_view {
  724. display: flex;
  725. width: 100%;
  726. height: 100%;
  727. .reset-button {
  728. width: 40px;
  729. text-align: center;
  730. height: 20px;
  731. position: absolute;
  732. left:50%;
  733. top:50%;
  734. padding: 0px;
  735. margin-left:-20px;
  736. margin-top:-10px;
  737. color: #ffffff;
  738. font-size: 14px;
  739. background: rgba(0,0,0,0.6);
  740. border-radius: 12px;
  741. display: none;
  742. cursor: pointer;
  743. }
  744. &:hover {
  745. .reset-button {
  746. display: block;
  747. }
  748. }
  749. }
  750. p:first-of-type {
  751. ::v-deep {
  752. .el-loading-mask { display: block !important;}
  753. }
  754. }
  755. }
  756. }
  757. .footer-controls {
  758. display: flex;
  759. justify-content: space-between;
  760. align-items: center;
  761. padding: 0px 20px;
  762. border-top: 1px solid #D9DEE6;
  763. background-color: #fff;
  764. min-height: 50px;
  765. flex-shrink: 0;
  766. position: fixed;
  767. bottom:0;
  768. left: 510px;
  769. right: 0;
  770. font-size: 14px;
  771. z-index: 100;
  772. img {
  773. height: 12px;
  774. margin: 0 5px;
  775. }
  776. .go {
  777. height: 12px;
  778. opacity: .8;
  779. }
  780. ::v-deep {
  781. .el-button {
  782. border-radius: 10px;
  783. height: 40px;
  784. line-height: 40px;
  785. }
  786. }
  787. .footer-left {
  788. display: flex;
  789. align-items: center;
  790. gap: 10px;
  791. .select-all-checkbox {
  792. margin-right: 0;
  793. ::v-deep {
  794. .el-checkbox__label {
  795. font-size: 14px;
  796. color: #666;
  797. }
  798. }
  799. }
  800. .image-count-text {
  801. font-size: 14px;
  802. color: #333;
  803. margin-left: 0;
  804. }
  805. }
  806. .footer-right {
  807. display: flex;
  808. align-items: center;
  809. gap: 10px;
  810. }
  811. }
  812. }
  813. .last-photo{
  814. position: fixed;
  815. padding: 10px;
  816. box-shadow: 0 0 5px rgb(0 0 0 / 50%);
  817. left: 510px;
  818. top: 30px;
  819. bottom: 0px;
  820. right: 10px;
  821. z-index: 1000;
  822. background-color: rgba(0,0,0,.5);
  823. .close-btn {
  824. position: absolute;
  825. top: 20px;
  826. right: 20px;
  827. z-index: 1001;
  828. width: 32px;
  829. height: 32px;
  830. ::v-deep(.el-icon) {
  831. font-size: 16px;
  832. }
  833. }
  834. .last-photo-content {
  835. width: 100%;
  836. height: 100%;
  837. display: flex;
  838. flex-direction: column;
  839. align-items: center;
  840. justify-content: center;
  841. .action-name-tag {
  842. background: rgba(41, 87, 255, 0.9);
  843. color: #fff;
  844. padding: 8px 20px;
  845. border-radius: 20px;
  846. font-size: 16px;
  847. font-weight: 500;
  848. margin-bottom: 15px;
  849. box-shadow: 0 2px 8px rgba(41, 87, 255, 0.4);
  850. }
  851. }
  852. .el-image {
  853. width: 100%;
  854. height: calc(100% - 60px);
  855. display: block;
  856. .el-image__inner {
  857. width: 100%;
  858. height:100%;
  859. display: block;
  860. }
  861. }
  862. }
  863. </style>