albumCarouselItem.vue 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582
  1. <template>
  2. <div class="carousel-container">
  3. <!-- 图片列表 -->
  4. <div class="carousel-wrapper" :style="carouselStyle">
  5. <!-- <photo-provider v-if="images" :photo-closable="true" @visibleChange="handleVisibleChange">
  6. <template v-for="(photo, index) in images"
  7. :key="photo.id">
  8. <album-draw-box :isShowNum="0" :farmId="766" :photo="photo" :current="currentIndex" :index="index" :length="images.length"
  9. ></album-draw-box>
  10. </template>
  11. </photo-provider> -->
  12. <div
  13. class="carousel-img"
  14. v-for="(photo, index) in images"
  15. :key="index"
  16. >
  17. <div class="label-text" v-if="labelText">{{ labelText }}</div>
  18. <img class="img-dom" :index="index" @click="clickPhoto(photo)" :src="getPhotoSrc(photo)" alt="" />
  19. <div class="carousel-img-mask" v-if="!isAchievementImgs">
  20. <div class="mask-content">
  21. <div class="mask-line line-top">
  22. <span class="date-text">{{ imgData?.executeDate }}</span>
  23. <span class="line-separator">|</span>
  24. <span class="executor-text">执行人:{{ imgData?.executeName }}</span>
  25. </div>
  26. <div class="mask-line line-middle">
  27. <span class="work-name">{{ imgData?.farmWorkName }}</span>
  28. <span class="line-separator">|</span>
  29. <span class="location-text">
  30. <img src="@/assets/watermark/address.png" alt="location" class="location-icon" />
  31. {{ imgData?.farmName }}
  32. </span>
  33. </div>
  34. <div class="mask-line line-bottom">
  35. <span class="prescription-text">药物处方:{{ prescriptionText }}</span>
  36. </div>
  37. </div>
  38. </div>
  39. <div class="carousel-img-mask" v-if="isAchievementImgs && imgData?.reCheckText">
  40. <div class="mask-content">
  41. <div class="review-effect-text">复核成效</div>
  42. <div class="review-effect-content">
  43. <span class="review-effect-content-text">{{ imgData?.reCheckText }}</span>
  44. </div>
  45. </div>
  46. </div>
  47. </div>
  48. </div>
  49. <!-- 左右箭头 -->
  50. <div @click.stop="prev" v-if="currentIndex !== 0" class="arrow left-arrow">
  51. <el-icon color="#F0D09C"><ArrowLeftBold /></el-icon>
  52. </div>
  53. <div @click.stop="next" v-if="images && currentIndex !== images.length - 1" class="arrow right-arrow">
  54. <el-icon color="#F0D09C"><ArrowRightBold /></el-icon>
  55. </div>
  56. <div class="curren-img" v-if="currentPhoto">
  57. <div ref="currentImgRef" class="carousel-img" :class="{ noFit: isAchievementImgs }">
  58. <img class="img-dom" :src="currentPhoto" alt="" />
  59. <img src="@/assets/img/home/qrcode.png" alt="" class="code-icon" />
  60. <div class="carousel-img-mask" v-if="!isAchievementImgs">
  61. <div class="mask-content">
  62. <div class="mask-line line-top">
  63. <span class="date-text">{{ imgData?.executeDate }}</span>
  64. <span class="line-separator">|</span>
  65. <span class="executor-text">执行人:{{ imgData?.executeName }}</span>
  66. </div>
  67. <div class="mask-line line-middle">
  68. <span class="work-name">{{ imgData?.farmWorkName }}</span>
  69. <span class="line-separator">|</span>
  70. <span class="location-text">
  71. <img src="@/assets/watermark/address.png" alt="location" class="location-icon" />
  72. {{ imgData?.farmName }}
  73. </span>
  74. </div>
  75. <div class="mask-line line-bottom">
  76. <span class="prescription-text">药物处方:{{ prescriptionText }}</span>
  77. </div>
  78. </div>
  79. </div>
  80. <div class="carousel-img-mask" v-if="isAchievementImgs && imgData?.reCheckText">
  81. <div class="mask-content">
  82. <div class="review-effect-text">复核成效</div>
  83. <div class="review-effect-content">
  84. <span class="review-effect-content-text">{{ imgData?.reCheckText }}</span>
  85. </div>
  86. </div>
  87. </div>
  88. </div>
  89. </div>
  90. <popup class="cavans-popup" v-model:show="showPopup">
  91. <div class="cavans-content">
  92. <img class="current-img" :src="previewCanvas" alt="" />
  93. </div>
  94. <!-- 底部操作按钮 -->
  95. <div class="bottom-actions" @click.stop="showPopup = false">
  96. <div class="action-buttons">
  97. <div class="action-btn text-btn">
  98. &lt;&lt;长按图片保存或转发&gt;&gt;
  99. </div>
  100. <!-- <div class="action-btn green-btn" @click.stop="handleWechat">
  101. <div class="icon-circle">
  102. <img src="@/assets/img/home/wechat.png" alt="" />
  103. </div>
  104. <span class="btn-label">微信</span>
  105. </div>
  106. <div class="action-btn orange-btn" @click.stop="handleSaveImage">
  107. <div class="icon-circle">
  108. <el-icon :size="24"><Download /></el-icon>
  109. </div>
  110. <span class="btn-label">保存图片</span>
  111. </div> -->
  112. </div>
  113. <div class="cancel-btn" @click="handleCancel">取消</div>
  114. </div>
  115. </popup>
  116. </div>
  117. </template>
  118. <script setup>
  119. import { Popup } from "vant";
  120. import { toRefs, ref, computed, onMounted, onUnmounted, nextTick, watch } from "vue";
  121. import AlbumDrawBox from "./albumDrawBox.vue";
  122. import html2canvas from "html2canvas";
  123. import { base_img_url2 } from "@/api/config";
  124. import "./cacheImg.js";
  125. const props = defineProps({
  126. images: {
  127. type: Array,
  128. required: true,
  129. },
  130. labelText: {
  131. type: String,
  132. default: "",
  133. },
  134. isAchievementImgs: {
  135. type: Boolean,
  136. default: false,
  137. },
  138. imgData: {
  139. type: Object,
  140. default: () => {},
  141. },
  142. });
  143. const { images, labelText, isAchievementImgs } = toRefs(props);
  144. let timer = null;
  145. const currentIndex = ref(0);
  146. onMounted(() => {
  147. updateImagePosition();
  148. clearAndRestartTimer();
  149. prescriptionText.value = buildPrescriptionText(props.imgData.prescriptionList);
  150. });
  151. onUnmounted(() => {
  152. clearInterval(timer);
  153. });
  154. watch(() => props.imgData, (newVal) => {
  155. if (newVal && newVal.prescriptionList) {
  156. prescriptionText.value = buildPrescriptionText(newVal.prescriptionList);
  157. }
  158. });
  159. const prescriptionText = ref("");
  160. function buildPrescriptionText(list) {
  161. try {
  162. return list
  163. .map((group) =>
  164. (group.pesticideFertilizerList || [])
  165. .map((p) => p.defaultName || p.pesticideFertilizerName || "")
  166. .filter(Boolean)
  167. .join("+")
  168. )
  169. .filter(Boolean)
  170. .join("+");
  171. } catch {
  172. return "";
  173. }
  174. }
  175. const updateImagePosition = () => {
  176. carouselStyle.value.transform = `translateX(-${currentIndex.value * 100}%)`;
  177. };
  178. const clickPhotoShow = () => {
  179. if (timer) {
  180. clearInterval(timer);
  181. }
  182. };
  183. // 图片显隐切换回调
  184. const handleVisibleChange = ({ visible }) => {
  185. if (visible.value) {
  186. if (timer) {
  187. clearInterval(timer);
  188. }
  189. } else {
  190. clearAndRestartTimer();
  191. }
  192. };
  193. // 计算轮播图样式
  194. const carouselStyle = computed(() => {
  195. return {
  196. transform: `translateX(-${currentIndex.value * 100}%)`,
  197. };
  198. });
  199. // 下一张图片
  200. const next = () => {
  201. // 图片总数
  202. const totalImages = images.value.length;
  203. currentIndex.value = (currentIndex.value + 1) % totalImages;
  204. updateImagePosition();
  205. clearAndRestartTimer();
  206. };
  207. // 上一张图片
  208. const prev = () => {
  209. // 图片总数
  210. const totalImages = images.value.length;
  211. currentIndex.value = (currentIndex.value - 1 + totalImages) % totalImages;
  212. updateImagePosition();
  213. clearAndRestartTimer();
  214. };
  215. const clearAndRestartTimer = () => {
  216. if (timer) {
  217. clearInterval(timer);
  218. }
  219. // timer = setInterval(next, 5000);
  220. };
  221. const showPopup = ref(false);
  222. const currentPhoto = ref(null);
  223. const previewCanvas = ref(null);
  224. const currentImgRef = ref(null);
  225. const clickPhoto = (photo) => {
  226. currentPhoto.value = getPhotoSrc(photo);
  227. nextTick(async () => {
  228. const canvas = await html2canvas(currentImgRef.value, {
  229. backgroundColor: "#ffffff00",
  230. scrollY: -window.scrollY, // 处理滚动条位置
  231. allowTaint: true, // 允许跨域图片
  232. useCORS: true, // 使用CORS
  233. scale: 2, // 提高分辨率(2倍)
  234. height: currentImgRef.value.scrollHeight, // 设置完整高度
  235. width: currentImgRef.value.scrollWidth, // 设置完整宽度
  236. logging: true, // 开启日志(调试用)
  237. });
  238. // 转换为图片并下载
  239. const image = canvas.toDataURL("image/png");
  240. setTimeout(() => {
  241. previewCanvas.value = image;
  242. showPopup.value = true;
  243. }, 100);
  244. });
  245. };
  246. const handleSaveImage = () => {
  247. downloadImage(previewCanvas.value, '执行照片');
  248. };
  249. function downloadImage(dataUrl, filename) {
  250. const link = document.createElement("a");
  251. link.href = dataUrl;
  252. link.download = filename;
  253. document.body.appendChild(link);
  254. link.click();
  255. document.body.removeChild(link);
  256. }
  257. const getPhotoSrc = (photo) => {
  258. // if (isAchievementImgs.value) {
  259. // return photo;
  260. // }
  261. return base_img_url2 + (photo.cloudFilename ? photo.cloudFilename : photo);
  262. };
  263. </script>
  264. <style lang="scss" scoped>
  265. @import "src/styles/index";
  266. .carousel-container {
  267. position: relative;
  268. width: 100%;
  269. overflow: hidden;
  270. margin: 0 auto;
  271. .curren-img {
  272. position: fixed;
  273. bottom: 0;
  274. left: 0;
  275. right: 0;
  276. width: 80%;
  277. height: 100%;
  278. margin: 0 auto;
  279. z-index: -1;
  280. pointer-events: none;
  281. .carousel-img {
  282. width: 100%;
  283. position: relative;
  284. overflow: hidden;
  285. }
  286. .img-dom {
  287. width: 100%;
  288. height: 100%;
  289. object-fit: cover;
  290. border-radius: 8px;
  291. }
  292. }
  293. .carousel-wrapper {
  294. display: flex;
  295. transition: transform 0.5s ease;
  296. width: 100%;
  297. .carousel-img {
  298. width: calc(100vw - 48px);
  299. min-width: calc(100vw - 48px);
  300. // min-width: 312px;
  301. height: 255px;
  302. object-fit: cover;
  303. position: relative;
  304. overflow: hidden;
  305. &.noFit {
  306. height: auto;
  307. object-fit: contain;
  308. }
  309. .img-dom {
  310. width: 100%;
  311. height: 100%;
  312. object-fit: cover;
  313. border-radius: 8px;
  314. }
  315. &.noFit {
  316. .img-dom {
  317. height: auto;
  318. object-fit: contain;
  319. }
  320. }
  321. }
  322. }
  323. .code-icon {
  324. position: absolute;
  325. right: 12px;
  326. top: 12px;
  327. width: 40px;
  328. }
  329. .carousel-img-mask {
  330. position: absolute;
  331. bottom: -2px;
  332. left: -2px;
  333. width: calc(100% + 2px);
  334. height: 80%;
  335. background: linear-gradient(360deg, rgba(0, 0, 0, 0.7) 0%, rgba(0, 0, 0, 0.4) 25%, rgba(0, 0, 0, 0) 40%);
  336. z-index: 1;
  337. pointer-events: none;
  338. display: flex;
  339. align-items: flex-end;
  340. padding: 8px 12px;
  341. box-sizing: border-box;
  342. border-radius: 8px 8px 12px 12px;
  343. .mask-content {
  344. width: 100%;
  345. color: #ffffff;
  346. font-size: 10px;
  347. line-height: 15px;
  348. }
  349. .review-effect-text {
  350. font-family: "PangMenZhengDao";
  351. font-size: 16px;
  352. margin-bottom: 4px;
  353. }
  354. .review-effect-content {
  355. font-size: 10px;
  356. line-height: 15px;
  357. }
  358. .mask-line {
  359. display: flex;
  360. align-items: center;
  361. flex-wrap: wrap;
  362. }
  363. .prescription-text {
  364. display: flex;
  365. .prescription-text-label {
  366. flex: none;
  367. }
  368. }
  369. .line-middle {
  370. margin-top: 4px;
  371. .work-name {
  372. font-family: "PangMenZhengDao", system-ui, -apple-system, BlinkMacSystemFont, "Helvetica Neue", Arial,
  373. sans-serif;
  374. font-size: 17px;
  375. }
  376. }
  377. .line-bottom {
  378. margin-top: 4px;
  379. }
  380. .date-text {
  381. font-family: "PangMenZhengDao";
  382. font-size: 12px;
  383. }
  384. .date-text,
  385. .executor-text,
  386. .location-text {
  387. white-space: nowrap;
  388. }
  389. .location-icon {
  390. width: 9px;
  391. height: 10px;
  392. margin-right: 2px;
  393. }
  394. .line-separator {
  395. margin: 0 6px;
  396. }
  397. }
  398. .label-text {
  399. position: absolute;
  400. top: 0;
  401. left: 0;
  402. padding: 4px 10px;
  403. background: rgba(54, 52, 52, 0.8);
  404. color: #fff;
  405. font-size: 12px;
  406. border-radius: 8px 0 8px 0;
  407. z-index: 1;
  408. }
  409. .blur-bg {
  410. position: absolute;
  411. top: 0;
  412. width: 100%;
  413. height: 100%;
  414. backdrop-filter: blur(1.4px);
  415. .blur-content {
  416. border-radius: 8px;
  417. background: rgba(0, 0, 0, 0.5);
  418. width: 100%;
  419. height: 100%;
  420. display: flex;
  421. flex-direction: column;
  422. align-items: center;
  423. justify-content: center;
  424. font-size: 12px;
  425. color: #fff;
  426. .blur-img {
  427. img {
  428. width: 54px;
  429. position: relative;
  430. left: 4px;
  431. top: 4px;
  432. }
  433. }
  434. .blur-text {
  435. padding: 8px 0;
  436. text-align: center;
  437. line-height: 1.5;
  438. }
  439. .blur-btn {
  440. padding: 0 40px;
  441. box-shadow: 0 -2px 2px #86c9ff;
  442. height: 28px;
  443. line-height: 28px;
  444. border-radius: 50px;
  445. background: rgba(33, 153, 248, 0.7);
  446. // background: linear-gradient(#86C9FF, rgba(255, 255, 255, 0));
  447. }
  448. }
  449. }
  450. .arrow {
  451. position: absolute;
  452. top: 50%;
  453. transform: translateY(-50%);
  454. background: rgba(0, 0, 0, 0.5);
  455. width: rpx(72);
  456. height: rpx(72);
  457. border-radius: 50%;
  458. display: inline-flex;
  459. align-items: center;
  460. justify-content: center;
  461. cursor: pointer;
  462. pointer-events: all;
  463. }
  464. .left-arrow {
  465. left: rpx(32);
  466. }
  467. .right-arrow {
  468. right: rpx(32);
  469. }
  470. }
  471. .cavans-popup {
  472. width: 100%;
  473. max-width: 100%;
  474. max-height: 90vh;
  475. background: none;
  476. border-radius: 12px;
  477. overflow: auto;
  478. display: flex;
  479. flex-direction: column;
  480. backdrop-filter: 4px;
  481. .cavans-content {
  482. text-align: center;
  483. padding: 16px;
  484. .current-img {
  485. width: 100%;
  486. }
  487. }
  488. // 底部操作按钮
  489. .bottom-actions {
  490. flex-shrink: 0;
  491. .action-buttons {
  492. padding: 16px;
  493. display: flex;
  494. justify-content: space-around;
  495. .action-btn {
  496. display: flex;
  497. flex-direction: column;
  498. align-items: center;
  499. cursor: pointer;
  500. &.text-btn {
  501. font-size: 12px;
  502. color: rgba(255, 255, 255, 0.7);
  503. }
  504. .icon-circle {
  505. width: 48px;
  506. height: 48px;
  507. border-radius: 50%;
  508. display: flex;
  509. align-items: center;
  510. justify-content: center;
  511. color: #fff;
  512. margin-bottom: 4px;
  513. .el-icon {
  514. color: #fff;
  515. }
  516. img {
  517. width: 50px;
  518. }
  519. }
  520. &.blue-btn .icon-circle {
  521. background: #2199f8;
  522. }
  523. &.green-btn .icon-circle {
  524. background: #07c160;
  525. }
  526. &.orange-btn .icon-circle {
  527. background: #ff790b;
  528. }
  529. .btn-label {
  530. font-size: 12px;
  531. color: #fff;
  532. }
  533. }
  534. }
  535. .cancel-btn {
  536. text-align: center;
  537. font-size: 18px;
  538. color: #fff;
  539. cursor: pointer;
  540. }
  541. }
  542. }
  543. </style>