Преглед изворни кода

统一操作流程 & 页面跳转逻辑

cc12458 пре 3 месеци
родитељ
комит
622ea1aece

+ 2 - 2
package.json

@@ -16,8 +16,8 @@
   },
   "dependencies": {
     "@alova/mock": "^2.0.7",
-    "@vueuse/core": "^11.1.0",
-    "@vueuse/router": "^11.1.0",
+    "@vueuse/core": "^13.6.0",
+    "@vueuse/router": "^13.6.0",
     "alova": "^3.0.20",
     "echarts": "^5.5.1",
     "eruda": "^3.4.0",

Разлика између датотеке није приказан због своје велике величине
+ 592 - 1575
pnpm-lock.yaml


+ 59 - 0
src/computable/useRouteNext.ts

@@ -0,0 +1,59 @@
+import { tryOnBeforeMount } from '@vueuse/core';
+
+import { useRequest } from 'alova/client';
+import { processMethod } from '@/request/api';
+import type { Flow, FlowRoute } from '@/request/model';
+
+import { useVisitor } from '@/stores';
+
+export interface RouteNextOption {
+  immediate: boolean;
+
+  onSuccess(flow: FlowRoute): void | Promise<void>;
+
+  onError(error: Error | { message: string }): void;
+
+  onComplete(): void;
+}
+
+export function useRouteNext(option?: Partial<RouteNextOption>) {
+  const router = useRouter();
+  const path = computed(() => router.currentRoute.value.path);
+
+  const flow = shallowRef<FlowRoute>();
+
+  const { send, loading } = useRequest(processMethod, { immediate: false }).onSuccess(({ data, args: [key] }) => {
+    flow.value = data.parse(key);
+  });
+
+  const handle = async () => {
+    try {
+      await send(path.value);
+      loading.value = true;
+      await option?.onSuccess?.(flow.value!);
+    } catch (error) {
+      flow.value = void 0;
+      option?.onError?.(<{ message: string }>error);
+    }
+    option?.onComplete?.();
+    loading.value = false;
+    return flow.value;
+  };
+
+  tryOnBeforeMount(() => {
+    if (option?.immediate) {
+      const block = () => void 0;
+      send(path.value).then(block, block);
+    }
+  });
+
+  return { handle, flow, loading };
+}
+
+export function getRoutePath(flow?: Flow) {
+  let route = flow?.route;
+  if (!route) throw { message: `[路由] 页面未找到` };
+  if (route === '/report') route += `/${useVisitor().reportId}`;
+
+  return route;
+}

+ 3 - 3
src/loader/bridge.loader.ts

@@ -1,5 +1,5 @@
-import { processMethod, scanAccountMethod } from '@/request/api';
-import router                               from '@/router';
+import { getNextProcess, scanAccountMethod } from '@/request/api';
+import router                                from '@/router';
 
 import { platformIsAIO, platformIsAIO_1 } from '@/platform';
 
@@ -46,7 +46,7 @@ export default function bridgeLoader(): DEV.Loader {
           const data = await scanAccountMethod(detail.data.code).catch(() => {});
 
           if (data) {
-            const path = data?.path ?? (route?.path === '/screen' ? await processMethod() : route?.path);
+            const path = data?.path ?? (route?.path === '/screen' ? await getNextProcess().catch(() => route?.path) : route?.path);
             const key = Date.now();
             sessionStorage.setItem(`scan_${key}`, JSON.stringify(data));
             await router.replace({ path, query: { scan: key } });

+ 33 - 36
src/modules/camera/camera-result.page.vue

@@ -1,46 +1,42 @@
 <script setup lang="ts">
-import { processMethod2 } from '@/request/api';
-import { useRequest } from 'alova/client';
+import { tryOnBeforeMount, tryOnUnmounted, useCountdown } from '@vueuse/core';
 import { useRouter } from 'vue-router';
 
+import { getRoutePath, useRouteNext } from '@/computable/useRouteNext';
+import type { Flow } from '@/request/model';
+
+import { Notify } from '@/platform';
+
 defineOptions({
   name: 'CameraResult',
 });
 
-const router = useRouter();
+const actionText = computed(() => `获取健康调理方案`);
+/* 倒计时完成动作 */
+const done = shallowRef<Flow>();
+/* 下一动作可选 */
+const next = shallowRef<Flow>();
 
-const { data, loading } = useRequest(processMethod2).onSuccess(({ data }) => {
-  init(data.optional ? 10 : 5);
+const { handle, loading } = useRouteNext({
+  onSuccess(flow) {
+    done.value = flow.next.optional ? { title: '返回首页', route: '/screen' } : flow.next;
+    next.value = flow.next.optional ? flow.next : void 0;
+    start(flow.next.optional ? 10 : 5);
+  },
+  onError(error) { Notify.warning(error.message); },
 });
-const tips = computed(() => (data.value.route === '/screen' || data.value.optional ? '返回首页' : '获取健康调理方案'));
-const countdown = ref(5);
-
-let timer: ReturnType<typeof setInterval>;
 
-function done() {
-  if (!data.value.optional) next();
-  else {
-    clearInterval(timer);
-    router.replace({ path: '/screen' });
-  }
-}
-
-function next() {
-  clearInterval(timer);
-  router.replace({ path: data.value.route });
-}
+const { remaining, start, stop } = useCountdown(5, {
+  onComplete() { replace(done.value!); },
+  immediate: false,
+});
+const countdown = computed(() => remaining.value.toString().padStart(2, '0'));
 
-function init(value = 5) {
-  countdown.value = value;
-  timer = setInterval(() => {
-    const _countdown = countdown.value - 1;
-    if ( _countdown <= 0 ) { done(); } else { countdown.value = _countdown; }
-  }, 1000);
-}
+tryOnBeforeMount(() => handle());
+tryOnUnmounted(() => stop());
 
-onBeforeUnmount(() => {
-  clearInterval(timer);
-});
+const router = useRouter();
+const replace = (flow: Flow) => router.push({ path: getRoutePath(flow), replace: true });
 </script>
 <template>
   <div>
@@ -55,13 +51,14 @@ onBeforeUnmount(() => {
           <div class="text-3xl text-center my-8">完成舌面象的采集</div>
         </div>
       </main>
-      <footer class="flex flex-col items-center" style="flex: 1 1 30%">
+      <footer class="flex flex-col items-center gap-4" style="flex: 1 1 30%">
         <template v-if="!loading">
-          <van-button v-if="data.optional" class="decorate !text-xl !text-primary-400 !mb-6" :loading @click="next()">
-            {{ data.title || '获取健康调理方案' }}
+          <van-button v-if="next" class="decorate !text-xl" @click="replace(next)">
+            {{ next.title ?? actionText }}
           </van-button>
-          <van-button class="decorate !text-xl !text-primary-400" :loading @click="done()">
-            {{ tips }}({{ countdown }})
+          <van-button v-if="done" class="decorate !text-xl !text-primary-400" @click="replace(done)">
+            {{ done.title ?? actionText }}
+            <template v-if="remaining">({{ countdown }}s)</template>
           </van-button>
         </template>
       </footer>

+ 19 - 1
src/modules/camera/camera.page.vue

@@ -6,6 +6,7 @@ import { useForm, useRequest }              from 'alova/client';
 import Segmented, { type ConfigProps }      from './camera.config';
 import Camera                               from './camera.vue';
 
+import { getRoutePath, useRouteNext } from '@/computable/useRouteNext';
 import NavHomeSelect from '@/assets/images/nav-home.select.png?url';
 
 
@@ -13,13 +14,30 @@ let audio: HTMLAudioElement | void;
 
 const router = useRouter();
 
+const { flow } = useRouteNext({ immediate: true });
 const { form: dataset, loading: submitting, send: submit } = useForm(data => saveFileMethod(data), {
   initialForm: { } as Record<string, any>,
-}).onSuccess(({ data }) => router.replace(data.route)).onError(() => {
+}).onSuccess(({ data }) => replace()).onError(() => {
   handle();
   step.value = 1;
 });
 
+const replace = async () => {
+  let next = flow.value?.next;
+
+  if (next?.optional) {
+    const action = await Dialog.show({
+      title: next.title || '获取健康调理方案',
+      confirmButtonText: '好的',
+      showCancelButton: true,
+      cancelButtonText: '返回首页',
+      width: 350,
+    }).catch((err) => err)
+    if (action === 'cancel') next = { title: '返回首页', route: '/screen' };
+  }
+  return router.push({ path: getRoutePath(next), replace: true });
+}
+
 
 const step = ref(0);
 const snapshot = ref<string | void>();

+ 29 - 43
src/modules/pulse/pulse-result.page.vue

@@ -1,49 +1,41 @@
 <script setup lang="ts">
 import NavHomeSelect from '@/assets/images/nav-home.select.png?url';
 import NavNextSelect from '@/assets/images/next-step.svg?url';
-import { Dialog } from '@/platform';
+import { Dialog, Notify } from '@/platform';
 
 import { useRouteQuery } from '@vueuse/router';
-import { useRequest, useWatcher } from 'alova/client';
+import { useRequest } from 'alova/client';
 import { useRouter } from 'vue-router';
 import { getPulseMethod } from '@/request/api/pulse.api';
-import { processMethod3 } from '@/request/api';
+import type { Flow, FlowRoute } from '@/request/model';
+import { useRouteNext } from '@/computable/useRouteNext';
 
 const id = useRouteQuery<string>('id');
 
-const next = ref<{ title: string; route: string; icon?: string; }>();
-useRequest(processMethod3, { immediate: true }).onSuccess(({ data }) => {
-  if (data.next?.route === '/screen') {
-    next.value = { title: '返回首页', route: '/screen', icon: NavHomeSelect };
-  } else if (data.next) {
-    next.value = { title: data.next.title || '获取健康调理方案', route: data.next.route, icon: NavNextSelect  };
-  }
+const { handle, loading } = useRouteNext({
+  onSuccess(flow) { return load(flow); },
+  onError(error) { Notify.warning(error.message); },
 });
 
-const { data, loading, error } = useWatcher(() => getPulseMethod(id.value), [id], {
-  initialData: {
-    date: '',
-    pulse: {},
-  },
-  immediate: false,
-}).onError(async ({ error }) => {
-  await Dialog.show({
-    message: error.message,
-    theme: 'round-button',
-    showCancelButton: false,
-    confirmButtonText: '好的',
-    width: '350px',
-  });
-  //
-});
+const { data, error, send } = useRequest(getPulseMethod, { immediate: false, initialData: { date: '', pulse: {}, } });
 
-const router = useRouter();
+const back = shallowRef<Flow>();
+const next = shallowRef<Flow>();
 
-const scrollable = computed(() => true);
+async function load(flow: FlowRoute) {
+  back.value = flow.next.optional ? { title: '返回首页', route: '/screen' } : void 0;
+  next.value = flow.next.route !== '/screen' ? flow.next : void 0;
 
-function replace(path: string = '/screen') {
-  return router.replace({ path });
+  if (id.value) await send(id.value);
+  else { /* 从 Visitor 中获取 */ }
 }
+
+watch(id, (value, oldValue) => { if (value !== oldValue || value == null) handle(); }, { immediate: true });
+
+const router = useRouter();
+const replace = (flow: Flow) => router.replace(flow.route);
+
+const scrollable = computed(() => true);
 </script>
 <template>
   <div class="report-wrapper">
@@ -65,21 +57,15 @@ function replace(path: string = '/screen') {
           <AnalysisPulseComponent title="脉象分析" v-bind="data.pulse"></AnalysisPulseComponent>
         </div>
       </van-skeleton>
-      <div class="flex-none flex justify-between py-2 nav-wrapper" style="background-color: #12312c" v-if="next">
-        <div class="m-auto min-w-16 text-center hover:text-primary" @click="replace(next.route)">
-          <img v-if="next.icon" :src="next.icon" :alt="next.title" />
-          <div class="mt-2">{{ next.title }}</div>
+      <div class="flex-none flex justify-between py-2 nav-wrapper" style="background-color: #12312c" v-if="next || back">
+        <div v-if="back" class="m-auto min-w-16 text-center hover:text-primary" @click="replace(back)">
+          <img :src="NavHomeSelect" :alt="back.title" />
+          <div class="mt-2">{{ back.title }}</div>
         </div>
-        <!--        <div class="m-auto min-w-16 text-center hover:text-primary" v-if="data.scheme" @click="toggle()">
-          <img :src="NavScheme" alt="调理方案" />
-          <div class="mt-2">调理方案</div>
+        <div v-if="next" class="m-auto min-w-16 text-center hover:text-primary" @click="replace(next)">
+          <img :src="NavNextSelect" :alt="next.title" />
+          <div class="mt-2">{{ next.title ?? `获取健康调理方案` }}</div>
         </div>
-
-        <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="打印" />
-          <div class="mt-2">打印</div>
-        </div>-->
       </div>
     </div>
   </div>

+ 55 - 99
src/modules/pulse/pulse.page.vue

@@ -1,11 +1,11 @@
 <script setup lang="ts">
 import { Notify } from '@/platform';
-import { tryOnMounted, tryOnUnmounted } from '@vueuse/core';
+import { tryOnBeforeMount, tryOnUnmounted, useCountdown } from '@vueuse/core';
 
 import { useRequest } from 'alova/client';
 import { putPulseMethod } from '@/request/api/pulse.api';
-import { processMethod3 } from '@/request/api';
-import type { Flow } from '@/request/model';
+import type { Flow, FlowRoute } from '@/request/model';
+import { getRoutePath, useRouteNext } from '@/computable/useRouteNext';
 
 import { useVisitor } from '@/stores';
 
@@ -14,104 +14,65 @@ import NavHomeSelect from '@/assets/images/nav-home.select.png?url';
 const router = useRouter();
 const Visitor = useVisitor();
 
-const pending = ref(false);
 const finished = ref(false);
 const supported = ref(true);
+const actionText = computed(() => supported.value ? `获取健康调理方案` : `下一步`);
+/* 倒计时完成动作 */
+const done = shallowRef<Flow>();
+/* 下一动作可选 */
+const next = shallowRef<Flow>();
+
+const { handle, loading } = useRouteNext({
+  onSuccess(flow) { return pulse(flow); },
+  onError(error) { Notify.warning(error.message); },
+});
+
+const { remaining, start, stop } = useCountdown(5, {
+  onComplete() { replace(done.value!); },
+  immediate: false,
+});
+const countdown = computed(() => remaining.value.toString().padStart(2, '0'));
 
-async function handle() {
-  if (pending.value) return;
-  pending.value = true;
-  clearInterval(timer);
+tryOnBeforeMount(() => handle());
+tryOnUnmounted(() => stop());
 
-  const patientId = Visitor.patientId;
+async function pulse(flow: FlowRoute) {
+  stop();
+  const showReport = flow.next.route === '/pulse/result';
   try {
-    await load();
+    const patientId = Visitor.patientId;
     const result = await Bridge.pulse(patientId!!);
     await submit(patientId, result);
-  } catch (e: any) {
-    let message = e.message;
+    if (showReport) await replace(flow.next);
+    else {
+      finished.value = true;
+      done.value = flow.next.optional ? { title: '返回首页', route: '/screen' } : flow.next;
+      next.value = flow.next.optional ? flow.next : void 0;
+      start(10);
+    }
+  } catch (e) {
+    let message;
+    let countdown;
+
     if (e instanceof ReferenceError) {
       supported.value = false;
-      message = '当前环境不支持脉诊设备,请联系管理员';
-    }
-    if (!supported.value || process.value?.current?.optional) {
-      done.value = next.value?.optional
-        ? { ...next.value, countdown: 5 }
-        : {
-            title: '返回首页',
-            route: '/screen',
-            countdown: 5,
-          };
-      next.value = void 0;
-      start();
+      message = `当前环境不支持脉诊设备,请联系管理员!`;
+      countdown = 5;
     } else {
-      done.value = void 0;
-      message = '请再次测量脉诊';
+      message = `请再次测量脉诊!`;
+      countdown = 10;
     }
+    done.value = flow.value.optional && !showReport ? flow.next : { title: '返回首页', route: '/screen' };
+    start(countdown);
     Notify.warning(message);
   }
-  pending.value = false;
 }
 
-const done = shallowRef<Partial<Flow> & { countdown: number }>();
-const next = shallowRef<Partial<Flow>>();
-
-const {
-  data: process,
-  loading,
-  send: load,
-} = useRequest(processMethod3, { immediate: false }).onSuccess(({ data }) => {
-  if (data.next.route === '/screen') {
-    done.value = { title: '返回首页', route: '/screen', countdown: 30 };
-    next.value = void 0;
-  } else if (data.next.route === '/pulse/result') {
-    done.value = data.next.optional ? { title: '返回首页', route: '/screen', countdown: 10 } : void 0;
-    next.value = { title: data.next.title || '查看报告', route: data.next.route };
-  } else {
-    done.value = { title: data.next.title || '获取健康调理方案', route: data.next.route, countdown: 10 };
-    next.value = void 0;
-  }
-});
-
-const {
-  data: report,
-  loading: submitting,
-  send: submit,
-} = useRequest((id, result) => putPulseMethod(id, result), { immediate: false }).onSuccess(({ data }) => {
+const { data: report, loading: submitting, send: submit, } = useRequest((id, result) => putPulseMethod(id, result), { immediate: false }).onSuccess(({ data }) => {
   Visitor.updatePulseReport(data);
-  finished.value = true;
-  start();
 });
 
-let timer: ReturnType<typeof setInterval>;
-const countdown = ref(5);
-
-function start(value?: number) {
-  if (!done.value) {
-    if (next.value?.route === '/pulse/result') replace(next.value.route);
-    return;
-  }
-  countdown.value = value ?? done.value.countdown ?? 3;
-  timer = setInterval(() => {
-    const _countdown = countdown.value - 1;
-    if (_countdown <= 0) {
-      replace(done.value?.route);
-    } else {
-      countdown.value = _countdown;
-    }
-  }, 1000);
-}
-
-function replace(path: string = '/screen') {
-  return router.replace({ path });
-}
-
-tryOnMounted(() => {
-  setTimeout(() => handle(), 300);
-});
-tryOnUnmounted(() => {
-  clearInterval(timer);
-});
+const replace = (flow: Flow) => router.push({ path: getRoutePath(flow), replace: true });
 </script>
 <template>
   <div>
@@ -145,23 +106,18 @@ tryOnUnmounted(() => {
         </template>
       </main>
       <footer class="flex flex-col justify-center items-center">
-        <van-button
-          v-if="!pending && finished && next"
-          class="decorate !text-xl !text-primary-400"
-          @click="replace(next.route)"
-        >
-          {{ next.title }}
-        </van-button>
-        <van-button
-          v-if="!pending && done"
-          class="decorate !text-xl !text-primary-400 !mb-6"
-          @click="replace(done.route)"
-        >
-          {{ done.title }}({{ countdown }})
-        </van-button>
-        <div v-if="supported && !finished" class="van-button decorate" @click="handle()">
+        <template v-if="!loading">
+          <van-button v-if="next" class="decorate !text-xl" @click="replace(next)">
+            {{ next.title ?? actionText }}
+          </van-button>
+          <van-button v-if="done" class="decorate !text-xl !text-primary-400" @click="replace(done)">
+            {{ done.title ?? actionText }}
+            <template v-if="remaining">({{ countdown }}s)</template>
+          </van-button>
+        </template>
+        <div v-if="supported && !finished" class="van-button decorate" @click="!loading && handle()">
           <div class="van-button__content">
-            <van-loading v-if="loading || pending || submitting" />
+            <van-loading v-if="loading || submitting" />
             <span v-else class="van-button__text">连接脉诊</span>
           </div>
         </div>

+ 4 - 2
src/modules/questionnaire/page.vue

@@ -2,6 +2,7 @@
 import { Dialog, Notify, Toast } from '@/platform';
 import { questionnaireMethod } from '@/request/api';
 import type { QuestionnaireProps } from '@/request/model';
+import { getRoutePath, useRouteNext } from '@/computable/useRouteNext';
 
 import TierSelectField from './TierSelect.field.vue';
 
@@ -42,12 +43,13 @@ function handle() {
   }
 }
 
+const { loading: nextLoading, flow } = useRouteNext({ immediate: true });
 async function load() {
   const _first = first.value;
   loading.value = true;
   try {
     const { reportId, questionnaires } = await questionnaireMethod(<any>data.value);
-    if (reportId) return await router.replace(`/report/${reportId}`);
+    if (reportId) return await router.push({ path: getRoutePath(flow.value?.next!), replace: true });
     showTitle.value = _first;
     data.value = [];
     // TODO 延迟渲染
@@ -100,7 +102,7 @@ load();
             :disabled="loading"
           />
         </div>
-        <van-button class="decorate" block :loading @click="handle()">提交</van-button>
+        <van-button class="decorate" block :loading :disabled="nextLoading" @click="handle()">提交</van-button>
       </template>
       <van-toast v-else-if="first" :show="loading" type="loading" message="加载中" />
     </div>

+ 11 - 15
src/modules/report/report-analyse.page.vue

@@ -1,9 +1,11 @@
 <script setup lang="ts">
-import NavHomeSelect                               from '@/assets/images/nav-home.select.png?url';
-import { Dialog }                                  from '@/platform';
-import { getAnalysisResultsMethod, processMethod } from '@/request/api';
-import { useRequest }                              from 'alova/client';
-import { useRouter }                               from 'vue-router';
+import { useRouter } from 'vue-router';
+import { useRequest } from 'alova/client';
+import { getAnalysisResultsMethod } from '@/request/api';
+import { getRoutePath, useRouteNext } from '@/computable/useRouteNext';
+
+import { Dialog } from '@/platform';
+import NavHomeSelect from '@/assets/images/nav-home.select.png?url';
 
 
 const router = useRouter();
@@ -37,20 +39,14 @@ const panelOpen = (min?: number) => {
   panelHeight.value = panelProps.anchors[1];
 };
 
-const scrollable = computed(() => !data.value.payLock &&
-                                  panelHeight.value < panelProps.anchors[ 1 ] || panelHeight.value === 0,
-);
-
+const scrollable = computed(() => !data.value.payLock && panelHeight.value < panelProps.anchors[ 1 ] || panelHeight.value === 0);
 
-const { data: path, loading: nextLoading } = useRequest(processMethod).onSuccess((data) => {
-  console.log(data);
-});
+const { flow, loading: nextLoading } = useRouteNext({ immediate: true });
 
-const showNext = computed(() => path.value && path.value !== '/screen');
+const showNext = computed(() => flow.value?.next.route && flow.value?.next.route !== '/screen');
 
 function next() {
-  console.log(path);
-  router.replace({ path: path.value });
+  router.push({ path: getRoutePath(flow.value?.next), replace: true })
 }
 </script>
 <template>

+ 13 - 9
src/pages/register.page.vue

@@ -4,7 +4,6 @@ import { Notify, Toast } from '@/platform';
 
 import {
   getCaptchaMethod,
-  processMethod,
   registerAccountMethod,
   registerFieldsMethod,
   searchAccountMethod,
@@ -12,7 +11,8 @@ import {
 import type { Fields, RegisterModel } from '@/request/model';
 import { useRouteQuery }              from '@vueuse/router';
 
-import { useCaptcha, useRequest, useSerialRequest } from 'alova/client';
+import { getRoutePath, useRouteNext } from '@/computable/useRouteNext';
+import { useCaptcha, useRequest } from 'alova/client';
 
 import type { FormInstance }           from 'vant';
 import { RadioGroup as vanRadioGroup } from 'vant';
@@ -24,12 +24,16 @@ const formRef = useTemplateRef<FormInstance>('register-form');
 const modelRef = ref<Partial<RegisterModel>>({ code: '' });
 
 const router = useRouter();
-const { loading: submitting, send: submit } = useSerialRequest([
-  data => registerAccountMethod(data),
-  () => processMethod(),
-], { immediate: false })
-  .onSuccess(({ data }) => {router.replace(data);})
-  .onError(({ error }) => Notify.warning(error.message));
+
+const { loading: submitting, send: submit } = useRequest(registerAccountMethod, { immediate: false }).onSuccess(({ data }) => {
+  submitting.value = true;
+  handle();
+});
+const { handle } = useRouteNext({
+  onSuccess(flow) { return router.push({ path: getRoutePath(flow.next), replace: true }).then() },
+  onError(error) { Notify.warning(error.message); },
+  onComplete() { submitting.value = false; },
+});
 
 const { loading: searching, send: search } = useRequest((data) => searchAccountMethod(data), {
   immediate: false,
@@ -43,7 +47,7 @@ const { loading: captchaLoading, countdown, send: getCaptcha } = useCaptcha(
   { initialCountdown: 60 },
 ).onSuccess(({ data }) => {
   captchaLoaded = true;
-  Toast.success(data ?? '获取成功')
+  Toast.success(data ?? '获取成功');
 });
 const getCaptchaHandle = async () => {
   try {

+ 11 - 13
src/pages/screen.page.vue

@@ -1,7 +1,8 @@
 <script setup lang="ts">
 import { Dialog, Notify } from '@/platform';
 
-import { copyrightMethod, processMethod, registerVisitorMethod } from '@/request/api';
+import { copyrightMethod, registerVisitorMethod } from '@/request/api';
+import { useRouteNext, getRoutePath } from '@/computable/useRouteNext';
 
 import { useVisitor }     from '@/stores';
 import getBubbles         from '@/tools/bubble';
@@ -25,15 +26,15 @@ const { data: copyright, send: load } = useRequest(copyrightMethod).onError(asyn
   });
   await load();
 });
-const { send: handle, loading } = useRequest(processMethod, { immediate: false })
-  .onSuccess(({ data }) => {
+
+const { handle, loading } = useRouteNext({
+  async onSuccess(flow) {
     Visitor.$reset();
-    router.push({ path: data, replace: true }).then(
-      () => { if (visitor.value) Visitor.patientId = visitor.value; },
-      () => {}
-    );
-  })
-  .onError(({ error }) => Notify.warning(error.message));
+    await router.push({ path: getRoutePath(flow.next), replace: true })
+    if (visitor.value) Visitor.patientId = visitor.value;
+  },
+  onError(error) { Notify.warning(error.message); },
+});
 
 const container = useTemplateRef<HTMLDivElement>('container');
 const { width, height } = useElementSize(container);
@@ -50,10 +51,7 @@ interface Bubble {
 }
 
 watchEffect(() => {
-  if ( width.value && height.value ) {
-    init(
-      { width: width.value, height: height.value * 0.90, container: container.value! });
-  }
+  if ( width.value && height.value ) init({ width: width.value, height: height.value * 0.90, container: container.value! });
 });
 
 function init({ width, height, container }: { width: number; height: number; container: HTMLElement }) {

+ 1 - 19
src/request/api/camera.api.ts

@@ -1,7 +1,5 @@
 import HTTP              from '@/request/alova';
-import { processMethod2 } from '@/request/api/flow.api';
 import { useVisitor }    from '@/stores';
-import { Dialog } from '@/platform';
 
 
 export function uploadFileMethod(file: File) {
@@ -17,23 +15,7 @@ export function saveFileMethod(params: Record<string, string>) {
     params,
     async transform(data: string, headers) {
       Visitor.resultId = data;
-      const flow = await processMethod2();
-      let path = flow.optional
-        ? await Dialog.show({
-            title: flow.title || '获取健康调理方案',
-            confirmButtonText: '好的',
-            showCancelButton: true,
-            cancelButtonText: '返回首页',
-            width: 350,
-          }).then(
-            () => flow.route,
-            () => '/screen'
-          )
-        : flow.route;
-      return {
-        resultId: data,
-        route: { path },
-      };
+      return data;
     },
   });
 }

+ 11 - 43
src/request/api/flow.api.ts

@@ -1,5 +1,6 @@
 import { cacheFor }     from '@/request/api/index';
-import { type Flow, fromFlowData } from '@/request/model';
+import { FlowMap }      from '@/request/model';
+import type { FlowKey } from '@/request/model';
 import Router           from '@/router';
 
 import HTTP from '../alova';
@@ -18,50 +19,17 @@ export function copyrightMethod() {
 }
 
 export function processMethod() {
-  const path = unref(Router.currentRoute).path;
-  return HTTP.Post<string, { tabletProcessModules?: string[]; }>(`/fdhb-tablet/warrantManage/getPageSets`, {}, {
-    cacheFor, name: `variate:process`,
-    params: { k: 'process', p: path },
-    transform(data) {
-      const options = data.tabletProcessModules ?? [];
-      const ref = fromFlowData(options);
-      const flow = ref.get(path);
-      if ( !flow ) throw { message: `[路由] 配置异常无法解析正确路径,请联系管理员 (${ data?.tabletProcessModules?.join(' -> ') }})` };
-      return flow.route;
-    },
-  });
-}
-
-export function processMethod2() {
-  const path = unref(Router.currentRoute).path;
-  return HTTP.Post<Flow, { tabletProcessModules?: string[]; }>(`/fdhb-tablet/warrantManage/getPageSets`, {}, {
+  return HTTP.Post(`/fdhb-tablet/warrantManage/getPageSets`, void 0, {
     cacheFor, name: `variate:process`,
-    params: { k: 'process', p: path },
-    transform(data) {
-      const options = data.tabletProcessModules ?? [];
-      const ref = fromFlowData(options);
-      const flow = ref.get(path);
-      if ( !flow ) throw { message: `[路由] 配置异常无法解析正确路径,请联系管理员 (${ data?.tabletProcessModules?.join(' -> ') }})` };
-      return flow;
-    },
+    params: { k: 'process' },
+    transform(data: any, headers) { return new FlowMap(data?.tabletProcessModules ?? []); },
   });
 }
 
-
-export function processMethod3() {
-  const path = unref(Router.currentRoute).path;
-  return HTTP.Post<{ current: Flow; prev: Flow; next: Flow }, { tabletProcessModules?: string[]; }>(`/fdhb-tablet/warrantManage/getPageSets`, {}, {
-    cacheFor, name: `variate:process`,
-    params: { k: 'process', p: path },
-    transform(data) {
-      const options = data.tabletProcessModules ?? [];
-      const ref = fromFlowData(options);
-      const [key, current] = [...ref.entries()].find(([,flow]) => flow.route === path) || [];
-      const prev = [...ref.values()].find(flow => flow.route === key);
-      const next = ref.get(path);
-      if ( !next ) throw { message: `[路由] 配置异常无法解析正确路径,请联系管理员 (${ data?.tabletProcessModules?.join(' -> ') }})` };
-      return { current, prev, next } as any;
-    }
-  })
+export async function getNextProcess(path?: FlowKey) {
+  const process = await processMethod();
+  path ??= unref(Router.currentRoute).path as FlowKey;
+  const flow = process.get(path);
+  if (!flow) throw { message: `[路由] 配置异常无法解析正确路径,请联系管理员!` };
+  return flow.route;
 }
-

+ 1 - 0
src/request/api/questionnaire.api.ts

@@ -17,6 +17,7 @@ export function questionnaireMethod(data = []) {
       transform(data: Record<string, any>, headers) {
         const { storage: _storage, model } = fromQuestionnaireData(data);
         storage = _storage as any;
+        if (model.reportId) Visitor.reportId = model.reportId;
         return model;
       },
     }

+ 14 - 8
src/request/api/report.api.ts

@@ -16,16 +16,22 @@ export function getAnalysisResultsMethod() {
         const date = data?.tonguefaceAnalysisReportDate;
         const miniProgramURL = data?.tonguefaceAnalysisReportAppletImg;
         data = data.nextQuestions?.find((item: any) => item.classify === 'tongue_result');
+        let message = '';
         if ( data ) {
-          const { show, force } = await miniProgramMethod();
-          return {
-            id, date, miniProgramURL: show ? miniProgramURL : void 0,
-            tongue: fromAnalysisModel('tongue', data),
-            face: fromAnalysisModel('face', data),
-            payLock: show && force,
-          };
+          const tongue = fromAnalysisModel('tongue', data);
+          const face = fromAnalysisModel('face', data);
+          if (!tongue.result && !face.result) {
+            message = data.content;
+          } else {
+            const { show, force } = await miniProgramMethod();
+            return {
+              id, date, miniProgramURL: show ? miniProgramURL : void 0,
+              tongue, face,
+              payLock: show && force,
+            };
+          }
         }
-        throw { message: `[分析结果] 照片不符合检测要求,图片不是舌头(请拍摄带有舌头的、清晰的彩色照!)` };
+        throw { message: message || `[分析结果] 照片不符合检测要求,图片不是舌头(请拍摄带有舌头的、清晰的彩色照!)` };
       },
     },
   );

+ 51 - 21
src/request/model/flow.model.ts

@@ -1,3 +1,5 @@
+const ROUTE_START = '/screen';
+
 const Routes = {
   'patient_file': /* 建档页 */ '/register',
   'tongueface_upload': /*拍照页*/ '/camera',
@@ -8,38 +10,66 @@ const Routes = {
   'pulse_upload': /* 脉诊页 */ '/pulse',
   'pulse_upload_result': /* 脉诊结果页 */ '/pulse/result',
 
-  'screen': '/screen',
+  'screen': ROUTE_START,
 } as const;
 
+export type FlowKey = (typeof Routes)[keyof typeof Routes];
+
 export interface Flow {
-  route: string;
+  route: FlowKey;
   title?: string;
   optional?: boolean;
 }
 
-export function fromFlowData(options: string[]): Map<string, Flow> {
-  const ref = new Map<string, Flow>();
-  const length = options.length;
+export type FlowRoute = { next: Flow; prev: Flow | null; value: Flow };
+
+export class FlowMap extends Map<FlowKey, Flow> {
+  private readonly string: string = '';
+
+  constructor(options: string[]) {
+    super();
+    this.string = options.join('->');
 
-  // 修正虚拟路由
-  const k1 = 'tongueface_upload';
-  const k2 = 'tongueface_upload_result';
-  if ( !options.includes(k2) && options[ length - 1 ] === k1 ) options.push(k2);
+    const length = options.length;
+    if (length === 0) return;
 
-  options.unshift('/screen');
-  options.push('screen');
+    // 修正虚拟路由
+    const k1 = 'tongueface_upload';
+    const k2 = 'tongueface_upload_result';
+    if ( !options.includes(k2) && options[ length - 1 ] === k1 ) options.push(k2);
 
-  for ( let i = 1; i < options.length; i++ ) {
-    const path = options[i];
-    const optional = path.includes('?');
-    const [name, title] = path.split('?');
-    const route = (options[i] = getPath(name));
-    ref.set(options[i - 1], { route, optional, title, });
+    options.unshift(ROUTE_START);
+    options.push('screen?返回首页');
+
+    for ( let i = 1; i < options.length; i++ ) {
+      const path = options[i];
+      const optional = path.endsWith('?');
+      const [name, title] = path.split('?');
+      const route = (options[i] = getPath(name));
+      this.set(<FlowKey>options[i - 1], { route, optional, title: title || void 0});
+    }
   }
-  return ref;
-}
 
+  parse(key: FlowKey): FlowRoute {
+    const next = this.get(key);
+    if (!next) throw { message: `[路由] 配置异常无法解析正确路径,请联系管理员! (${this})` };
+
+    if (key === ROUTE_START) return { next, prev: null, value: { route: '/screen', optional: false, title: '' } };
+
+    const keys = [...this.keys()];
+    const values = [...this.values()];
+
+    const v = values.findIndex((flow) => flow.route === key);
+    const k = values.findIndex((flow) => flow.route === keys[v]);
+
+    return { next, prev: values[k], value: values[v] };
+  }
+
+  toString(): string {
+    return this.string || `未配置路由`;
+  }
+}
 
-export function getPath(value?: string) {
-  return Routes[ value as keyof typeof Routes ];
+export function getPath(value?: string): FlowKey {
+  return Routes[value as keyof typeof Routes];
 }

Неке датотеке нису приказане због велике количине промена