share.vue 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664
  1. <template>
  2. <div class="share" :style="{ 'z-index': zIndex }">
  3. <!-- canvasImageUrl canvase 渲染成功后删除原始元素,避免当前页面元素太多出现卡顿 -->
  4. <div class="share-content" ref="shareContent">
  5. <!-- <div class="share-content" ref="shareContent" v-if="!canvasImageUrl"> -->
  6. <div class="header">
  7. <div class="left-icon">
  8. <img :src="require('@/assets/shareLeft.png')" />
  9. </div>
  10. <div class="right-icon">
  11. <img :src="require('@/assets/shareRight.png')" />
  12. </div>
  13. </div>
  14. <!-- 点评 -->
  15. <!-- <template v-for="item in reportTarget.reportRemarks"> -->
  16. <div
  17. class="comment summaryDay"
  18. v-if="
  19. reportRemarksIndex != -1 &&
  20. reportTarget.reportRemarks &&
  21. reportTarget.reportRemarks.length
  22. ">
  23. <div class="title-box">
  24. <div class="title">
  25. <div class="name">
  26. {{ reportTarget.reportRemarks[reportRemarksIndex].nickName }}点评
  27. </div>
  28. <div class="date">
  29. {{
  30. reportTarget.reportRemarks[reportRemarksIndex].createTime
  31. ? formatChineseDate(
  32. reportTarget.reportRemarks[reportRemarksIndex].createTime.split(' ')[0],
  33. )
  34. : ''
  35. }}
  36. </div>
  37. </div>
  38. </div>
  39. <div class="commentMessage">
  40. {{ reportTarget.reportRemarks[reportRemarksIndex].remarkContent }}
  41. </div>
  42. </div>
  43. <!-- </template> -->
  44. <!-- 内容 -->
  45. <div class="summaryDay">
  46. <div class="title-box">
  47. <div class="title">
  48. <div class="name">{{ reportTarget.nickName }}的日报</div>
  49. <div class="date">
  50. {{
  51. reportTarget.commitTime
  52. ? formatChineseDate(reportTarget.commitTime.split(' ')[0])
  53. : ''
  54. }}
  55. </div>
  56. </div>
  57. </div>
  58. <!-- 旧的日报内容 -->
  59. <!-- <template v-if="checkedPlan && reportContents">
  60. <template v-for="value in reportContents">
  61. <div :class="['text']">{{ value.dictValue }}</div>
  62. <div class="content">{{ value.content }}</div>
  63. </template>
  64. </template> -->
  65. <!-- 日报指标 -->
  66. <template v-if="isTarget">
  67. <shareAtarget :reportInfo="reportTarget"></shareAtarget>
  68. </template>
  69. <!-- 日报填写内容 -->
  70. <!-- 今日拜访照片 -->
  71. <template v-if="photosData && photosData.length">
  72. <div class="text">今日拜访照片</div>
  73. <div class="content-photos">
  74. <div class="imgBox" v-for="item in photosData">
  75. <img
  76. :src="'data:image/jpg;base64,' + item"
  77. crossorigin="anonymous"
  78. referrerpolicy="no-referrer"
  79. alt=""
  80. style="display: block" />
  81. </div>
  82. <!-- <template v-for="item in urlList">
  83. <img
  84. :src="item + '&' + new Date().getTime()"
  85. crossOrigin="anonymous"
  86. referrerpolicy="no-referrer"
  87. alt=""
  88. style="display: block" />
  89. </template> -->
  90. </div>
  91. </template>
  92. </div>
  93. <div class="footerShare">
  94. <div class="right-text">
  95. <div>长按识别二维码查看详情&点评</div>
  96. <div></div>
  97. </div>
  98. <vue-qr
  99. class="QRcodes"
  100. :callback="QRCallback"
  101. :text="vueQrText"
  102. :dotScale="0.8"
  103. :margin="8"
  104. :logoSrc="require('@/assets/logo1.png')"
  105. :logoMargin="2"></vue-qr>
  106. </div>
  107. </div>
  108. <div
  109. class="share-mask"
  110. style="background: #000; position: fixed; top: 0; width: 100%; height: 100%"></div>
  111. <div class="html2canvasBox">
  112. <div id="html2canvas" ref="html2canvas">
  113. <div class="scroll-container">
  114. <div style="position: relative">
  115. <div class="shareHeader">
  116. <div class="shareTips">
  117. 长按图片可下载、收藏、转发
  118. <!-- <img :src="require('@/assets/shareTips.png')" alt="" /> -->
  119. </div>
  120. <!-- <div class="closeShare" @click="closeShare"><van-icon name="close" /></div> -->
  121. <div class="closeShare" @click="closeShare">返回</div>
  122. </div>
  123. <img :src="canvasImageUrl" width="100%" class="canvasImage" />
  124. </div>
  125. </div>
  126. </div>
  127. </div>
  128. </div>
  129. </template>
  130. <script>
  131. import html2canvas from 'html2canvas';
  132. import { imgToBase64 } from '@/api/index';
  133. import VueQr from 'vue-qr';
  134. import ShareAtarget from './shareAtarget.vue';
  135. export default {
  136. name: 'share',
  137. components: { VueQr, ShareAtarget },
  138. props: {
  139. // 日报数据
  140. reportTarget: {
  141. type: Object,
  142. default() {
  143. return {};
  144. },
  145. },
  146. //日报id
  147. reportId: {
  148. type: [String, Number],
  149. },
  150. // 点评数据
  151. reportRemarksIndex: {
  152. type: Number,
  153. default: -1,
  154. },
  155. // 图片转换列表
  156. urlList: {
  157. type: Array,
  158. default() {
  159. return [];
  160. },
  161. },
  162. // 是否显示 今日总结&明日规划
  163. checkedPlan: {
  164. type: Boolean,
  165. default: true,
  166. },
  167. reportContents: {
  168. type: Array,
  169. default() {
  170. return [];
  171. },
  172. },
  173. // 是否显示 指标
  174. isTarget: {
  175. type: Boolean,
  176. default: true,
  177. },
  178. },
  179. data() {
  180. return {
  181. canvasImageUrl: '',
  182. retryCount: 0,
  183. photosData: [],
  184. zIndex: -1,
  185. qrcode: null,
  186. vueQrText: '',
  187. heightFlag: 'start', //图片高度不足,居中显示
  188. };
  189. },
  190. created() {
  191. this.zIndex = -1;
  192. this.canvasImageUrl = '';
  193. let links = process.env.NODE_ENV == 'production' ? 'http://1.npz.cn/3/' : 'http://1.npz.cn/2/';
  194. this.vueQrText = links + this.reportId;
  195. this.toastLoading(0, '生成中...', true);
  196. },
  197. methods: {
  198. // 初始化会先执行二维码生成,成功后回调
  199. QRCallback() {
  200. // 二维码
  201. if (this.urlList.length) {
  202. imgToBase64({ urlList: this.urlList }).then((res) => {
  203. if (res.data && res.code == 200) {
  204. this.photosData = res.data;
  205. this.$nextTick(async () => {
  206. await this.htmlToCanvas();
  207. });
  208. } else {
  209. this.$toast(res.msg);
  210. this.$nextTick(async () => {
  211. await this.htmlToCanvas();
  212. });
  213. }
  214. });
  215. } else {
  216. this.$nextTick(async () => {
  217. await this.htmlToCanvas();
  218. });
  219. }
  220. },
  221. //异步执行
  222. imageUrlToBase64(imageUrl) {
  223. return new Promise((resolve, reject) => {
  224. //一定要设置为let,不然图片不显示
  225. let image = new Image();
  226. //解决跨域问题
  227. image.setAttribute('crossOrigin', 'anonymous');
  228. image.src = imageUrl;
  229. image.onload = () => {
  230. var canvas = document.createElement('canvas');
  231. canvas.width = image.width;
  232. canvas.height = image.height;
  233. var context = canvas.getContext('2d');
  234. context.drawImage(image, 0, 0, image.width, image.height);
  235. var quality = 1;
  236. //这里的dataurl就是base64类型
  237. let dataURL = canvas.toDataURL('image/png', quality); //使用toDataUrl将图片转换成jpeg的格式,不要把图片压缩成png,因为压缩成png后base64的字符串可能比不转换前的长!
  238. resolve(dataURL);
  239. };
  240. image.onerror = () => {
  241. reject(new Error('图像加载失败'));
  242. };
  243. });
  244. },
  245. filterText(index) {
  246. if (index == 0) {
  247. return '明日工作计划';
  248. } else {
  249. return '今日机会与挑战总结';
  250. }
  251. },
  252. htmlToCanvas() {
  253. html2canvas(this.$refs.shareContent, {
  254. scale: window.devicePixelRatio || 1,
  255. useCORS: true,
  256. allowTaint: false,
  257. backgroundColor: null,
  258. logging: false, // 关闭日志提升性能
  259. // onclone: (clonedDoc) => {
  260. // // 确保克隆的DOM保持原始样式
  261. // clonedDoc.getElementById('html2canvas').style.overflow = 'auto';
  262. // },
  263. // ignoreElements: (e) => {
  264. // if (
  265. // e.contains(element) ||
  266. // element.contains(e) ||
  267. // e.tagName === 'STYLE' ||
  268. // e.tagName === 'LINK' ||
  269. // e.getAttribute('data-html2canvas') != null // header里面的样式不能筛掉
  270. // ) {
  271. // console.log(e);
  272. // return false;
  273. // }
  274. // return true;
  275. // },
  276. })
  277. .then((canvas) => {
  278. this.toastLoading().clear();
  279. let imageUrl = canvas.toDataURL('image/png');
  280. this.canvasImageUrl = imageUrl;
  281. // 图片加载完成后设置滚动容器高度
  282. const img = new Image();
  283. img.onload = () => {
  284. const canvasImage = this.$refs.html2canvas.querySelector('.canvasImage');
  285. const scrollContainer = this.$refs.html2canvas.querySelector('.scroll-container');
  286. const shareHeader = this.$refs.html2canvas.querySelector('.shareHeader');
  287. console.log(scrollContainer.style);
  288. if (canvasImage.height < window.innerHeight) {
  289. scrollContainer.style.alignItems = 'center';
  290. shareHeader.style.position = 'absolute';
  291. } else {
  292. scrollContainer.style.alignItems = 'start';
  293. shareHeader.style.position = 'fixed';
  294. }
  295. this.$forceUpdate();
  296. // // 根据图片实际高度设置容器高度(增加20px缓冲)
  297. // // 根据设备像素比调整图片高度
  298. // // 使用实际渲染高度而非原始图片高度
  299. // const imgHeight = img.offsetHeight + 20;
  300. // // 设置容器最小高度保证内容显示,同时允许自动扩展
  301. // scrollContainer.style.minHeight = `${imgHeight}px`;
  302. // // 保持父容器为可见滚动
  303. // scrollContainer.parentElement.style.overflow = 'visible';
  304. // // 强制浏览器重排
  305. // scrollContainer.style.display = 'none';
  306. // scrollContainer.offsetHeight; // 触发重排
  307. // scrollContainer.style.display = 'block';
  308. // // 添加移动端滚动优化
  309. // requestAnimationFrame(() => {
  310. // scrollContainer.style.overflow = 'auto';
  311. // scrollContainer.style.overflowScrolling = 'touch';
  312. // scrollContainer.style.webkitOverflowScrolling = 'touch';
  313. // scrollContainer.style.overscrollBehavior = 'contain';
  314. // scrollContainer.style.touchAction = 'pan-y';
  315. // });
  316. };
  317. img.src = imageUrl;
  318. this.zIndex = 9;
  319. this.$emit('setShareImg', true);
  320. this.$emit('setDailyDetailsBox', false);
  321. })
  322. .catch((error) => {
  323. this.toastLoading().clear();
  324. console.error('html2canvas error:', error);
  325. this.$toast('生成分享图失败,请稍后重试');
  326. });
  327. },
  328. closeShare() {
  329. this.canvasImageUrl = '';
  330. this.$emit('setShareImg', false);
  331. this.$emit('setDailyDetailsBox', true);
  332. },
  333. },
  334. beforeDestroy() {
  335. // 组件销毁时清除实例
  336. if (this.qrcode) {
  337. this.$refs.qrcode.innerHTML = '';
  338. }
  339. },
  340. };
  341. </script>
  342. <style lang="scss" scoped>
  343. .share {
  344. width: 100%;
  345. height: 100%;
  346. overflow: hidden;
  347. background: #fff;
  348. position: fixed;
  349. top: 0;
  350. .share-content {
  351. background: url('../assets/shareBack.webp') no-repeat center;
  352. background-size: 100% 100%;
  353. // background-attachment: local;
  354. width: 100%;
  355. // min-height: 100%;
  356. padding: vw(30);
  357. position: absolute;
  358. // padding-bottom: vw(30);
  359. top: 0;
  360. z-index: -10;
  361. }
  362. .header {
  363. display: flex;
  364. justify-content: space-between;
  365. padding-bottom: vw(40);
  366. .left-icon {
  367. width: vw(114);
  368. height: vw(71);
  369. }
  370. .right-icon {
  371. width: vw(230);
  372. height: vw(78);
  373. }
  374. img {
  375. width: 100%;
  376. height: 100%;
  377. }
  378. }
  379. .title-box {
  380. position: relative;
  381. width: 100%;
  382. height: auto;
  383. display: flex;
  384. justify-content: center;
  385. align-items: center;
  386. top: -6px;
  387. .title {
  388. // width: vw(262);
  389. // height: vw(115);
  390. display: flex;
  391. flex-direction: column;
  392. justify-content: center;
  393. align-items: center;
  394. color: #600d0e;
  395. font-weight: bold;
  396. text-align: center;
  397. // background: url('../assets/titleBack.png') no-repeat;
  398. // background-size: 100% 100%;
  399. padding: vw(20) vw(30);
  400. background-color: #ecdd9a;
  401. border-radius: 6px 6px 25px 25px;
  402. .name {
  403. font-size: vw(36);
  404. }
  405. .date {
  406. font-size: vw(28);
  407. }
  408. }
  409. }
  410. .summaryDay {
  411. background: url('../assets/summaryDay.png') no-repeat;
  412. background-size: 100%;
  413. padding: vw(24);
  414. position: relative;
  415. margin-top: vw(55);
  416. border-radius: 0 0 vw(35) vw(35);
  417. padding-top: 0;
  418. .text {
  419. background: url('../assets/textBack.png') no-repeat;
  420. width: 100%;
  421. height: vw(94);
  422. background-size: cover;
  423. color: #7d0207;
  424. font-size: vw(36);
  425. text-align: center;
  426. line-height: vw(94);
  427. // margin-top: vw(152);
  428. // margin-bottom: vw(45);
  429. margin: vw(35) 0;
  430. font-weight: bold;
  431. }
  432. .content {
  433. font-size: vw(28);
  434. font-weight: bold;
  435. color: #ffff;
  436. line-height: vw(63);
  437. white-space: pre-wrap;
  438. }
  439. .content-photos {
  440. display: flex;
  441. flex-wrap: wrap;
  442. // justify-content: space-between;
  443. .imgBox {
  444. width: 47.5%;
  445. margin-bottom: 15px;
  446. margin-right: 5%;
  447. background-color: rgb(0, 0, 0);
  448. height: 200px;
  449. display: flex;
  450. align-items: center;
  451. border-radius: 10px;
  452. overflow: hidden;
  453. &:nth-child(2n) {
  454. margin-right: 0;
  455. }
  456. }
  457. img {
  458. // width: 100%;
  459. // height: 42vw;
  460. // width: 30%;
  461. // height: vw(188);
  462. // margin-bottom: 15px;
  463. // margin-right: 5%;
  464. // background-color: rgba(255, 255, 255, 0.1);
  465. // &:nth-child(2n) {
  466. // margin-right: 0;
  467. // }
  468. width: 100%;
  469. // height: 100%;
  470. background-color: rgba(255, 255, 255, 0.1);
  471. max-height: 200px;
  472. // object-fit: contain;
  473. }
  474. }
  475. }
  476. .comment {
  477. .title {
  478. // background: url('../assets/comment.png') no-repeat;
  479. // background-size: 100% 100%;
  480. background-color: #032371;
  481. border-radius: 6px 6px 25px 25px;
  482. color: #e6cd78;
  483. }
  484. .commentMessage {
  485. padding: vw(60) 0;
  486. font-family: PingFang-SC-Medium;
  487. font-size: vw(28);
  488. font-weight: bold;
  489. color: #ffffff;
  490. }
  491. }
  492. .footerShare {
  493. // position: absolute;
  494. display: flex;
  495. // align-items: center;
  496. justify-content: flex-end;
  497. margin-top: vw(30);
  498. margin-right: 2px;
  499. .right-text {
  500. // margin-left: vw(12.5);
  501. display: flex;
  502. flex-direction: column;
  503. justify-content: space-between;
  504. margin-right: vw(24);
  505. div {
  506. color: #ffffff;
  507. font-size: vw(26);
  508. // font-weight: bold;
  509. padding: 6px 0;
  510. }
  511. }
  512. // display: flex;
  513. // flex-direction: column;
  514. // justify-content: center;
  515. // align-items: center;
  516. // margin-top: vw(100);
  517. // .right-text {
  518. // // margin-left: vw(12.5);
  519. // display: flex;
  520. // // flex-direction: column;
  521. // // justify-content: space-between;
  522. // align-items: center;
  523. // justify-content: center;
  524. // padding: vw(10) 0;
  525. // div {
  526. // color: #ffffff;
  527. // font-size: vw(30);
  528. // font-weight: bold;
  529. // }
  530. // }
  531. }
  532. .html2canvasBox {
  533. width: 100%;
  534. height: 100%;
  535. min-height: auto;
  536. min-height: 100vh;
  537. overflow: hidden !important;
  538. display: flex;
  539. flex-direction: column;
  540. }
  541. #html2canvas {
  542. width: 100%;
  543. height: 100%;
  544. position: static; /* 改为静态定位 */
  545. .scroll-container {
  546. width: 100%;
  547. height: 100%;
  548. flex: 1;
  549. overflow-y: auto !important;
  550. -webkit-overflow-scrolling: touch;
  551. overscroll-behavior: contain;
  552. touch-action: pan-y;
  553. // padding: 20px 0;
  554. box-sizing: border-box;
  555. /* 修复iOS弹性滚动 */
  556. overflow-scrolling: touch;
  557. max-height: 100vh; /* 添加最大高度限制 */
  558. position: relative; /* 修复定位上下文 */
  559. display: flex;
  560. // align-items: start;
  561. img {
  562. width: 100%;
  563. display: block;
  564. }
  565. .shareHeader {
  566. position: absolute;
  567. z-index: 1;
  568. top: 10px;
  569. width: 100%;
  570. display: flex;
  571. align-items: center;
  572. padding: 0 vw(30);
  573. .closeShare {
  574. // position: absolute;
  575. // z-index: 1;
  576. // right: 10px;
  577. // top: 10px;
  578. color: #fff;
  579. font-size: vw(30);
  580. background: #a2819c;
  581. text-align: center;
  582. line-height: 28px;
  583. border-radius: 6px;
  584. padding: 0 7px;
  585. border: 2px solid #11224e;
  586. .van-icon {
  587. font-size: vw(40);
  588. }
  589. }
  590. .shareTips {
  591. display: flex;
  592. align-items: center;
  593. justify-content: center;
  594. color: #f8f8fa;
  595. width: 82%;
  596. // flex: 1;
  597. margin-right: 10px;
  598. background: rgba(157, 157, 188, 0.8);
  599. height: vw(60);
  600. font-weight: 600;
  601. font-size: vw(26);
  602. img {
  603. width: 100%;
  604. // height: vw(60);
  605. }
  606. }
  607. }
  608. }
  609. }
  610. }
  611. </style>
  612. <style lang="scss">
  613. .share {
  614. .QRcodes {
  615. // width: 45px; /* 固定像素尺寸 */
  616. // height: 45px;
  617. width: vw(200); /* 固定像素尺寸 */
  618. height: vw(200);
  619. position: relative;
  620. background: #ffffff;
  621. image-rendering: crisp-edges;
  622. image-rendering: -webkit-optimize-contrast; /* 安卓浏览器兼容 */
  623. // padding: 2px;
  624. display: flex;
  625. align-items: center;
  626. justify-content: center;
  627. border-radius: 3px;
  628. .logo {
  629. position: absolute;
  630. top: 50%;
  631. left: 50%;
  632. transform: translate(-50%, -50%);
  633. width: 10px;
  634. height: 10px;
  635. img {
  636. object-fit: contain;
  637. }
  638. z-index: 3;
  639. }
  640. img {
  641. width: 100%;
  642. height: 100%;
  643. }
  644. }
  645. }
  646. </style>