albumCarouselItem.vue 15 KB

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