scheme.page.vue 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220
  1. <script setup lang="ts">
  2. import NavHomeSelect from '@/assets/images/nav-home.select.png?url';
  3. import NavScheme from '@/assets/images/nav-scheme.png?url';
  4. import SchemeMedia from '@/modules/report/SchemeMedia.vue';
  5. import { getReportSchemeMethod } from '@/request/api/report.api';
  6. import { useRouteParams } from '@vueuse/router';
  7. import { useWatcher } from 'alova/client';
  8. import { useRouter } from 'vue-router';
  9. import MiniProgram from '@/components/MiniProgram.vue';
  10. import type { SchemeGoodsProps } from '@/request/model';
  11. import { createReusableTemplate } from '@vueuse/core';
  12. import { Toast } from '@/platform';
  13. import { useVisitor } from '@/stores';
  14. const miniProgramRef = useTemplateRef<InstanceType<typeof MiniProgram>>('mini-program');
  15. const route = useRoute();
  16. const Visitor = useVisitor();
  17. const id = useRouteParams<string>('id', Visitor.reportId);
  18. const scrollable = ref(true);
  19. const closeable = computed(() => !data.value.payLock);
  20. const { data, loading } = useWatcher(() => getReportSchemeMethod(id.value, !route.meta.toggle), [id], {
  21. initialData: {
  22. children: [],
  23. },
  24. immediate: true,
  25. }).onSuccess(({ data }) => {
  26. if ( data?.miniProgramURL && data.payLock ) {
  27. scrollable.value = false;
  28. nextTick(() => miniProgramRef.value?.open());
  29. }
  30. });
  31. const router = useRouter();
  32. const toggleable = computed(() => route.meta.toggle ?? true);
  33. function toggle() {
  34. const path = router.currentRoute.value.fullPath.replace('/scheme', '');
  35. router.replace({ path });
  36. }
  37. const { define: PreviewLinkSlot, reuse: ReusePreviewLink } = createReusableTemplate<{ src: string | void; complete?: Function; error?: Function }>();
  38. const panelConfig = reactive({
  39. fullHeight: 0,
  40. height: 0,
  41. anchors: [0],
  42. goods: void 0 as unknown as SchemeGoodsProps,
  43. onError() {},
  44. onComplete(event?: Event) {},
  45. });
  46. const pageHeader = useTemplateRef('page-header');
  47. const container = useTemplateRef('container');
  48. const getHeightAndScrollTop = (value?: HTMLElement | string) => {
  49. const el = typeof value === 'string' ? container.value?.querySelector<HTMLDivElement>(`#${value}`) : value;
  50. if (!el) return true;
  51. el.scrollIntoView({ behavior: 'instant', block: 'start' });
  52. const maxHeight = window.innerHeight - (pageHeader.value?.getBoundingClientRect().height ?? 0);
  53. const height = window.innerHeight - el.getBoundingClientRect().bottom - Number.parseFloat(getComputedStyle(el).marginBottom);
  54. panelConfig.anchors = [0, height, maxHeight];
  55. panelConfig.height = height;
  56. panelConfig.fullHeight = maxHeight;
  57. };
  58. let lastFrameSrc: string;
  59. async function openGoodsPanel(goods: SchemeGoodsProps, event?: Event | string) {
  60. if (panelConfig.goods === goods) {
  61. panelConfig.height = panelConfig.anchors[panelConfig.anchors.length - 1];
  62. return;
  63. }
  64. if (goods.type === 'link') {
  65. const toast = lastFrameSrc !== goods.value ? Toast.loading(500) : void 0;
  66. lastFrameSrc = goods.value;
  67. panelConfig.goods = goods;
  68. panelConfig.onComplete = () => {
  69. toast?.close();
  70. panelConfig.height = panelConfig.anchors[panelConfig.anchors.length - 1];
  71. };
  72. panelConfig.onError = () => {
  73. Toast.error(`链接加载错误`)
  74. panelConfig.height = 0;
  75. };
  76. if (!toast) setTimeout(panelConfig.onComplete, 100);
  77. } else {
  78. Toast.warning(`暂不支持该操作 (${goods.type})`);
  79. return;
  80. }
  81. if (getHeightAndScrollTop(typeof event === 'string' ? event : (event?.target as HTMLElement))) {
  82. const height = window.innerHeight * 0.8;
  83. panelConfig.anchors = [0, height];
  84. panelConfig.height = height;
  85. panelConfig.fullHeight = window.innerHeight - (pageHeader.value?.getBoundingClientRect().height ?? 0);
  86. }
  87. }
  88. </script>
  89. <template>
  90. <div>
  91. <div ref="page-header" class="page-header flex py-4 px-4">
  92. <div class="grow shrink-0 h-full min-w-16"></div>
  93. <div class="grow-[3] shrink mx-2 flex flex-col justify-center overflow-hidden">
  94. <div class="font-bold text-3xl text-nowrap text-center tracking-wide overflow-ellipsis overflow-hidden">
  95. 调理方案
  96. </div>
  97. </div>
  98. <div class="grow shrink-0 h-full min-w-16">
  99. <router-link :to="{ path: '/screen' }" replace>
  100. <img class="size-8 object-scale-down" :src="NavHomeSelect" alt="返回首页" />
  101. </router-link>
  102. </div>
  103. </div>
  104. <div class="page-content flex flex-col overflow-hidden">
  105. <!--{{ data }}-->
  106. <van-skeleton class="flex-auto" title :row="3" :loading>
  107. <div class="flex-auto px-6" :class="[scrollable ? 'overflow-y-auto' : 'overflow-hidden']" ref="container">
  108. <div class="card my-6 text-lg" :id="item.id" v-for="item in data.children" :key="item.id">
  109. <div class="card__title mb-3 text-primary text-2xl font-bold">{{ item.title }}</div>
  110. <div class="card__content">
  111. <div class="my-4" :id="'T_' + card.id" v-for="card in item.children" :key="card.id">
  112. <div class="relative" :class="{ 'has-link': card.goods }">
  113. <div class="text-xl text-center text-primary" v-if="card.title">{{ card.title }}</div>
  114. <van-button
  115. class="!absolute top-0 right-0" v-if="card.goods"
  116. type="primary" icon="cart-o" size="small" plain
  117. @click="openGoodsPanel(card.goods, 'T_' + card.id)"
  118. >{{ card.goods.label }}</van-button>
  119. </div>
  120. <SchemeMedia :media="card.media"></SchemeMedia>
  121. <div v-if="card.description">{{ card.description }}</div>
  122. <div v-for="(item, index) in card.descriptions">
  123. <span class="text-primary">【{{ item.title }}】</span>
  124. <span v-html="item.description"></span>
  125. </div>
  126. </div>
  127. </div>
  128. </div>
  129. </div>
  130. </van-skeleton>
  131. <div class="flex-none flex justify-between py-2 nav-wrapper" style="background-color: #12312c">
  132. <div v-if="toggleable" class="m-auto min-w-16 text-center hover:text-primary" @click="toggle()">
  133. <img class="nav-img" :src="NavScheme" alt="健康报告" />
  134. <div class="mt-2">健康报告</div>
  135. </div>
  136. <mini-program ref="mini-program" :url="data.miniProgramURL" :closeable="!data.payLock"></mini-program>
  137. </div>
  138. </div>
  139. <PreviewLinkSlot v-slot="{ src, complete, error }">
  140. <iframe v-if="src" :src="src" :style="{ maxHeight: panelConfig.height - 30 + 'px' }" @load="complete!" @error="error!"></iframe>
  141. </PreviewLinkSlot>
  142. <van-floating-panel
  143. ref="panel-wrapper-ref"
  144. :class="{ full: panelConfig.height === panelConfig.fullHeight }"
  145. :content-draggable="false"
  146. :lock-scroll="true"
  147. :anchors="panelConfig.anchors"
  148. v-model:height="panelConfig.height"
  149. >
  150. <template #header>
  151. <div class="van-floating-panel__header !justify-between">
  152. <div></div>
  153. <div class="van-floating-panel__header-bar"></div>
  154. <van-icon class="pr-2" name="cross" @click="panelConfig.height = 0" />
  155. </div>
  156. </template>
  157. <ReusePreviewLink v-if="panelConfig.goods?.type === 'link'" :src="panelConfig.goods?.value" :complete="panelConfig.onComplete"></ReusePreviewLink>
  158. </van-floating-panel>
  159. </div>
  160. </template>
  161. <style scoped lang="scss">
  162. .card {
  163. padding: 24px;
  164. border-radius: 24px;
  165. box-shadow: inset 0 0 80px 0 #34a76b60;
  166. }
  167. .van-button.decorate {
  168. font-size: 20px;
  169. height: 62px;
  170. width: 240px;
  171. background-size: 80%;
  172. letter-spacing: 2px;
  173. }
  174. .text-grey {
  175. color: #e3e3e3;
  176. }
  177. .nav-wrapper {
  178. .nav-img {
  179. margin: auto;
  180. width: 36px;
  181. height: 36px;
  182. object-fit: scale-down;
  183. }
  184. }
  185. .full {
  186. --van-floating-panel-border-radius: 0;
  187. }
  188. .has-link {
  189. display: flex;
  190. justify-content: center;
  191. align-items: center;
  192. min-height: 40px;
  193. > div + .van-button {
  194. top: 4px !important;
  195. }
  196. }
  197. iframe {
  198. width: 100%;
  199. height: 100%;
  200. border: none;
  201. }
  202. </style>