detail.tsx 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668
  1. import MHeader from '@/components/m-header';
  2. import {
  3. Cell,
  4. CellGroup,
  5. Col,
  6. Icon,
  7. Row,
  8. Tag,
  9. Image,
  10. showToast,
  11. showFailToast,
  12. showLoadingToast,
  13. showSuccessToast,
  14. Popup,
  15. Grid,
  16. GridItem
  17. } from 'vant';
  18. import { defineComponent, nextTick, onMounted, reactive, ref } from 'vue';
  19. import styles from './detail.module.less';
  20. import { browser, toChinesNum } from '@/helpers/utils';
  21. import SOLO from './images/SOLO.png';
  22. import REPRISE from './images/REPRISE.png';
  23. import ENSEMBLE from './images/ENSEMBLE.png';
  24. import UNISON from './images/UNISON.png';
  25. import MImagePreview from '@/components/m-image-preview';
  26. import SkeletionIndexModal from './skeletion-index-modal';
  27. import SkeletionDetailModal from './skeletion-detail-modal';
  28. import request from '@/helpers/request';
  29. import { useRoute, useRouter } from 'vue-router';
  30. import { activeStatus, activityStatus } from '@/helpers/constant';
  31. import dayjs from 'dayjs';
  32. import { formatterTimer } from './operation';
  33. import MPopup from '@/components/m-popup';
  34. import CastModal from './components/cast-modal';
  35. import iconEdit from './images/icon-edit.png';
  36. import html2canvas from 'html2canvas';
  37. import { postMessage, promisefiyPostMessage } from '@/helpers/native-message';
  38. import backIcon from './images/back-icon.png';
  39. import eidtIcon from './images/eidt-icon.png';
  40. import shareIcon from './images/share-icon.png';
  41. import jiemuIcon from './images/jiemu-icon.png';
  42. import hornIcon from './images/horn-icon.png';
  43. import noteIcon from './images/note-icon.png';
  44. import colorBg from './images/colorBg.png';
  45. import dieIcon from './images/die-icon.png';
  46. import dBall from './images/d-ball.png';
  47. import qBall from './images/q-ball.png';
  48. import cBall from './images/c-ball.png';
  49. import hBall from './images/h-ball.png';
  50. import dotIcon from './images/dot-icon.png';
  51. import Detailswiper from './detail-swiper';
  52. import bottomBg from './images/bottomBg.png';
  53. import logo from './images/logo.png';
  54. import MQrcode from '@/components/m-qrcode';
  55. import { state } from '@/state';
  56. import { useRect } from '@vant/use';
  57. import iconWechat from './images/iconWechat.png';
  58. import iconSaveImage from './images/iconSaveImage.png';
  59. import shareBg from './images/shareBg.png';
  60. import schoolIcon from './images/schoolIcon.png';
  61. import popupQrcodeBg from './images/popupQrcodeBg.png';
  62. import shareBottom from './images/shareBottom.png';
  63. import { vaildTeachingUrl } from '@/helpers/utils';
  64. export default defineComponent({
  65. name: 'detail-page',
  66. setup() {
  67. const route = useRoute();
  68. const router = useRouter();
  69. // console.log(toChinesNum(11));
  70. const forms = reactive({
  71. id: route.query.id,
  72. imageShow: false,
  73. imagePreview: [] as any,
  74. startPosition: 0,
  75. headerLoading: true,
  76. detailLoading: true,
  77. detail: [] as any[],
  78. headerDetail: {} as any,
  79. castStatus: false,
  80. studentAllList: [] as any,
  81. navBarHeight: state.navBarHeight
  82. });
  83. const shareForms = reactive({
  84. id: route.query.id,
  85. share: route.query.share as any,
  86. showQrcode: false,
  87. url:
  88. vaildTeachingUrl() +
  89. '/school/#/activity-record-detail' +
  90. `?id=${route.query.id}`,
  91. width: 0,
  92. height: 0
  93. });
  94. const getDetail = async () => {
  95. try {
  96. const { data } = await request.get(
  97. '/api-web/schoolActivity/detail/' + forms.id
  98. );
  99. const { detail, ...res } = data || {};
  100. detail.forEach((item: any) => {
  101. item.attachmentUrl = item.attachmentUrl
  102. ? item.attachmentUrl.split(',')
  103. : [];
  104. });
  105. forms.detail = detail;
  106. forms.headerDetail = { ...res };
  107. } catch {
  108. //
  109. } finally {
  110. forms.headerLoading = false;
  111. forms.detailLoading = false;
  112. }
  113. };
  114. const formatterStudentList = (list: any[]) => {
  115. const tempList: any[] = [];
  116. list.map((student: any) => {
  117. let count = 0;
  118. const students: any[] = [];
  119. student.studentList.forEach((item: any) => {
  120. if (item.selected) {
  121. count++;
  122. students.push(item);
  123. }
  124. });
  125. if (count > 0) {
  126. tempList.push({
  127. studentCount: count,
  128. subjectId: student.subjectId,
  129. subjectName: student.subjectName,
  130. studentList: students
  131. });
  132. }
  133. });
  134. forms.studentAllList = tempList;
  135. forms.castStatus = true;
  136. };
  137. const formatterImage = (name: string) => {
  138. let image: string = '';
  139. switch (name) {
  140. case 'SOLO':
  141. image = dBall;
  142. break;
  143. case 'REPRISE':
  144. image = cBall;
  145. break;
  146. case 'ENSEMBLE':
  147. image = hBall;
  148. break;
  149. case 'UNISON':
  150. image = qBall;
  151. break;
  152. default:
  153. image = SOLO;
  154. break;
  155. }
  156. return image;
  157. };
  158. onMounted(() => {
  159. // setTimeout(() => {
  160. // // forms.headerLoading = false;
  161. // // forms.detailLoading = false;
  162. // }, 1000);
  163. getDetail();
  164. });
  165. const imgs = reactive({
  166. saveLoading: false,
  167. image: null as any,
  168. shareLoading: false
  169. });
  170. const onSaveImg = async () => {
  171. // 判断是否在保存中...
  172. if (imgs.saveLoading) {
  173. return;
  174. }
  175. imgs.saveLoading = true;
  176. // 判断是否已经生成图片
  177. if (imgs.image) {
  178. saveImg();
  179. } else {
  180. const container: any = document.getElementById(`preview-container`);
  181. html2canvas(container, {
  182. allowTaint: true,
  183. useCORS: true,
  184. backgroundColor: null
  185. })
  186. .then(async canvas => {
  187. const url = canvas.toDataURL('image/png');
  188. imgs.image = url;
  189. saveImg();
  190. })
  191. .catch(() => {
  192. // closeToast();
  193. imgs.saveLoading = false;
  194. });
  195. }
  196. };
  197. const showImageToas = (obj: any) => {
  198. forms.imagePreview = obj.imagePreview;
  199. forms.imageShow = obj.imageShow;
  200. forms.startPosition = obj.index;
  201. console.log(forms.imagePreview, 'showImageToas', obj);
  202. };
  203. const onShare = () => {
  204. console.log('onShare');
  205. if (imgs.shareLoading) {
  206. return;
  207. }
  208. imgs.shareLoading = true;
  209. if (imgs.image) {
  210. openShare();
  211. console.log('onShareimgs.image');
  212. } else {
  213. const container: any = document.getElementById(`preview-container`);
  214. html2canvas(container, {
  215. allowTaint: true,
  216. foreignObjectRendering: true,
  217. useCORS: true,
  218. backgroundColor: null
  219. })
  220. .then(async canvas => {
  221. const url = canvas.toDataURL('image/png');
  222. imgs.image = url;
  223. openShare();
  224. console.log('onShareimgs.Noimage');
  225. })
  226. .catch(e => {
  227. // closeToast();
  228. console.log(e);
  229. imgs.shareLoading = false;
  230. });
  231. }
  232. };
  233. const openShare = () => {
  234. const image = imgs.image;
  235. setTimeout(() => {
  236. imgs.shareLoading = false;
  237. }, 100);
  238. if (image) {
  239. console.log('postMessage', 'shareTripartite');
  240. postMessage(
  241. {
  242. api: 'shareTripartite',
  243. content: {
  244. title: '',
  245. desc: '',
  246. image,
  247. video: '',
  248. type: 'image',
  249. // button: ['copy']
  250. shareType: 'wechat'
  251. }
  252. },
  253. (res: any) => {
  254. if (res && res.content) {
  255. showToast(
  256. res.content.message ||
  257. (res.content.status ? '分享成功' : '分享失败')
  258. );
  259. }
  260. }
  261. );
  262. }
  263. };
  264. const saveImg = async () => {
  265. showLoadingToast({ message: '图片生成中...', forbidClick: true });
  266. setTimeout(() => {
  267. imgs.saveLoading = false;
  268. }, 100);
  269. const res = await promisefiyPostMessage({
  270. api: 'savePicture',
  271. content: {
  272. base64: imgs.image
  273. }
  274. });
  275. if (res?.content?.status === 'success') {
  276. showSuccessToast('保存成功');
  277. } else {
  278. showFailToast('保存失败');
  279. }
  280. };
  281. return () => (
  282. <div class={styles.detail}>
  283. {/* <MHeader>
  284. {{
  285. right: () => (
  286. <>
  287. <Icon
  288. name={iconEdit}
  289. class={styles.iconEdit}
  290. onClick={() => {
  291. router.push({
  292. path: '/activity-record-operation',
  293. query: {
  294. id: forms.id
  295. }
  296. });
  297. }}
  298. />
  299. <Icon
  300. name={iconEdit}
  301. class={styles.iconEdit}
  302. onClick={() => onShare()}
  303. />
  304. </>
  305. )
  306. }}
  307. </MHeader> */}
  308. <div
  309. class={styles.detailTop}
  310. style={{ 'padding-top': state.navBarHeight + 'px' }}>
  311. {browser().isApp ? (
  312. <>
  313. {' '}
  314. <div
  315. class={styles.fixWrap}
  316. style={{ top: state.navBarHeight + 30 + 'px' }}>
  317. <div class={styles.fixWrapLeft}>
  318. <Image
  319. src={backIcon}
  320. onClick={() => {
  321. if (browser().isApp) {
  322. postMessage({
  323. api: 'goBack'
  324. });
  325. } else {
  326. router.back();
  327. }
  328. }}></Image>
  329. </div>
  330. <div class={styles.fixWrapRight}>
  331. <Image
  332. src={eidtIcon}
  333. class={styles.editIcon}
  334. onClick={() => {
  335. router.push({
  336. path: '/activity-record-operation',
  337. query: {
  338. id: forms.id
  339. }
  340. });
  341. }}></Image>
  342. <Image
  343. src={shareIcon}
  344. onClick={() => {
  345. shareForms.showQrcode = true;
  346. nextTick(() => {
  347. const previewContainer = document.querySelector(
  348. '#preview-container'
  349. ) as HTMLElement;
  350. const share = useRect(previewContainer);
  351. shareForms.width = share.width;
  352. shareForms.height = share.height;
  353. if (share.width > 0 && share.height > 0) {
  354. previewContainer.style.width =
  355. Math.round(share.width) + 'px';
  356. previewContainer.style.height =
  357. Math.round(share.height) + 'px';
  358. }
  359. });
  360. }}></Image>
  361. </div>
  362. </div>
  363. </>
  364. ) : null}
  365. <div class={styles.wall}> </div>
  366. <Image src={jiemuIcon} class={styles.jiemuIcon}></Image>
  367. <div class={styles.typeCard}>
  368. {' '}
  369. {activityStatus[forms.headerDetail.type]}
  370. </div>
  371. <SkeletionIndexModal
  372. v-model:show={forms.headerLoading}
  373. showCount={[1]}
  374. isLink={false}>
  375. {/* <CellGroup inset class={styles.cellGroup}>
  376. <Cell center class={styles.cellTitle}>
  377. {{
  378. icon: () => (
  379. <Tag plain type="primary" class={styles.tag}>
  380. {activityStatus[forms.headerDetail.type]}
  381. </Tag>
  382. ),
  383. title: () => (
  384. <div class={[styles.title, 'van-ellipsis']}>
  385. {forms.headerDetail.name}
  386. </div>
  387. )
  388. }}
  389. </Cell>
  390. <Cell
  391. class={styles.cellTimer}
  392. center
  393. title={`活动日期:${dayjs(forms.headerDetail.startTime).format(
  394. 'YYYY年MM月DD日'
  395. )}`}
  396. value={activeStatus[forms.headerDetail.status]}
  397. valueClass={
  398. forms.headerDetail.status === 'PROCESSING' ? styles.ing : ''
  399. }></Cell>
  400. </CellGroup> */}
  401. <div class={styles.activeInfo}>
  402. <Image src={hornIcon} class={styles.hornIcon}></Image>
  403. <h4 class={styles.headerName}> {forms.headerDetail.name}</h4>
  404. <p class={styles.headerTimes}>{`活动日期:${dayjs(
  405. forms.headerDetail.startTime
  406. ).format('YYYY年MM月DD日')}`}</p>
  407. <Image src={noteIcon} class={styles.noteIcon}></Image>
  408. </div>
  409. </SkeletionIndexModal>
  410. </div>
  411. <SkeletionDetailModal
  412. v-model:show={forms.detailLoading}
  413. showCount={[1, 2]}>
  414. <div class={styles.programWrap}>
  415. <div class={styles.programList}>
  416. {forms.detail.map((item: any, index: number) => (
  417. <div class={styles.programItem}>
  418. <Image src={colorBg} class={styles.colorBg}></Image>
  419. <div class={styles.pTitle}>节目{toChinesNum(index + 1)}</div>
  420. <div class={styles.programInfo}>
  421. <Image
  422. class={styles.ballIcon}
  423. src={formatterImage(item.type)}></Image>
  424. <div class={styles.programInfoTitleWrap}>
  425. <Image class={styles.dieIcon} src={dieIcon}></Image>
  426. <div class={styles.programInfoTitle}> {item.name}</div>
  427. <Image class={styles.dotIcon} src={dotIcon}></Image>
  428. </div>
  429. <Row class={styles.itemRow}>
  430. <Col span={6} class={styles.label}>
  431. 表演乐团:
  432. </Col>
  433. <Col span={18} class={styles.content}>
  434. {item.musicGroupName}
  435. <span
  436. onClick={() => {
  437. if (item.studentNum <= 0) return;
  438. formatterStudentList(item.studentList);
  439. }}>
  440. 共{item.studentNum}名 <Icon name="arrow" />
  441. </span>
  442. </Col>
  443. </Row>
  444. <Row class={styles.itemRow} style={{ marginBottom: '0' }}>
  445. <Col span={6} class={styles.label}>
  446. 表演团队:
  447. </Col>
  448. <Col span={18} class={styles.content}>
  449. {item.subjectNameList}
  450. </Col>
  451. </Row>
  452. <Row class={styles.itemRow} style={{ marginBottom: '0' }}>
  453. <Col span={6} class={styles.label}>
  454. 节目时长:
  455. </Col>
  456. <Col span={18} class={styles.content}>
  457. {formatterTimer(item.time).minute}分
  458. {formatterTimer(item.time).secord}秒
  459. </Col>
  460. </Row>
  461. </div>
  462. <Detailswiper
  463. item={item}
  464. onShowImageToas={showImageToas}></Detailswiper>
  465. {/* <CellGroup inset class={styles.pCellGroup}>
  466. <Cell center>
  467. {{
  468. icon: () => (
  469. <img
  470. src={formatterImage(item.type)}
  471. class={styles.imgType}
  472. />
  473. ),
  474. title: () => (
  475. <div class={[styles.title, 'van-ellipsis']}>
  476. {item.name}
  477. </div>
  478. ),
  479. value: () => {
  480. const timer = formatterTimer(item.time);
  481. return (
  482. <span class={styles.time}>
  483. {timer.minute}分{timer.secord}秒
  484. </span>
  485. );
  486. }
  487. }}
  488. </Cell>
  489. <Cell
  490. center
  491. class={styles.moreCell}
  492. valueClass={styles.valueClass}>
  493. <Row class={styles.item}>
  494. <Col span={6} class={styles.label}>
  495. 表演乐团
  496. </Col>
  497. <Col span={18} class={styles.content}>
  498. {item.musicGroupName}
  499. <span
  500. onClick={() => {
  501. if (item.studentNum <= 0) return;
  502. formatterStudentList(item.studentList);
  503. }}>
  504. 共{item.studentNum}名 <Icon name="arrow" />
  505. </span>
  506. </Col>
  507. </Row>
  508. <Row class={styles.item} style={{ marginBottom: '0' }}>
  509. <Col span={6} class={styles.label}>
  510. 表演团队
  511. </Col>
  512. <Col span={18} class={styles.content}>
  513. {item.subjectNameList}
  514. </Col>
  515. </Row>
  516. {item.attachmentUrl ? (
  517. <div class={styles.photoList}>
  518. {item.attachmentUrl.map(
  519. (i: any, index: number) =>
  520. index <= 3 && (
  521. <div
  522. class={styles.photo}
  523. onClick={() => {
  524. forms.imagePreview = item.attachmentUrl;
  525. forms.imageShow = true;
  526. forms.startPosition = index;
  527. }}>
  528. {checkFile(i, 'image') ? (
  529. <Image
  530. src={i + '@base@tag=imgScale&w=120'}
  531. fit="cover"
  532. />
  533. ) : (
  534. <video
  535. style={{ backgroundColor: '#F8F8F8' }}
  536. poster={iconVideoDefault}
  537. src={i + '#t=1,4'}
  538. />
  539. )}
  540. {item.attachmentUrl.length > 4 &&
  541. index === 3 ? (
  542. <div class={styles.photoMore}>
  543. +{item.attachmentUrl.length - 4}
  544. </div>
  545. ) : (
  546. ''
  547. )}
  548. </div>
  549. )
  550. )}
  551. </div>
  552. ) : (
  553. ''
  554. )}
  555. </Cell>
  556. </CellGroup> */}
  557. </div>
  558. ))}
  559. </div>
  560. <div class={styles.bottomWrap}>
  561. {browser().isApp ? null : (
  562. <Image class={styles.logo} src={logo}></Image>
  563. )}
  564. <Image class={styles.bottomBg} src={bottomBg}></Image>
  565. </div>
  566. </div>
  567. </SkeletionDetailModal>
  568. <MImagePreview
  569. v-model:show={forms.imageShow}
  570. images={forms.imagePreview}
  571. startPosition={forms.startPosition}
  572. />
  573. {/* 演员名单 */}
  574. <MPopup v-model:modelValue={forms.castStatus}>
  575. <CastModal
  576. type="look"
  577. subjectAllList={forms.studentAllList}
  578. onClose={() => (forms.castStatus = false)}
  579. />
  580. </MPopup>
  581. <Popup
  582. v-model:show={shareForms.showQrcode}
  583. position="bottom"
  584. style={{ background: 'transparent' }}>
  585. <div class={styles.codeContainer}>
  586. <div
  587. class={[styles.codeImg, styles.teacherCodeImg]}
  588. id="preview-container">
  589. <Image src={shareBg} class={styles.popupWeekBanner} />
  590. <div class={styles.timerBg}>
  591. {dayjs(forms.headerDetail.startTime).format('YYYY年MM月DD日')}
  592. </div>
  593. <div class={styles.codeContent}>
  594. <Image
  595. class={styles.schoolLogo}
  596. src={
  597. forms.headerDetail?.cooperationOrganLogo
  598. ? forms.headerDetail?.cooperationOrganLogo
  599. : schoolIcon
  600. }></Image>
  601. <div class={styles.schoolName}>
  602. {forms.headerDetail?.cooperationOrganName || '武汉小学'}
  603. </div>
  604. <div class={styles.shareName}>{forms.headerDetail.name}</div>
  605. <div class={styles.codeQr}>
  606. <Image src={popupQrcodeBg} class={styles.popupQrcodeBg} />
  607. <MQrcode
  608. text={shareForms.url}
  609. size={'100%'}
  610. logoSize="small"
  611. />
  612. </div>
  613. <Image src={shareBottom} class={styles.shareBottom}></Image>
  614. </div>
  615. </div>
  616. <div class={styles.codeBottom}>
  617. <Icon
  618. name="cross"
  619. size={22}
  620. class={styles.close}
  621. color="#666"
  622. onClick={() => (shareForms.showQrcode = false)}
  623. />
  624. <h3 class={styles.title}>
  625. <i></i>分享方式
  626. </h3>
  627. <Grid columnNum={2} border={false}>
  628. <GridItem onClick={onSaveImg}>
  629. {{
  630. icon: () => (
  631. <Image class={styles.shareImg} src={iconSaveImage} />
  632. ),
  633. text: () => <div class={styles.shareText}>保存图片</div>
  634. }}
  635. </GridItem>
  636. <GridItem onClick={onShare}>
  637. {{
  638. icon: () => (
  639. <Image class={styles.shareImg} src={iconWechat} />
  640. ),
  641. text: () => <div class={styles.shareText}>微信</div>
  642. }}
  643. </GridItem>
  644. </Grid>
  645. </div>
  646. </div>
  647. </Popup>
  648. </div>
  649. );
  650. }
  651. });