detail.vue 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723
  1. <template>
  2. <div class="page" id="page">
  3. <div class="top-title wow fadeInUp" data-wow-duration="2s" data-wow-delay="0s" data-wow-offset="0">
  4. <div class="top-container">
  5. <n-icon :component="IosFiling" size="40" style="vertical-align: middle" />
  6. <span>简报智库</span>
  7. </div>
  8. </div>
  9. <div class="page-nav-container wow fadeInLeft" data-wow-duration="2s" data-wow-delay="0s" data-wow-offset="0">
  10. <div class="nav-txt">
  11. <n-breadcrumb separator=">">
  12. <n-breadcrumb-item> <n-icon :component="MdHome" /><router-link to="/home">首页</router-link> </n-breadcrumb-item>
  13. <n-breadcrumb-item> <n-icon :component="IosFiling" />简报智库 </n-breadcrumb-item>
  14. </n-breadcrumb>
  15. </div>
  16. </div>
  17. <div class="bulletin-detail" :class="{ 'mobile-detail': !pcShow }">
  18. <div class="container">
  19. <h1 class="wow fadeInLeft">{{ detail?.title }}</h1>
  20. <p class="wow fadeInLeft">
  21. <span class="bqfl-iconfont icon-icon color99 mr5"></span>
  22. <span>发布时间:{{ formatDate(detail?.publishDate) }}</span>
  23. </p>
  24. <n-divider />
  25. <div class="btns wow fadeInRight">
  26. <n-button type="primary" color="#F0A226" @click="handleTools('subscribe')"> 定制报告 </n-button>
  27. <n-button type="primary" color="#2683F0" @click="handleTools('pdf')"> PDF下载 </n-button>
  28. <n-button type="primary" color="#2683F0" @click="handleTools('ppt')"> PPT下载 </n-button>
  29. </div>
  30. <div class="iframe-box" v-if="detail?.id">
  31. <ClientOnly :is-show="isShow">
  32. <PDFPreview :key="`t_${new Date().getTime()}`" :src="PdfPreviewUrl" :config="pdfConfig"></PDFPreview>
  33. </ClientOnly>
  34. </div>
  35. <div class="reader wow fadeInUp" data-wow-duration="3s" data-wow-delay="0s" data-wow-offset="0" v-if="listData && listData.length > 0">
  36. <h3>相关阅读</h3>
  37. <ul>
  38. <li v-for="item in listData" :key="item.id">
  39. <div class="img">
  40. <img :src="BaseUrl + '/fileupload/' + item.filePath + item.imageFileName" alt="" />
  41. </div>
  42. <div class="info">
  43. <a :href="`/bulletin/bulletin-detail/${item.webTitle}-${item.id}`">
  44. <h4>{{ item.title }}</h4>
  45. </a>
  46. <span class="tags" v-if="item.secondLabelName">
  47. <span class="tag">{{ item.secondLabelName.split(',')[0] }}</span>
  48. </span>
  49. <span class="time">{{ formatDate(item.publishDate) }}</span>
  50. <span class="num">{{ item.pdfNum || 0 }}页</span>
  51. </div>
  52. </li>
  53. </ul>
  54. </div>
  55. </div>
  56. </div>
  57. <n-modal :show="toolsVisible" preset="dialog" :title="toolsTitle" :showIcon="false" :z-index="9" @close="toolsVisible = false" @esc="toolsVisible = false" @mask-click="toolsVisible = false" :class="{ 'login-dialog': !pcShow }">
  58. <demand @closeDialog="handleCloseDiag" v-if="toolsType == 'subscribe'" />
  59. <pay v-if="toolsType == 'pdf' || toolsType == 'ppt'" :detail="detail" @closeToolsDialog="closeToolsDialog" @openWXCode="openWXCode"></pay>
  60. <template v-if="toolsType == 'wxQRCode'">
  61. <div class="wxQRCode">
  62. <h3>微信支付</h3>
  63. <qrcode-vue :value="wxQRCode" size="200" level="H" render-as="svg" />
  64. <p>请使用微信扫码支付</p>
  65. <p v-if="payShow" style="color: red">支付状态请求中,请勿重复支付</p>
  66. </div>
  67. </template>
  68. </n-modal>
  69. <n-modal v-model:show="payJumpVisible" preset="dialog" title="提示" :showIcon="false" content="" positive-text="确认" negative-text="取消" :close-on-esc="false" :mask-closable="false" @positive-click="submitPayEndCallback" @negative-click="cancelPayEndCallback" :class="{ 'login-dialog': !pcShow }">
  70. <h2>请确认是否支付完成?</h2>
  71. <p>支付完成后,请到个人中心—我的订单中进行下载</p>
  72. </n-modal>
  73. <n-spin class="load" :description="description" :show="spinShow" :delay="1000">
  74. <div style="width: 150px"></div>
  75. </n-spin>
  76. </div>
  77. </template>
  78. <script lang="ts" setup>
  79. import {
  80. ref,
  81. reactive,
  82. onMounted,
  83. computed,
  84. watch,
  85. onUnmounted,
  86. onServerPrefetch,
  87. inject,
  88. } from "vue";
  89. import { useRouter, useRoute } from "vue-router";
  90. import {
  91. NIcon,
  92. NBreadcrumb,
  93. NBreadcrumbItem,
  94. NButton,
  95. NInput,
  96. createDiscreteApi,
  97. NDivider,
  98. NModal,
  99. NSpin,
  100. } from "naive-ui";
  101. import { IosFiling, MdHome } from "@vicons/ionicons4";
  102. import pay from "./modules/pay.vue";
  103. import { useUserStore } from "@/store/user";
  104. import QrcodeVue from "qrcode.vue";
  105. import ClientOnly from "@duannx/vue-client-only";
  106. import PDFPreview from "@/components/PdfPreview/index.vue";
  107. import { useI18n } from "#imports";
  108. const { t } = useI18n();
  109. const setTitleKeywordsDescription = inject("setTitleKeywordsDescription");
  110. const router = useRouter(); // 传递参数
  111. const route = useRoute();
  112. const userStore = useUserStore();
  113. const toolsVisible = ref<boolean>(false);
  114. const toolsTitle = ref<string>("");
  115. const toolsType = ref<string>();
  116. const pcShow = ref<boolean>(true);
  117. const id = ref();
  118. const detail = ref({});
  119. const listData = ref([]);
  120. const spinShow = ref(false);
  121. const description = ref("数据加载中");
  122. const wxQRCode = ref("");
  123. const count = ref(0);
  124. const timer = ref();
  125. const payShow = ref(false);
  126. const websiteToken = computed(() => userStore.getToken);
  127. const userInfo = computed(() => userStore.getUserInfo);
  128. const isShow = ref(false);
  129. const PdfPreviewUrl = ref("");
  130. const payJumpVisible = ref(false);
  131. const config = useRuntimeConfig();
  132. const BaseUrl = ref(config.public.baseUrl);
  133. const pageSize = 10;
  134. const pdfConfig = {
  135. toolbar: {
  136. toolbarViewerLeft: {
  137. findbar: true,
  138. previous: true,
  139. next: true,
  140. pageNumber: true,
  141. },
  142. toolbarViewerRight: {
  143. presentationMode: true,
  144. openFile: false,
  145. print: true,
  146. download: true,
  147. viewBookmark: true,
  148. },
  149. toolbarViewerMiddle: {
  150. zoomOut: true,
  151. zoomIn: true,
  152. scaleSelectContainer: true,
  153. },
  154. },
  155. };
  156. // onMounted(async () => {
  157. // pcShow.value = !isMobile();
  158. // const webTitle = route.params.webTitle;
  159. // const wts = webTitle.split("-");
  160. // id.value = wts[wts.length - 1];
  161. // getDetail();
  162. // });
  163. onUnmounted(() => {
  164. payEnd();
  165. });
  166. watch(
  167. () => toolsVisible.value,
  168. (newVal, oldVal) => {
  169. if (newVal === false) {
  170. payEnd();
  171. }
  172. }
  173. );
  174. const getDetail = async () => {
  175. isShow.value = false;
  176. spinShow.value = true;
  177. description.value = "数据加载中";
  178. const {
  179. code,
  180. data: { list, vo },
  181. } = await bulletinDetail_Api({
  182. id: id.value,
  183. });
  184. if (code === 200) {
  185. if (vo) {
  186. detail.value = vo;
  187. listData.value = [...list];
  188. isShow.value = true;
  189. PdfPreviewUrl.value = `${BaseUrl.value}/fileupload/${vo.filePath}${
  190. vo.previewPdfFileName
  191. }?t=${new Date().getTime()}`;
  192. useHead({
  193. title: detail.value.title,
  194. viewport: "width=device-width,initial-scale=1,maximum-scale=1 ",
  195. charset: "utf-8",
  196. meta: [
  197. {
  198. hid: "keywords",
  199. name: "keywords",
  200. content: t("defaultSettings.keyword"),
  201. },
  202. {
  203. hid: "description",
  204. name: "description",
  205. content: detail.value.outline,
  206. },
  207. ],
  208. });
  209. } else {
  210. errorMsg("报告查询失败,请查看其它报告");
  211. }
  212. }
  213. spinShow.value = false;
  214. };
  215. const toDetail = (item: object) => {
  216. router.push({
  217. name: "bulletinDetail",
  218. query: {
  219. id: item.id,
  220. },
  221. });
  222. };
  223. const handleTools = (type: string) => {
  224. switch (type) {
  225. case "subscribe":
  226. toolsVisible.value = true;
  227. toolsTitle.value = "定制报告";
  228. toolsType.value = type;
  229. break;
  230. case "pdf":
  231. toCreatedPayOrder(type);
  232. break;
  233. case "ppt":
  234. toCreatedPayOrder(type);
  235. break;
  236. default:
  237. break;
  238. }
  239. };
  240. const toCreatedPayOrder = async (type: string) => {
  241. if (detail.value.useDownPay == "0" || detail.value.useShowPay == "0") {
  242. downFile();
  243. } else {
  244. if (userInfo.value?.id && websiteToken.value) {
  245. toolsVisible.value = true;
  246. toolsTitle.value = ``;
  247. toolsType.value = type;
  248. } else {
  249. userStore.setShowLoginDialog(true);
  250. // errorMsg("请先登录");
  251. }
  252. }
  253. };
  254. const downFile = async () => {
  255. const { code, data } = await getDownUrl_Api({
  256. researchBriefReportId: detail.value.id,
  257. fileType: detail.value.previewPdfFileName.split(".")[1],
  258. });
  259. if (code === 200) {
  260. const url = `${BaseUrl.value}/fileupload/${data.filePath}${data.fileName}`;
  261. downloadFile(url);
  262. }
  263. };
  264. const closeToolsDialog = (type: string) => {
  265. switch (type) {
  266. case "success":
  267. toolsVisible.value = false;
  268. break;
  269. case "subscribe":
  270. break;
  271. case "pdf":
  272. break;
  273. case "ppt":
  274. break;
  275. case "zfbPayEnd":
  276. setTimeout(() => {
  277. // toolsVisible.value = false;
  278. payJumpVisible.value = true;
  279. }, 3000);
  280. break;
  281. default:
  282. break;
  283. }
  284. };
  285. const handleCloseDiag = () => {
  286. toolsVisible.value = false;
  287. };
  288. const openWXCode = (val: string) => {
  289. toolsType.value = "wxQRCode";
  290. wxQRCode.value = val?.orderPayUrl;
  291. setTimeout(() => {
  292. // toolsVisible.value = false;
  293. payJumpVisible.value = true;
  294. // payDetail(val?.orderNumber);
  295. }, 3000);
  296. };
  297. const submitPayEndCallback = () => {
  298. toolsVisible.value = false;
  299. payJumpVisible.value = false;
  300. NMessage.message.success("已确认支付,正在前往支付订单页");
  301. setTimeout(() => {
  302. router.push({
  303. name: "mineCenter",
  304. params: { active: 1 },
  305. });
  306. }, 300);
  307. };
  308. const cancelPayEndCallback = () => {
  309. if (toolsType.value != "wxQRCode") {
  310. toolsVisible.value = false;
  311. }
  312. payJumpVisible.value = false;
  313. NMessage.message.success("已取消确认操作");
  314. };
  315. const payDetail = async (orderNumber) => {
  316. payShow.value = true;
  317. const { code, data } = await bulletinDetailPay_Api({
  318. researchBriefReportId: id.value,
  319. orderNumber: orderNumber,
  320. });
  321. if (code === 200) {
  322. if (data.payStatus == 1) {
  323. NMessage.message.success("支付成功");
  324. payEnd();
  325. } else if (data.payStatus != 1) {
  326. if (toolsVisible.value) {
  327. if (count.value >= 5) {
  328. payEnd();
  329. NMessage.message.success("请前往个人中心订单列表页查看订单详情");
  330. } else {
  331. timer.value = setTimeout(() => {
  332. count.value++;
  333. payDetail(orderNumber);
  334. }, 10 * 1000);
  335. }
  336. }
  337. }
  338. }
  339. };
  340. const payEnd = () => {
  341. count.value = 0;
  342. toolsVisible.value = false;
  343. clearTimeout(timer.value);
  344. };
  345. const getTitleKeywordsDescription = (data) => {
  346. let title, description, keywords;
  347. try {
  348. title = data.title;
  349. description = data.outline || "";
  350. keywords = "";
  351. } catch (error) {
  352. console.log("getTitleKeywordsDescription", error);
  353. }
  354. return {
  355. title,
  356. keywords,
  357. description,
  358. };
  359. };
  360. const NMessage = createDiscreteApi(["message"]);
  361. const errorMsg = (msg: any) => {
  362. NMessage.message.error(msg || "服务端异常");
  363. };
  364. // onServerPrefetch(async () => {
  365. // try {
  366. // const webTitle = route.params.webTitle;
  367. // const wts = webTitle.split("-");
  368. // description.value = "数据加载中";
  369. // spinShow.value = true;
  370. // const res = await bulletinDetail_Api({
  371. // id: wts[wts.length - 1],
  372. // });
  373. // detail.value = res.data.vo;
  374. // listData.value = [...res.data.list];
  375. // if (res.data.vo) {
  376. // setTitleKeywordsDescription(getTitleKeywordsDescription(res.data.vo));
  377. // }
  378. // spinShow.value = false;
  379. // } catch (error) {
  380. // console.log(error);
  381. // }
  382. // });
  383. if (process.client) {
  384. pcShow.value = !isMobile();
  385. }
  386. const webTitle = route.params.webTitle;
  387. const wts = webTitle.split("-");
  388. id.value = wts[wts.length - 1];
  389. getDetail();
  390. </script>
  391. <style lang="scss" scoped>
  392. .bulletin-detail {
  393. background-color: #fff;
  394. border-bottom: 1px solid #f2f2f2;
  395. .container {
  396. padding-top: 60px;
  397. }
  398. h1,
  399. h3,
  400. p {
  401. padding: 0;
  402. margin: 0;
  403. }
  404. h1 {
  405. margin-bottom: 12px;
  406. text-align: center;
  407. font-size: 32px;
  408. font-weight: bold;
  409. color: #2d4393;
  410. }
  411. p {
  412. display: flex;
  413. align-items: center;
  414. justify-content: center;
  415. font-size: 14px;
  416. color: #666666;
  417. }
  418. .btns {
  419. margin-bottom: 20px;
  420. text-align: center;
  421. .n-button {
  422. width: 140px;
  423. height: 50px;
  424. margin-right: 20px;
  425. font-size: 18px;
  426. border-radius: 8px;
  427. }
  428. }
  429. .iframe-box {
  430. width: 100%;
  431. height: 900px;
  432. margin-bottom: 40px;
  433. iframe {
  434. width: 100%;
  435. height: 100%;
  436. }
  437. #vuePdfApp {
  438. :deep(.toolbar) {
  439. z-index: 3;
  440. }
  441. :deep(#sidebarContainer) {
  442. z-index: 3;
  443. }
  444. :deep(#errorWrapper) {
  445. z-index: 3;
  446. }
  447. :deep(#thumbnailView) {
  448. width: 100%;
  449. }
  450. }
  451. }
  452. ul {
  453. padding: 0;
  454. li {
  455. margin: 0;
  456. list-style: none;
  457. padding: 0;
  458. p {
  459. padding: 0;
  460. margin: 0;
  461. }
  462. }
  463. }
  464. .reader {
  465. h3 {
  466. text-align: left;
  467. font-size: 24px;
  468. color: #1a1a1a;
  469. }
  470. ul {
  471. display: flex;
  472. li {
  473. // width: 240px;
  474. width: 15rem;
  475. margin-right: 85px;
  476. &:last-child {
  477. margin-right: 0;
  478. }
  479. }
  480. }
  481. .img {
  482. width: 100%;
  483. height: 320px;
  484. margin-bottom: 25px;
  485. overflow: hidden;
  486. img {
  487. width: 100%;
  488. height: 100%;
  489. vertical-align: middle;
  490. object-fit: contain;
  491. transition: all 0.4s ease;
  492. }
  493. }
  494. h4 {
  495. margin: 0;
  496. padding: 0;
  497. display: block;
  498. text-align: left;
  499. margin-bottom: 10px;
  500. font-size: 14px;
  501. font-weight: normal;
  502. color: #1a1a1a;
  503. line-height: 23px;
  504. cursor: pointer;
  505. overflow: hidden;
  506. text-overflow: ellipsis;
  507. display: -webkit-box;
  508. -webkit-line-clamp: 2;
  509. -webkit-box-orient: vertical;
  510. }
  511. .tags {
  512. margin-right: 10px;
  513. .tag {
  514. display: inline-block;
  515. padding: 5px 13px 3px;
  516. text-align: center;
  517. background: #cbffe3;
  518. font-size: 12px;
  519. color: #18a058;
  520. &:last-child {
  521. margin-bottom: 0;
  522. }
  523. }
  524. }
  525. .num {
  526. margin-left: 10px;
  527. // display: block;
  528. // margin-top: 10px;
  529. }
  530. }
  531. .n-dialog {
  532. padding-top: 48px;
  533. border-radius: 20px;
  534. .n-dialog__title {
  535. display: block;
  536. font-size: 24px;
  537. color: #1a1a1a;
  538. font-weight: bold;
  539. text-align: center;
  540. }
  541. }
  542. }
  543. .mobile-detail.bulletin-detail {
  544. .container {
  545. padding: 30px 0;
  546. h1,
  547. h3 {
  548. font-size: 20px;
  549. }
  550. .btns {
  551. // position: fixed;
  552. // left: 0;
  553. // bottom: 0;
  554. width: 100%;
  555. // padding: 20px 0;
  556. // margin-bottom: 0;
  557. display: flex;
  558. justify-content: space-around;
  559. background-color: #fff;
  560. z-index: 5;
  561. .n-button {
  562. width: 30%;
  563. margin-right: 0;
  564. font-size: 14px;
  565. border-radius: 4px;
  566. }
  567. }
  568. .iframe-box {
  569. margin-bottom: 20px;
  570. }
  571. .reader {
  572. h4 {
  573. font-size: 18px;
  574. }
  575. ul {
  576. display: block;
  577. li {
  578. display: flex;
  579. width: 100%;
  580. margin-right: 0;
  581. margin-bottom: 20px;
  582. padding-bottom: 15px;
  583. border-bottom: 1px solid #cccccc;
  584. }
  585. }
  586. .img {
  587. max-width: 100px;
  588. // height: 135px;
  589. max-height: 135px;
  590. margin-bottom: 0;
  591. margin-right: 10px;
  592. flex-shrink: 0;
  593. overflow: hidden;
  594. img {
  595. width: 100%;
  596. height: 100%;
  597. vertical-align: middle;
  598. object-fit: cover;
  599. }
  600. }
  601. }
  602. }
  603. }
  604. .login-dialog.n-dialog {
  605. min-width: auto;
  606. padding-top: 20px;
  607. border-radius: 10px;
  608. .n-dialog__title {
  609. font-size: 18px;
  610. }
  611. .n-dialog__content {
  612. .container {
  613. padding: 20px 10px;
  614. .login-btn {
  615. height: 45px;
  616. }
  617. }
  618. .n-form {
  619. .n-form-item {
  620. .n-form-item-label {
  621. width: 85px !important;
  622. font-size: 12px;
  623. }
  624. .n-form-item-feedback-wrapper {
  625. min-height: 10px;
  626. }
  627. }
  628. }
  629. .pay-container {
  630. padding: 10px;
  631. :deep(.pay-box) {
  632. width: 50%;
  633. margin-right: 10px;
  634. .img {
  635. height: 150px;
  636. }
  637. }
  638. }
  639. }
  640. }
  641. .wxQRCode {
  642. width: 100%;
  643. text-align: center;
  644. }
  645. .load {
  646. z-index: 10;
  647. :deep(.n-spin-body) {
  648. left: auto;
  649. }
  650. }
  651. </style>