Browse Source

Merge branch 'feature/task-153' into develop

cc12458 1 tháng trước cách đây
mục cha
commit
535c5e5116

+ 80 - 0
src/components/MiniProgram.vue

@@ -0,0 +1,80 @@
+<script setup lang="ts">
+import NavMiniProgram from '@/assets/images/mini-program.svg?url';
+import { Notify } from '@/platform';
+
+const props = defineProps<{ url?: string; closeable?: boolean; forcedShow?: boolean }>();
+
+const panelHeight = ref(0);
+const panelProps = reactive({
+  anchors: [0, window.innerWidth],
+  contentDraggable: false,
+  lockScroll: true,
+});
+const showOverlay = computed(() => panelHeight.value >= panelProps.anchors[0] && panelHeight.value > 0)
+
+watchEffect(() => {
+  panelProps.anchors[0] = props.closeable ? 0 : 100;
+});
+
+const handle = () => {
+  if (!props.url) {
+    Notify.warning(`未获取到小程序地址,请联系管理员或重试`);
+    return;
+  }
+  panelOpen();
+};
+
+const panelOpen = (min?: number) => {
+  if (min) panelProps.anchors[0] = min;
+  panelHeight.value = panelProps.anchors[1];
+};
+
+defineExpose({
+  open: handle,
+});
+</script>
+
+<template>
+  <div class="nav m-auto min-w-16 text-center hover:text-primary" v-if="url || props.forcedShow" @click="handle()">
+    <img class="nav-img" :src="NavMiniProgram" alt="小程序" />
+    <div class="mt-2">小程序</div>
+  </div>
+  <van-overlay :show="showOverlay" class="panel-mask"></van-overlay>
+  <van-floating-panel v-model:height="panelHeight" v-bind="panelProps">
+    <template #header>
+      <div class="van-floating-panel__header !justify-between">
+        <div></div>
+        <div class="van-floating-panel__header-bar"></div>
+        <div>
+          <van-icon v-if="props.closeable" name="cross" @click.stop="panelHeight = panelProps.anchors[0]" />
+        </div>
+      </div>
+    </template>
+    <Transition>
+      <div class="panel-content">
+        <img class="size-full object-contain" v-if="panelHeight === panelProps.anchors[1] || panelHeight > panelProps.anchors[0] * 1.5" :src="url" alt="小程序码" />
+        <div class="flex justify-center items-center" v-else @click="panelOpen()">
+          <img class="h-10 w-10" src="@/assets/images/mini-program.svg" alt="小程序" />
+          <span class="text-lg ml-2">小程序</span>
+        </div>
+      </div>
+    </Transition>
+  </van-floating-panel>
+</template>
+
+<style scoped lang="scss">
+.nav-img {
+  margin: auto;
+  width: 36px;
+  height: 36px;
+  object-fit: scale-down;
+}
+
+.panel-mask {
+  --van-overlay-background: transparent;
+}
+
+.panel-content {
+  padding: 0 var(--van-floating-panel-header-height) var(--van-floating-panel-header-height);
+}
+</style>

+ 1 - 0
src/computable/useRouteNext.ts

@@ -54,6 +54,7 @@ export function getRoutePath(flow?: Flow) {
   let route = flow?.route;
   if (!route) throw { message: `[路由] 页面未找到` };
   if (route === '/report') route += `/${useVisitor().reportId}`;
+  if (route === '/scheme') route += `/${useVisitor().reportId}`;
 
   return route;
 }

+ 2 - 0
src/modules/report/SchemeMedia.vue

