scheme.page.vue 7.4 KB

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