@@ -55,6 +55,8 @@ function onPlay(event?: Event) {
     <div class="flex-none mx-2" v-for="item in media" :key="item.title">
       <div v-if="item.url" class="relative h-32 rounded-lg	overflow-hidden" @click="handle(item)">
         <img class="size-full object-scale-down" v-if="item.poster" :src="item.poster" :alt="item.title" />
+        <video class="size-full object-scale-down" v-else-if="item.type === 'video'" :src="item.url" preload="metadata"></video>
+        <div class="size-full object-scale-down w-64 bg-gray-600" v-else></div>
         <van-icon class="play" v-if="item.type === 'video'" name="play-circle-o" />
       </div>
       <template v-if="item.title">

+ 11 - 58
src/modules/report/report.page.vue

@@ -13,6 +13,9 @@ import { useRouteParams }         from '@vueuse/router';
 import { useRequest, useWatcher } from 'alova/client';
 import { useRouter }              from 'vue-router';
 
+import MiniProgram from '@/components/MiniProgram.vue';
+const miniProgramRef = useTemplateRef<InstanceType<typeof MiniProgram>>('mini-program');
+
 const hidePulseExceptionTemplate = computed(() => platformIsAIO())
 
 const id = useRouteParams<string>('id');
@@ -25,7 +28,9 @@ const { data, loading } = useWatcher(() => getReportMethod(id.value), [ id ], {
   },
   immediate: true,
 }).onSuccess(({ data }) => {
-  if ( data?.miniProgramURL && data.payLock ) panelOpen(100);
+  if ( data?.miniProgramURL && data.payLock ) {
+    nextTick(() => miniProgramRef.value?.open());
+  }
 })
 
 const { loading: uploading, send: upload } = useRequest(() => updateReportMethod(id.value, data.value), {
@@ -72,36 +77,12 @@ async function print() {
   }
 }
 
-async function miniProgram() {
-  let url = data.value.miniProgramURL;
-  if ( !url ) {
-    Notify.warning(`未获取到小程序地址,请联系管理员或重试`);
-    return;
-  }
-  panelOpen();
-}
-
 const router = useRouter();
 
 function toggle() {
   const path = `${ router.currentRoute.value.fullPath }/scheme`.replace(/\/{2,}/g, '/');
   router.replace({ path });
 }
-
-const panelHeight = ref(0);
-const panelProps = reactive({
-  anchors: [0, window.innerWidth],
-  contentDraggable: false,
-  lockScroll: true,
-});
-const panelOpen = (min?: number) => {
-  if ( min ) panelProps.anchors[ 0 ] = min;
-  panelHeight.value = panelProps.anchors[1];
-};
-
-const scrollable = computed(() => !data.value.payLock &&
-                                  panelHeight.value < panelProps.anchors[ 1 ] || panelHeight.value === 0,
-);
 </script>
 <template>
   <div class="report-wrapper">
@@ -120,7 +101,7 @@ const scrollable = computed(() => !data.value.payLock &&
     </div>
     <div class="page-content flex flex-col overflow-hidden">
       <van-skeleton class="flex-auto" title :row="3" :loading>
-        <div class="flex-auto" :class="{ 'overflow-y-auto': scrollable }">
+        <div class="flex-auto overflow-y-auto">
           <div class="my-6 text-primary text-2xl text-center">报告日期:{{ data.date }}</div>
           <div class="card m-6 text-lg">
             <div class="card__title text-primary text-3xl font-bold"></div>
@@ -216,45 +197,17 @@ const scrollable = computed(() => !data.value.payLock &&
       </van-skeleton>
       <div class="flex-none flex justify-between py-2 nav-wrapper" style="background-color: #12312c;">
         <div class="m-auto min-w-16 text-center hover:text-primary" v-if="data.scheme" @click="toggle()">
-          <img :src="NavScheme" alt="调理方案">
+          <img class="nav-img" :src="NavScheme" alt="调理方案">
           <div class="mt-2">调理方案</div>
         </div>
-        <div class="m-auto min-w-16 text-center hover:text-primary" v-if="data.miniProgramURL" @click="miniProgram()">
-          <img :src="NavMiniProgram" alt="小程序">
-          <div class="mt-2">小程序</div>
-        </div>
+        <mini-program ref="mini-program" :url="data.miniProgramURL" :closeable="!data.payLock"></mini-program>
         <div class="m-auto min-w-16 text-center hover:text-primary" @click="print()">
           <van-loading v-if="uploading" color="#38ff6e" style="font-size: 24px;" />
-          <img v-else :src="NavPrint" alt="打印">
+          <img class="nav-img" v-else :src="NavPrint" alt="打印">
           <div class="mt-2">打印</div>
         </div>
       </div>
       <Component :is="ReportPreview" v-bind="reportPreviewProps" v-model:show="reportPreviewProps.show"></Component>
-      <van-floating-panel v-model:height="panelHeight" v-bind="panelProps">
-        <template #header>
-          <div class="van-floating-panel__header !justify-between">
-            <div></div>
-            <div class="van-floating-panel__header-bar"></div>
-            <div>
-              <van-icon v-if="!data.payLock" name="cross" @click.stop="panelHeight = panelProps.anchors[0];" />
-            </div>
-          </div>
-        </template>
-        <Transition>
-          <div class="panel-content">
-            <img
-              class="size-full object-contain"
-              v-if="panelHeight === panelProps.anchors[1] || panelHeight > panelProps.anchors[0] * 1.5"
-              :src="data.miniProgramURL"
-              alt="小程序码"
-            />
-            <div class="flex justify-center items-center" v-else @click="panelOpen()">
-              <img class="h-10 w-10" src="@/assets/images/mini-program.svg" alt="小程序" />
-              <span class="text-lg ml-2">小程序</span>
-            </div>
-          </div>
-        </Transition>
-      </van-floating-panel>
     </div>
   </div>
 </template>
@@ -299,7 +252,7 @@ const scrollable = computed(() => !data.value.payLock &&
 }
 
 .nav-wrapper {
-  img {
+  .nav-img {
     margin: auto;
     width: 36px;
     height: 36px;

+ 24 - 5
src/modules/report/scheme.page.vue

@@ -7,17 +7,26 @@ import { useRouteParams }        from '@vueuse/router';
 import { useWatcher }            from 'alova/client';
 import { useRouter } from 'vue-router';
 
+import MiniProgram from '@/components/MiniProgram.vue';
+const miniProgramRef = useTemplateRef<InstanceType<typeof MiniProgram>>('mini-program');
 
+const route = useRoute();
 const id = useRouteParams<string>('id');
-const { data, loading } = useWatcher(() => getReportSchemeMethod(id.value), [ id ], {
+const { data, loading } = useWatcher(() => getReportSchemeMethod(id.value, !route.meta.toggle), [id], {
   initialData: {
     children: [],
   },
   immediate: true,
+}).onSuccess(({ data }) => {
+  if ( data?.miniProgramURL && data.payLock ) {
+    nextTick(() => miniProgramRef.value?.open());
+  }
 });
 
 const router = useRouter();
 
+const toggleable = computed(() => route.meta.toggle ?? true);
+
 function toggle() {
   const path = router.currentRoute.value.fullPath.replace('/scheme', '');
   router.replace({ path });
@@ -49,7 +58,7 @@ function toggle() {
                 <div class="text-xl text-center text-primary">{{ card.title }}</div>
                 <SchemeMedia :media="card.media"></SchemeMedia>
                 <div v-if="card.description">{{ card.description }}</div>
-                <div v-for="(item,index) in card.descriptions ">
+                <div v-for="(item, index) in card.descriptions">
                   <span class="text-primary">【{{ item.title }}】</span>
                   <span v-html="item.description"></span>
                 </div>
@@ -58,11 +67,12 @@ function toggle() {
           </div>
         </div>
       </van-skeleton>
-      <div class="flex-none flex justify-between py-2 nav-wrapper" style="background-color: #12312c;">
-        <div class="m-auto min-w-16 text-center hover:text-primary" @click="toggle()">
-          <img :src="NavScheme" alt="健康报告">
+      <div class="flex-none flex justify-between py-2 nav-wrapper" style="background-color: #12312c">
+        <div v-if="toggleable" class="m-auto min-w-16 text-center hover:text-primary" @click="toggle()">
+          <img class="nav-img" :src="NavScheme" alt="健康报告" />
           <div class="mt-2">健康报告</div>
         </div>
+        <mini-program ref="mini-program" :url="data.miniProgramURL" :closeable="!data.payLock"></mini-program>
       </div>
     </div>
   </div>
@@ -85,4 +95,13 @@ function toggle() {
 .text-grey {
   color: #e3e3e3;
 }
+
+.nav-wrapper {
+  .nav-img {
+    margin: auto;
+    width: 36px;
+    height: 36px;
+    object-fit: scale-down;
+  }
+}
 </style>

+ 22 - 10
src/request/api/report.api.ts

@@ -23,7 +23,7 @@ export function getAnalysisResultsMethod() {
           if (!tongue.result && !face.result) {
             message = data.content;
           } else {
-            const { show, force } = await miniProgramMethod();
+            const { show, force } = await miniProgramMethod('report');
             return {
               id, date, miniProgramURL: show ? miniProgramURL : void 0,
               tongue, face,
@@ -47,7 +47,7 @@ export function getReportMethod(id: string) {
       if (params.patientId !== patientId ) Visitor.$reset()
       const report = fromReportData(<any> data);
       Visitor.updatePulseReport(report.pulse, patientId);
-      const { show, force } = await miniProgramMethod();
+      const { show, force } = await miniProgramMethod('report');
       if ( !show ) { report.miniProgramURL = void 0; }
       report.payLock = show && force;
       return report;
@@ -80,27 +80,39 @@ export function updateReportMethod(id: string, data: Record<string, any>) {
   return HTTP.Post(`/fdhb-tablet/analysisManage/upConFacImgById`, params, {});
 }
 
-export function getReportSchemeMethod(id: string) {
+export function getReportSchemeMethod(id: string, standalone?: boolean) {
   const Visitor = useVisitor();
-  const params = { healthAnalysisReportId: id, patientId: Visitor.patientId };
+  const params = { healthAnalysisReportId: id, patientId: Visitor.patientId, standalone };
   return HTTP.Get(`/fdhb-tablet/analysisManage/getCondProgDetailById`, {
     params,
-    transform(data: any, headers) {
-      return fromSchemeRequest(data);
+    async transform(data: any, headers) {
+      const scheme = fromSchemeRequest(data);
+      const { show, force } = await miniProgramMethod('scheme');
+      if ( !show ) { scheme.miniProgramURL = void 0; }
+      scheme.payLock = show && force;
+      return scheme;
     },
   });
 }
 
 
-export function miniProgramMethod() {
+export function miniProgramMethod(type: 'report' | 'scheme' = 'report') {
   return HTTP.Post<{ show: boolean; force: boolean; }, { tabletRequiredPageOperationElements: string[] }>(
     `/fdhb-tablet/warrantManage/getPageSets`, void 0, {
       cacheFor, name: `variate:mini_program`,
-      params: { k: 'mini_program' },
+      params: { k: `mini_program_${type}` },
       transform(data, headers) {
+        const cfg = Array.isArray(data.tabletRequiredPageOperationElements) ? data.tabletRequiredPageOperationElements : [];
+        /**
+         *  - health_analysis_report_page_appletbutton
+         *  - health_analysis_report_page_appletscan
+         *
+         *  - health_analysis_scheme_page_appletbutton
+         *  - health_analysis_scheme_page_appletscan
+         */
         return {
-          show: data?.tabletRequiredPageOperationElements?.includes('health_analysis_report_page_appletbutton'),
-          force: data?.tabletRequiredPageOperationElements?.includes('health_analysis_report_page_appletscan'),
+          show: cfg.includes(`health_analysis_${type}_page_appletbutton`),
+          force: cfg.includes(`health_analysis_${type}_page_appletscan`),
         };
       },
     });

+ 1 - 0
src/request/model/flow.model.ts

@@ -7,6 +7,7 @@ const Routes = {
   'tongueface_analysis': /* 问卷页 */ '/questionnaire',
   'tongueface_analysis_result': /* 舌面象分析报告页 */ '/report/analysis',
   'health_analysis': /* 健康报告页 */ '/report',
+  'health_analysis_scheme': /* 调理方案页 */ '/scheme',
   'pulse_upload': /* 脉诊页 */ '/pulse',
   'pulse_upload_result': /* 脉诊结果页 */ '/pulse/result',
   'alcohol_upload_result': /* 酒精结果页 */ '/alcohol/result',

+ 2 - 0
src/request/model/scheme.model.ts

@@ -14,6 +14,8 @@ export function fromSchemeRequest(data: Data) {
         }) ?? [],
       };
     }) ?? [],
+    miniProgramURL: data?.appletImg,
+    payLock: data?.payLock,
   };
 }
 

+ 2 - 1
src/router/index.ts

@@ -13,8 +13,9 @@ const router = createRouter({
     { path: '/questionnaire', component: () => import('@/modules/questionnaire/page.vue'), meta: { title: '问卷' } },
     { path: '/alcohol/result', component: () => import('@/modules/alcohol/alcohol.page.vue'), meta: { title: '黄酒建议' } },
     { path: '/report/analysis', component: () => import('@/modules/report/report-analyse.page.vue'), meta: { title: '舌面象分析报告' } },
-    { path: '/report/:id/scheme', component: () => import('@/modules/report/scheme.page.vue'), meta: { title: '调理方案' } },
+    { path: '/report/:id/scheme', component: () => import('@/modules/report/scheme.page.vue'), meta: { title: '调理方案', toggle: true } },
     { path: '/report/:id', component: () => import('@/modules/report/report.page.vue'), meta: { title: '健康分析报告' } },
+    { path: '/scheme/:id', component: () => import('@/modules/report/scheme.page.vue'), meta: { title: '调理方案', toggle: false, } },
     { path: '/', redirect: '/screen' },
   ],
 });