2
0

3 Achegas 9308f2b79b ... 755f4f93a6

Autor SHA1 Mensaxe Data
  kumu 755f4f93a6 首页添加 游客访问 方式 hai 6 meses
  kumu 031717eabb 完善报告 pdf 取值 hai 6 meses
  cc12458 61d43bab1c 1. 建档页表单必填字段 hai 6 meses

+ 1 - 5
src/components/AnalysisComponent.vue

@@ -4,14 +4,10 @@ import type { AnalysisModel } from '@/request/model';
 type Props = AnalysisModel & { title?: string };
 
 const { table, exception, result = null, cover = [], title = '分析' } = defineProps<Props>();
-const attrs = useAttrs();
-watchEffect(() => {
-  console.log(result, '12-->', attrs);
-});
 </script>
 
 <template>
-  <van-skeleton class="analysis" :row="5" :loading="result == null">
+  <van-skeleton class="analysis" :row="5" :loading="result == null" v-if="result !== ''">
     <slot>
       <div class="card m-6 text-lg">
         <div class="card__title mb-3 text-primary text-2xl font-bold">{{ title }}</div>

+ 33 - 7
src/loader/bridge.loader.ts

@@ -1,30 +1,56 @@
-import router from '@/router';
-import pinia  from '@/stores';
+import { processMethod, scanAccountMethod } from '@/request/api';
+import router                               from '@/router';
+
+import type { RouteLocation } from 'vue-router';
 
 
 export interface globalAIO {
   scan(value: string): number;
+  print(value: string): void;
 }
 
 declare var window: Window & typeof globalThis & { AIO: globalAIO };
 
 
 export default function bridgeLoader(): DEV.Loader {
+  async function scan(value: string, route?: RouteLocation) {
+    const { Toast } = await import(`@/platform/toast.ui`);
+    const toast = Toast.loading(100, { message: '加载中' });
+    const data = await scanAccountMethod(value).catch(() => {});
+
+    if ( data ) {
+      const path = data?.path ?? (
+        route?.path === '/screen' ? await processMethod() : route?.path
+      );
+      const key = Date.now();
+      sessionStorage.setItem(`scan_${ key }`, JSON.stringify(data));
+      await router.replace({ path, query: { scan: key } });
+      Toast.success('扫码成功');
+    }
+    toast.close();
+  }
+
+
   return async function() {
     window.AIO ??= {} as globalAIO;
 
     window.AIO.scan = (value) => {
       if ( !value ) return -1;
 
-      if ( !router.currentRoute.value.meta?.scan ) {
+      const route = unref(router.currentRoute);
+
+      if ( !route.meta?.scan ) {
         import(`@/platform/notify.ui`).then(({ Notify }) => { Notify.warning(`请返回首页后,再进行扫码!`); });
         return 1;
       }
-
-      const key = Date.now();
-      sessionStorage.setItem(`scan_${ key }`, JSON.stringify(value));
-      router.replace({ path: '/register', query: { scan: key } });
+      scan(value, route);
       return 0;
     };
+
+    window.AIO.print = (value) => {
+      (
+        window as any
+      ).sixWisdom.printPdfByUrl(value);
+    };
   };
 }

+ 9 - 7
src/modules/camera/camera-result.page.vue

@@ -1,5 +1,6 @@
 <script setup lang="ts">
-import { useRouteQuery } from '@vueuse/router';
+import { processMethod } from '@/request/api';
+import { useRequest }    from 'alova/client';
 import { useRouter }     from 'vue-router';
 
 
@@ -8,23 +9,24 @@ defineOptions({
 });
 
 const router = useRouter();
-const to = useRouteQuery('to', '/screen');
-const tips = computed(() => to.value.includes('screen') ? '返回首页' : '下一步');
+
+const { data, loading } = useRequest(processMethod).onSuccess(() => {init(); });
+const tips = computed(() => data.value === '/screen' ? '返回首页' : '下一步');
 const countdown = ref(5);
 
 let timer: ReturnType<typeof setInterval>;
 
 function done() {
   clearInterval(timer);
-  router.replace({ path: to.value });
+  router.replace({ path: data.value });
 }
 
-onMounted(() => {
+function init() {
   timer = setInterval(() => {
     const _countdown = countdown.value - 1;
     if ( _countdown <= 0 ) { done(); } else { countdown.value = _countdown; }
   }, 1000);
-});
+}
 
 onBeforeUnmount(() => {
   clearInterval(timer);
@@ -44,7 +46,7 @@ onBeforeUnmount(() => {
         </div>
       </main>
       <footer class="flex justify-evenly items-start" style="flex: 1 1 30%">
-        <van-button class="decorate !text-xl !text-primary-400" @click="done()">
+        <van-button class="decorate !text-xl !text-primary-400" :loading @click="done()">
           {{ tips }}({{ countdown }})
         </van-button>
       </footer>

+ 7 - 8
src/modules/report/NavBar.vue

@@ -1,10 +1,9 @@
 <script setup lang="ts">
-import NavHome         from '@/assets/images/nav-home.png?url';
-import NavHomeSelect   from '@/assets/images/nav-home.select.png?url';
-import NavPrint        from '@/assets/images/nav-print.png?url';
-import NavPrintSelect  from '@/assets/images/nav-print.select.png?url';
-import NavScheme       from '@/assets/images/nav-scheme.png?url';
-import NavSchemeSelect from '@/assets/images/nav-scheme.select.png?url';
+import NavMiniProgramSelect from '@/assets/images/mini-program.svg?url';
+import NavPrint             from '@/assets/images/nav-print.png?url';
+import NavPrintSelect       from '@/assets/images/nav-print.select.png?url';
+import NavScheme            from '@/assets/images/nav-scheme.png?url';
+import NavSchemeSelect      from '@/assets/images/nav-scheme.select.png?url';
 
 
 interface Tabbar {
@@ -32,8 +31,8 @@ watchEffect(() => {
   ] : [
     // { key: 'screen', label: '返回首页', icon: NavHome, select: NavHomeSelect },
     { key: 'scheme', label: '调理方案', icon: NavScheme, select: NavSchemeSelect },
-    // { key: 'print', label: '打印', icon: NavPrint, select: NavPrintSelect },
-    { key: 'mini', label: '小程序', icon: NavPrint, select: NavPrintSelect },
+    { key: 'mini', label: '小程序', icon: NavMiniProgramSelect, select: NavMiniProgramSelect },
+    { key: 'print', label: '打印', icon: NavPrint, select: NavPrintSelect },
   ];
 });
 

+ 42 - 24
src/modules/report/report-analyse.page.vue

@@ -1,27 +1,29 @@
 <script setup lang="ts">
-import { useRequest } from 'alova/client';
-import { analysisResultsMethod } from '@/request/api/analysis.api';
-import { Dialog } from '@/platform';
-import NavHomeSelect from '@/assets/images/nav-home.select.png?url';
+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';
+
 
 const router = useRouter();
 
-const { data, loading, error } = useRequest(analysisResultsMethod, {
-  initialData: {
-    tongue: {},
-    face: {},
-  },
-}).onSuccess(({ data }) => {
-  if (data?.miniProgramURL) panelOpen(data.payLock ? panelProps.anchors[1] : 100);
-}).onError(async ({ error }) => {
-  await Dialog.show({
-    message: error.message,
-    theme: 'round-button',
-    showCancelButton: false,
-    confirmButtonText: '好的',
-    width: '350px',
-  });
-  await router.replace(`/camera`);
+const { data, loading, error } = useRequest(getAnalysisResultsMethod, { initialData: { tongue: {}, face: {} } })
+  .onSuccess(({ data }) => {
+    if ( data?.miniProgramURL ) {
+      panelProps.anchors[ 0 ] = 100;
+      if ( data.payLock ) panelOpen(100);
+    }
+  })
+  .onError(async ({ error }) => {
+    await Dialog.show({
+      message: error.message,
+      theme: 'round-button',
+      showCancelButton: false,
+      confirmButtonText: '好的',
+      width: '350px',
+    });
+    await router.replace(`/camera`);
 });
 
 const panelHeight = ref(0);
@@ -29,16 +31,27 @@ const panelProps = reactive({
   anchors: [0, window.innerWidth],
   contentDraggable: false,
   lockScroll: true,
-
-  show: true,
 });
 const panelOpen = (min?: number) => {
-  if (panelProps.show && min) panelProps.anchors[0] = min;
+  if ( min ) panelProps.anchors[ 0 ] = min;
   panelHeight.value = panelProps.anchors[1];
 };
 
-const scrollable = computed(() => 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 showNext = computed(() => path.value && path.value !== '/screen');
 
+function next() {
+  console.log(path);
+  router.replace({ path: path.value });
+}
 </script>
 <template>
   <div class="report-wrapper">
@@ -61,6 +74,11 @@ const scrollable = computed(() => panelHeight.value < panelProps.anchors[1] || p
           <div class="my-6 text-primary text-2xl text-center" v-if="data.date">报告日期:{{ data.date }}</div>
           <AnalysisComponent title="舌象分析" v-bind="data.tongue"></AnalysisComponent>
           <AnalysisComponent title="面象分析" v-bind="data.face"></AnalysisComponent>
+          <div class="m-4" v-if="!nextLoading && showNext">
+            <div class="m-auto size-16 cursor-pointer">
+              <img class="size-full" src="@/assets/images/next-step.svg" alt="提交" @click="next()">
+            </div>
+          </div>
           <div :style="{ height: panelHeight + 'px' }"><!--补偿面板打开高度--></div>
         </div>
       </van-skeleton>

+ 64 - 20
src/modules/report/report.page.vue

@@ -1,13 +1,17 @@
 <script setup lang="ts">
-import NavBar                                  from '@/modules/report/NavBar.vue';
+import NavMiniProgram    from '@/assets/images/mini-program.svg?url';
+import NavHomeSelect     from '@/assets/images/nav-home.select.png?url';
+import NavPrint          from '@/assets/images/nav-print.png?url';
+import NavScheme         from '@/assets/images/nav-scheme.png?url';
+
 import PhysiqueChart                           from '@/modules/report/PhysiqueChart.vue';
 import SyndromeChart                           from '@/modules/report/SyndromeChart.vue';
-import { Notify }                              from '@/platform';
-import { getReportMethod, updateReportMethod } from '@/request/api/report.api';
-import { useRouteParams }                      from '@vueuse/router';
-import { useRequest, useWatcher }              from 'alova/client';
+import { Notify, Toast } from '@/platform';
+import { getReportMethod, updateReportMethod } from '@/request/api';
 
-import NavHomeSelect from '@/assets/images/nav-home.select.png?url';
+import { useRouteParams }         from '@vueuse/router';
+import { useRequest, useWatcher } from 'alova/client';
+import { useRouter }              from 'vue-router';
 
 
 const id = useRouteParams<string>('id');
@@ -19,12 +23,13 @@ const { data, loading } = useWatcher(() => getReportMethod(id.value), [ id ], {
   },
   immediate: true,
 }).onSuccess(({ data }) => {
-  if (data?.miniProgramURL) panelOpen(data.payLock ? panelProps.anchors[1] : 100);
+  if ( data?.miniProgramURL && data.payLock ) panelOpen(100);
 })
 
 const { loading: uploading, send: upload } = useRequest(() => updateReportMethod(id.value, data.value), {
   immediate: false,
   middleware(_, next) {
+    if (data.value.reportURL) return;
     const hasConstitutionGroupImg = data.value.constitutionGroupImg;
     const hasFactorItemRadarImg = data.value[ '中医证素' ]?.length ? data.value.factorItemRadarImg : true;
     if ( hasConstitutionGroupImg && hasFactorItemRadarImg ) { next(); }
@@ -40,16 +45,26 @@ const reportPreviewProps = reactive({
 });
 async function print() {
   let url = data.value.reportURL;
-  if (!url) url = await upload();
+  if ( !url ) url = await upload();
   if (!url) {
     Notify.warning(`未获取到报告地址,请联系管理员或重试`);
     return;
   }
-  ReportPreview = defineAsyncComponent(() => import('./ReportPreview.vue'));
-  reportPreviewProps.mode = 'qr';
-  reportPreviewProps.title = '扫一扫 获取报告';
-  reportPreviewProps.url = url;
-  reportPreviewProps.show = true;
+
+  try {
+    // @ts-ignore
+    window.AIO.print(url);
+    Toast.success(`开始打印`);
+  } catch ( e ) {
+    Notify.warning(`打印失败 (${ e.message })`, { duration: 1500 });
+    setTimeout(() => {
+      ReportPreview = defineAsyncComponent(() => import('./ReportPreview.vue'));
+      reportPreviewProps.mode = 'qr';
+      reportPreviewProps.title = '扫一扫 获取报告';
+      reportPreviewProps.url = url;
+      reportPreviewProps.show = true;
+    }, 1500);
+  }
 }
 
 async function miniProgram() {
@@ -61,20 +76,27 @@ async function miniProgram() {
   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,
-
-  show: false,
 });
 const panelOpen = (min?: number) => {
-  if (panelProps.show && min) panelProps.anchors[0] = min;
+  if ( min ) panelProps.anchors[ 0 ] = min;
   panelHeight.value = panelProps.anchors[1];
 };
 
-const scrollable = computed(() => panelHeight.value < panelProps.anchors[1] || panelHeight.value === 0);
+const scrollable = computed(() => !data.value.payLock &&
+                                  panelHeight.value < panelProps.anchors[ 1 ] || panelHeight.value === 0,
+);
 </script>
 <template>
   <div class="report-wrapper">
@@ -184,9 +206,22 @@ const scrollable = computed(() => panelHeight.value < panelProps.anchors[1] || p
           </div>
         </div>
       </van-skeleton>
-      <NavBar class="flex-none" :uploading @print="print" @mini="miniProgram"></NavBar>
+      <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="调理方案">
+          <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>
+        <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>
       <Component :is="ReportPreview" v-bind="reportPreviewProps" v-model:show="reportPreviewProps.show"></Component>
-
       <van-floating-panel v-model:height="panelHeight" v-bind="panelProps">
         <Transition>
           <div class="panel-content">
@@ -245,4 +280,13 @@ const scrollable = computed(() => panelHeight.value < panelProps.anchors[1] || p
   border-radius: 24px;
   box-shadow: inset 0 0 80px 0 #34a76b60;
 }
-</style>
+
+.nav-wrapper {
+  img {
+    margin: auto;
+    width: 36px;
+    height: 36px;
+    object-fit: scale-down;
+  }
+}
+</style>

+ 16 - 4
src/modules/report/scheme.page.vue

@@ -1,11 +1,11 @@
 <script setup lang="ts">
-import NavBar                    from '@/modules/report/NavBar.vue';
+import NavHomeSelect from '@/assets/images/nav-home.select.png?url';
+import NavScheme     from '@/assets/images/nav-scheme.png?url';
 import SchemeMedia               from '@/modules/report/SchemeMedia.vue';
 import { getReportSchemeMethod } from '@/request/api/report.api';
 import { useRouteParams }        from '@vueuse/router';
 import { useWatcher }            from 'alova/client';
-
-import NavHomeSelect from '@/assets/images/nav-home.select.png?url';
+import { useRouter } from 'vue-router';
 
 
 const id = useRouteParams<string>('id');
@@ -15,6 +15,13 @@ const { data, loading } = useWatcher(() => getReportSchemeMethod(id.value), [ id
   },
   immediate: true,
 });
+
+const router = useRouter();
+
+function toggle() {
+  const path = router.currentRoute.value.fullPath.replace('/scheme', '');
+  router.replace({ path });
+}
 </script>
 <template>
   <div>
@@ -51,7 +58,12 @@ const { data, loading } = useWatcher(() => getReportSchemeMethod(id.value), [ id
           </div>
         </div>
       </van-skeleton>
-      <NavBar class="flex-none"></NavBar>
+      <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="mt-2">健康报告</div>
+        </div>
+      </div>
     </div>
   </div>
 </template>

+ 29 - 185
src/pages/register.page.vue

@@ -1,165 +1,35 @@
 <script setup lang="ts">
+import NavHomeSelect from '@/assets/images/nav-home.select.png?url';
 import { Notify, Toast } from '@/platform';
 
 import {
-  type FieldKey,
   getCaptchaMethod,
   processMethod,
   registerAccountMethod,
   registerFieldsMethod,
-  scanAccountMethod, searchAccountMethod,
-} from '@/request/api';
-
-import { useVisitor } from '@/stores';
-import { useRouteQuery } from '@vueuse/router';
-
-import { useCaptcha, useForm, useRequest, useWatcher } from 'alova/client';
+  searchAccountMethod,
+}                                     from '@/request/api';
+import type { Fields, RegisterModel } from '@/request/model';
+import { useRouteQuery }              from '@vueuse/router';
 
-import type { FieldRule, FormInstance, NumberKeyboardProps, PasswordInputProps } from 'vant';
-import { RadioGroup as vanRadioGroup }                                           from 'vant';
+import { useCaptcha, useRequest, useSerialRequest } from 'alova/client';
 
-import NavHomeSelect from '@/assets/images/nav-home.select.png?url';
+import type { FormInstance }           from 'vant';
+import { RadioGroup as vanRadioGroup } from 'vant';
 
-interface Field {
-  control: {
-    label: string; placeholder?: string;
-    type?: string; min?: number; max?: number; minlength?: number; maxlength?: number;
-    clearable?: boolean; border?: boolean; readonly?:boolean;
-  };
-  component?: |
-    { name: 'radio', options: { label: string; value: string; }[] } |
-    { name: 'code', props?: Partial<PasswordInputProps> };
-  keyboard?: { show: boolean; } & Partial<NumberKeyboardProps>;
-  suffix?: string;
-  rules?: FieldRule[];
-}
 
-const Fields: Record<FieldKey, Field> = {
-  height: {
-    control: {
-      label: '身高', placeholder: '请输入身高',
-      type: 'number', min: 1, max: 300, clearable: true, readonly: true,
-      maxlength: 5,
-    },
-    keyboard: { show: false, title: '身高', extraKey:'.', closeButtonText: '完成' },
-    suffix: 'cm',
-  },
-  weight: {
-    control: {
-      label: '体重', placeholder: '请输入体重',
-      type: 'number', min: 1, max: 300, clearable: true, readonly: true,
-      maxlength: 5,
-    },
-    keyboard: { show: false, title: '体重', extraKey:'.', closeButtonText: '完成' },
-    suffix: 'kg',
-  },
-  age: {
-    control: {
-      label: '年龄', placeholder: '请输入年龄',
-      type: 'digit', min: 0, max: 300, clearable: true, readonly: true,
-      maxlength: 3,
-    },
-    keyboard: { show: false, title: '年龄', closeButtonText: '完成' },
-    suffix: '岁',
-  },
-  sex: {
-    control: { label: '性别', border: false },
-    component: {
-      name: 'radio' as const,
-      options: [
-        { label: '男', value: '0' },
-        { label: '女', value: '1' },
-      ],
-    },
-  },
-  isEasyAllergy: {
-    control: { label: '容易过敏', border: false },
-    component: {
-      name: 'radio' as const,
-      options: [
-        { label: '是', value: 'Y' },
-        { label: '否', value: 'N' },
-      ],
-    },
-  },
-  name: {
-    control: {
-      label: '姓名', placeholder: '请输入姓名',
-      type: 'text', maxlength: 10, clearable: true,
-    },
-  },
-  cardno: {
-    control: {
-      label: '身份证号', placeholder: '请输入身份证号',
-      type: 'text', maxlength: 18, minlength: 18, clearable: true, readonly: true,
-    },
-    keyboard: { show: false, title: '身份证号', extraKey: 'X', closeButtonText: '完成' },
-    rules: [
-      { required: true, message: '请输入身份证号' },
-      {
-        validator: (value: string) => value && value.length === 18,
-        message: '请输入正确的身份证',
-        trigger: 'onBlur',
-      },
-    ],
-  },
-  phone: {
-    control: {
-      label: '手机号码', placeholder: '请输入手机号码',
-      type: 'tel', maxlength: 11, minlength: 11, clearable: true, readonly: true,
-    },
-    keyboard: { show: false, title: '手机号码', closeButtonText: '完成' },
-    rules: [
-      { required: true, message: '请输入手机号码' },
-      {
-        validator: (value: string) => value && value.length === 11,
-        message: '请输入正确的手机号码',
-        trigger: 'onBlur',
-      },
-    ],
-  },
-  code: {
-    control: {
-      label: '验证码', placeholder: '请输入验证码',
-      type: 'digit', maxlength: 6, minlength: 6, clearable: true,
-      border: false,
-    },
-    component: {
-      name: 'code' as const,
-      props: { mask: false },
-    },
-    keyboard: { show: false, title: '验证码', closeButtonText: '完成' },
-    rules: [
-      { required: true, message: '请输入验证码' },
-      {
-        validator: (value: string) => value && value.length === 6,
-        message: '请输入验证码',
-        trigger: [ 'onChange', 'onBlur' ],
-      },
-    ],
-  },
-};
+const { data: fields, loading } = useRequest(registerFieldsMethod);
 
-
-const fields = ref<( Field & { name: FieldKey } )[]>([]);
-const { loading } = useRequest(registerFieldsMethod).onSuccess(({ data }) => {
-  fields.value = data.map(name => {return { ...Fields[ name ], name };});
-});
-
-const Visitor = useVisitor();
 const formRef = useTemplateRef<FormInstance>('register-form');
-const { form: modelRef, loading: submitting, send: submit } = useForm(data => registerAccountMethod(data), {
-  initialForm: { code: '' } as Record<string, any>,
-}).onSuccess(async ({ data }) => {
-  Visitor.patientId = data;
-  Toast.success(`操作成功`);
-  try {
-    submitting.value = true;
-    await handle();
-  } finally {
-    submitting.value = false;
-  }
-});
+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: searching, send: search } = useRequest((data) => searchAccountMethod(data), {
   immediate: false,
@@ -195,7 +65,7 @@ const searchHandle = async (key: 'cardno' | 'code') => {
   }
 };
 
-function onKeyboardBlur(field: Field & { name: FieldKey }) {
+function onKeyboardBlur(field: Fields[number]) {
   if ( field?.name === 'phone' && !captchaLoaded ) { getCaptchaHandle(); }
   if ( field?.name === 'cardno' ) { searchHandle('cardno'); }
   if ( field?.name === 'code' ) { searchHandle('code'); }
@@ -205,47 +75,21 @@ function onSubmitHandle() {
   submit(toValue(modelRef));
 }
 
-const router = useRouter();
-const { send: handle } = useRequest(
-  () => processMethod('/register'),
-  { immediate: false },
-).onSuccess(
-  ({ data }) => {
-    if ( data ) {
-      router.replace(data);
-    } else {
-      Notify.warning(`[路由] 配置异常无法解析正确路径,请联系管理员`);
-    }
-  });
-
-function fix(key: FieldKey) {
+function fix(key: string) {
   for ( const field of fields.value ) {
     if (field.keyboard?.show && field.name !== key ) field.keyboard.show = false;
   }
 }
 
 const scan = useRouteQuery<string>('scan');
-useWatcher(
-  () => scanAccountMethod(sessionStorage.getItem(`scan_${ scan.value }`)!),
-  [ scan ],
-  {
-    immediate: true,
-    async middleware(_, next) {
-      if ( scan.value ) {
-        let scanToastRef: any;
-        try {
-          scanToastRef = Toast.loading(100, { message: '加载中' });
-          await next();
-        } catch ( error ) {} finally {
-          scanToastRef?.close?.();
-        }
-      }
-    },
-  },
-).onSuccess(({ data }) => {
-  Toast.success('扫码成功');
-  modelRef.value = { ...modelRef.value, ...data };
-});
+watch(scan, key => {
+  if ( key ) {
+    try {
+      const { model } = JSON.parse(sessionStorage.getItem(`scan_${ key }`) ?? '');
+      modelRef.value = { ...modelRef.value, ...model };
+    } catch ( e: any ) {}
+  }
+}, { immediate: true });
 
 onBeforeUnmount(() => {
   for ( let i = 0; i < sessionStorage.length; i++ ) {
@@ -319,7 +163,7 @@ onBeforeUnmount(() => {
       </van-form>
       <div class="m-4">
         <div class="m-auto size-16 cursor-pointer">
-          <van-loading v-if="submitting" type="spinner" size="64" color="#38ff6e" />
+          <van-loading v-if="submitting || loading" type="spinner" size="64" color="#38ff6e" />
           <img v-else class="size-full"
                src="@/assets/images/next-step.svg" alt="提交" @click="formRef?.submit()"
           >

+ 36 - 16
src/pages/screen.page.vue

@@ -1,26 +1,41 @@
 <script setup lang="ts">
-import { Notify }                         from '@/platform';
-import { copyrightMethod, processMethod } from '@/request/api';
-import { useVisitor }                     from '@/stores';
-import getBubbles                         from '@/tools/bubble';
-import { useElementSize }                 from '@vueuse/core';
-import { useRequest }                     from 'alova/client';
-import p5                                 from 'p5';
+import { Dialog, Notify } from '@/platform';
+
+import { copyrightMethod, processMethod, registerVisitorMethod } from '@/request/api';
+
+import { useVisitor }     from '@/stores';
+import getBubbles         from '@/tools/bubble';
+import { useElementSize } from '@vueuse/core';
+import { useRequest }     from 'alova/client';
+import p5                 from 'p5';
 
 
 const router = useRouter();
 const Visitor = useVisitor();
 
 const title = import.meta.env.SIX_APP_TITLE;
-const { data: copyright } = useRequest(copyrightMethod);
-const { send: handle, loading } = useRequest(processMethod, { immediate: false }).onSuccess(({ data }) => {
-  if ( data ) {
-    Visitor.$reset();
-    router.push({ path: data, replace: true }).then();
-  } else {
-    Notify.warning(`[路由] 配置异常无法解析正确路径,请联系管理员`);
-  }
+const { data: visitor, loading: registering, send: register } = useRequest(registerVisitorMethod, { immediate: false, });
+const { data: copyright, send: load } = useRequest(copyrightMethod).onError(async ({ error }) => {
+  await Dialog.show({
+    message: error.message,
+    theme: 'round-button',
+    showCancelButton: false,
+    confirmButtonText: '刷新',
+    width: '350px',
+  });
+  await load();
 });
+const { send: handle, loading } = useRequest(processMethod, { immediate: false })
+  .onSuccess(({ data }) => {
+    router.push({ path: data, replace: true }).then(
+      () => {
+        Visitor.$reset();
+        if (visitor.value) Visitor.patientId = visitor.value;
+      },
+      () => {}
+    );
+  })
+  .onError(({ error }) => Notify.warning(error.message));
 
 const container = useTemplateRef<HTMLDivElement>('container');
 const { width, height } = useElementSize(container);
@@ -163,6 +178,11 @@ function init({ width, height, container }: { width: number; height: number; con
     };
   }, container);
 }
+
+onBeforeRouteLeave((to, from) => {
+  if (to.path === '/register') return true;
+  return register().then((data) => !!data, () => false);
+});
 </script>
 <template>
   <div class="wrapper">
@@ -173,7 +193,7 @@ function init({ width, height, container }: { width: number; height: number; con
       </div>
       <div class="flex-auto flex flex-col">
         <div class="flex-auto flex justify-center items-center">
-          <van-button class="decorate" :loading @click="handle()">开始检测</van-button>
+          <van-button class="decorate" :loading="loading || registering" @click="handle()">开始检测</van-button>
         </div>
         <div class="flex-none text-xl p-8 text-center" v-html="copyright"></div>
       </div>

+ 32 - 14
src/request/api/account.api.ts

@@ -1,9 +1,8 @@
-import { cacheFor }           from '@/request/api/index';
-import type { RegisterModel } from '@/request/model';
-import HTTP                   from '../alova';
+import { cacheFor }                                                     from '@/request/api/index';
+import { type Fields, fromRegisterFields, getPath, type RegisterModel } from '@/request/model';
+import { useVisitor } from '@/stores';
 
-
-export type FieldKey = 'cardno' | 'phone' | 'code' | 'name' | 'sex' | 'height' | 'weight' | 'age' | 'isEasyAllergy'
+import HTTP from '../alova';
 
 
 export function getCaptchaMethod(mobile: string) {
@@ -13,20 +12,31 @@ export function getCaptchaMethod(mobile: string) {
 }
 
 export function registerFieldsMethod() {
-  return HTTP.Post<FieldKey[], { tabletFileFields: FieldKey[] }>(`/fdhb-tablet/warrantManage/getPageSets`, {}, {
-    cacheFor,
+  return HTTP.Post<Fields, { tabletFileFields: string[] }>(`/fdhb-tablet/warrantManage/getPageSets`, void 0, {
+    cacheFor, name: `variate:register_fields`,
+    params: { k: 'register_fields' },
     transform(data, headers) {
-      // 修正 phone,code
-      const keys = data?.tabletFileFields?.join(',').replace(`phone`, 'phone,code') ?? '';
-      const replace = (key: string) => key.replace(/_([a-z])/g, (match, letter) => letter.toUpperCase());
-      return keys.split(',').filter(Boolean).map(replace) as FieldKey[] ?? [];
+      const options = data?.tabletFileFields ?? [];
+      return fromRegisterFields(options);
     },
   });
 }
 
+export function registerVisitorMethod() {
+  return HTTP.Get<string, string>(`/fdhb-tablet/patientInfoManage/createTourist`, {
+    name: 'register',
+    cacheFor: null,
+  })
+}
+
 export function registerAccountMethod(params: Partial<RegisterModel>) {
   return HTTP.Post<string, string>(`/fdhb-tablet/patientInfoManage/savePatientInfo`, params, {
     name: 'register',
+    transform(data) {
+      const Visitor = useVisitor();
+      Visitor.patientId = data;
+      return data;
+    },
   });
 }
 
@@ -40,12 +50,20 @@ export function searchAccountMethod(params: Partial<RegisterModel>) {
   });
 }
 
-export function scanAccountMethod(value: string) {
+export function scanAccountMethod(key: string) {
   return HTTP.Get(`/fdhb-tablet/patientInfoManage/getPatInfoByScanCode`, {
     hitSource: 'register',
-    params: { scanCode: value },
+    params: { scanCode: key },
     transform(data: Record<string, any>, headers) {
-      return Object.fromEntries(Object.entries(data).filter(([ item, value ]) => !!value)) as Partial<RegisterModel>;
+      const { patientId, processModule, ..._data } = data;
+      if ( patientId ) {
+        const Visitor = useVisitor();
+        Visitor.patientId = data.patientId;
+      }
+      return {
+        key, patientId, path: getPath(processModule),
+        model: Object.fromEntries(Object.entries(_data).filter(([ item, value ]) => !!value)) as Partial<RegisterModel>,
+      };
     },
   });
 }

+ 0 - 29
src/request/api/analysis.api.ts

@@ -1,29 +0,0 @@
-import { useVisitor } from '@/stores';
-import HTTP from '@/request/alova';
-import { fromFaceAnalysisModel, fromTongueAnalysisModel } from '@/request/model';
-
-const Visitor = useVisitor();
-
-export function analysisResultsMethod(visitor = Visitor) {
-  return HTTP.Post(
-    `/fdhb-tablet/dialogueManage/dialog/${Visitor.patientId}/${Visitor.resultId}`,
-    { asyncTongueResult: false, questions: [] },
-    {
-      meta: { ignoreException: true },
-      transform(data: Record<string, any>, headers) {
-        data = data.nextQuestions?.find((item: any) => item.classify === 'tongue_result');
-        if (data) {
-          return {
-            date: data?.tonguefaceAnalysisReportDate,
-            miniProgramURL: data?.tonguefaceAnalysisReportAppletImg,
-            tongue: fromTongueAnalysisModel(data),
-            face: fromFaceAnalysisModel(data),
-
-            payLock: data?.payLock ?? true
-          };
-        }
-        throw { message: `[分析结果] 照片不符合检测要求,图片不是舌头(请拍摄带有舌头的、清晰的彩色照!)` };
-      },
-    }
-  );
-}

+ 2 - 5
src/request/api/camera.api.ts

@@ -12,13 +12,10 @@ export function saveFileMethod(params: Record<string, string>) {
   return HTTP.Get(`/fdhb-tablet/patientInfoManage/saveTonguefaceImg`, {
     params,
     async transform(data: string, headers) {
-      const path = await processMethod(`/camera`);
+      const path = await processMethod();
       return {
         resultId: data,
-        route: path ? { path } : {
-          path: '/camera/result',
-          query: { to: `/screen` },
-        },
+        route: { path },
       };
     },
   });

+ 22 - 26
src/request/api/flow.api.ts

@@ -1,37 +1,33 @@
-import { cacheFor } from '@/request/api/index';
-import HTTP         from '../alova';
+import { cacheFor }     from '@/request/api/index';
+import { fromFlowData } from '@/request/model';
+import Router           from '@/router';
 
+import HTTP from '../alova';
 
-export function copyrightMethod(value = '/copyright') {
-  return HTTP.Post(`/fdhb-tablet/warrantManage/getPageSets`, {}, {
-    cacheFor,
-    params: { t: 'process', k: value },
+
+export function copyrightMethod() {
+  return HTTP.Post(`/fdhb-tablet/warrantManage/getPageSets`, void 0, {
+    cacheFor, name: `variate:copyright`,
+    params: { k: 'copyright' },
+    meta: { ignoreException: true },
     transform(data: any, headers) {
+      if ( !data ) { throw { message: `无效的证书` }; }
       return [ data?.partner, data?.technicalSupporter ].filter(Boolean).join('<br>');
     },
   });
 }
 
-export function processMethod(value = '/screen') {
-  const routes: Record<string, string> = {
-    'patient_file': '/register',
-    'tongueface_upload': '/camera',
-    'tongueface_analysis': '/questionnaire',
-    'health_analysis': '/report',
-  };
-  return HTTP.Post(`/fdhb-tablet/warrantManage/getPageSets`, {}, {
-    cacheFor,
-    params: { t: 'process', k: value },
-    transform(data: any, headers) {
-      const options = data?.tabletProcessModules ?? [];
-      const ref = new Map<string, string>();
-      for ( let i = 0; i < options.length; i++ ) {
-        const route = routes[ options[ i ] ];
-        if ( !route ) continue;
-        if ( !i ) ref.set('/screen', route);
-        ref.set(route, routes[ options[ i + 1 ] ] ?? '');
-      }
-      return ref.get(value);
+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 route = ref.get(path);
+      if ( !route ) throw { message: `[路由] 配置异常无法解析正确路径,请联系管理员 (${ data?.tabletProcessModules?.join(' -> ') }})` };
+      return route;
     },
   });
 }

+ 2 - 1
src/request/api/index.ts

@@ -1,6 +1,7 @@
 export * from './account.api';
 export * from './flow.api';
 export * from './questionnaire.api';
+export * from './report.api';
 
 
-export const cacheFor = 60 * 60 * 1000;
+export const cacheFor = 24 * 60 * 60 * 1000;

+ 2 - 2
src/request/api/questionnaire.api.ts

@@ -1,13 +1,13 @@
-import { useVisitor }                                 from '@/stores';
+import { useVisitor } from '@/stores';
 import HTTP                                           from '../alova';
 import type { QuestionnaireStorage }                  from '../model';
 import { fromQuestionnaireData, toQuestionnaireData } from '../model';
 
 
-const Visitor = useVisitor();
 let storage: Pick<QuestionnaireStorage, 'dialogId'> & { questions: QuestionnaireStorage['questions'][] } = { questions: [] };
 
 export function questionnaireMethod(data = []) {
+  const Visitor = useVisitor();
   if ( !data?.length ) { storage = { questions: [] }; }
   return HTTP.Post(
     `/fdhb-tablet/dialogueManage/dialog/${ Visitor.patientId }/${ Visitor.resultId }`,

+ 56 - 5
src/request/api/report.api.ts

@@ -1,18 +1,52 @@
-import HTTP                                  from '../alova';
-import { fromReportData, fromSchemeRequest } from '../model';
-import { useVisitor } from '@/stores';
+import { cacheFor }                                                                          from '@/request/api/index';
+import { useVisitor }                                                                        from '@/stores';
+import HTTP                                                                                  from '../alova';
+import { fromFaceAnalysisModel, fromReportData, fromSchemeRequest, fromTongueAnalysisModel } from '../model';
 
-const Visitor = useVisitor();
+
+export function getAnalysisResultsMethod() {
+  const Visitor = useVisitor();
+  return HTTP.Post(
+    `/fdhb-tablet/dialogueManage/dialog/${ Visitor.patientId }/${ Visitor.resultId }`,
+    { asyncTongueResult: false, questions: [] },
+    {
+      meta: { ignoreException: true },
+      async transform(data: Record<string, any>, headers) {
+        data = data.nextQuestions?.find((item: any) => item.classify === 'tongue_result');
+        if ( data ) {
+          const { show, force } = await miniProgramMethod();
+          return {
+            date: data?.tonguefaceAnalysisReportDate,
+            miniProgramURL: show ? data?.tonguefaceAnalysisReportAppletImg : void 0,
+            tongue: fromTongueAnalysisModel(data),
+            face: fromFaceAnalysisModel(data),
+
+            payLock: show && force,
+          };
+        }
+        throw { message: `[分析结果] 照片不符合检测要求,图片不是舌头(请拍摄带有舌头的、清晰的彩色照!)` };
+      },
+    },
+  );
+}
 
 export function getReportMethod(id: string) {
+  const Visitor = useVisitor();
   const params = { healthAnalysisReportId: id, patientId: Visitor.patientId };
   return HTTP.Get(`/fdhb-tablet/analysisManage/getHealRepDetailById`, {
     params,
-    transform(data, headers) { return fromReportData(<any> data); },
+    async transform(data, headers) {
+      const report = fromReportData(<any> data);
+      const { show, force } = await miniProgramMethod();
+      if ( !show ) { report.miniProgramURL = void 0; }
+      report.payLock = show && force;
+      return report;
+    },
   });
 }
 
 export function updateReportMethod(id: string, data: Record<string, any>) {
+  const Visitor = useVisitor();
   const params = {
     healthAnalysisReportId: id,
     constitutionGroupImg: data?.constitutionGroupImg,
@@ -23,6 +57,7 @@ export function updateReportMethod(id: string, data: Record<string, any>) {
 }
 
 export function getReportSchemeMethod(id: string) {
+  const Visitor = useVisitor();
   const params = { healthAnalysisReportId: id, patientId: Visitor.patientId };
   return HTTP.Get(`/fdhb-tablet/analysisManage/getCondProgDetailById`, {
     params,
@@ -31,3 +66,19 @@ export function getReportSchemeMethod(id: string) {
     },
   });
 }
+
+
+export function miniProgramMethod() {
+  return HTTP.Post<{ show: boolean; force: boolean; }, { tabletRequiredPageOperationElements: string[] }>(
+    `/fdhb-tablet/warrantManage/getPageSets`, void 0, {
+      cacheFor, name: `variate:mini_program`,
+      params: { k: 'mini_program' },
+      transform(data, headers) {
+        return {
+          show: data?.tabletRequiredPageOperationElements?.includes('health_analysis_report_page_appletbutton'),
+          force: data?.tabletRequiredPageOperationElements?.includes('health_analysis_report_page_appletscan'),
+        };
+      },
+    });
+}
+

+ 1 - 1
src/request/model/analysis.model.ts

@@ -85,7 +85,7 @@ function fromAnalysisException(exception: AnalysisException[], $title = (label:
         let title: string = item?.actualValue ?? '';
         const suffix = item?.contrast ?? 's';
         if (suffix !== 's') {
-          title += ` (${suffix || ''}) `;
+          if (suffix !== 'r') title += ` (${suffix || ''}) `;
           is = true;
           exception.push({
             title: $title(label, title),

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

@@ -0,0 +1,34 @@
+const Routes = {
+  'patient_file': /* 建档页 */ '/register',
+  'tongueface_upload': /*拍照页*/ '/camera',
+  'tongueface_upload_result': /* [虚拟] 拍照结果页 */ '/camera/result',
+  'tongueface_analysis': /* 问卷页 */ '/questionnaire',
+  'tongueface_analysis_result': /* 舌面象分析报告页 */ '/report/analysis',
+  'health_analysis': /* 健康报告页 */ '/report',
+
+  'screen': '/screen',
+} as const;
+
+export function fromFlowData(options: string[]): Map<string, string> {
+  const ref = new Map<string, string>();
+  const length = options.length;
+
+  // 修正虚拟路由
+  const k1 = 'tongueface_upload';
+  const k2 = 'tongueface_upload_result';
+  if ( !options.includes(k2) && options[ length - 1 ] === k1 ) options.push(k2);
+
+  options.unshift('/screen');
+  options.push('screen');
+
+  for ( let i = 1; i < options.length; i++ ) {
+    const route = options[ i ] = getPath(options[ i ]);
+    ref.set(options[ i - 1 ], route);
+  }
+  return ref;
+}
+
+
+export function getPath(value?: string) {
+  return Routes[ value as keyof typeof Routes ];
+}

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

@@ -1,3 +1,4 @@
+export * from './flow.model';
 export * from './register.model';
 export * from './analysis.model';
 export * from './questionnaire.model';

+ 154 - 0
src/request/model/register.model.ts

@@ -1,10 +1,164 @@
+import { toCamelCase } from '@/tools';
+
+import type { FieldRule, NumberKeyboardProps, PasswordInputProps } from 'vant';
+
+
 export interface RegisterModel {
   cardno: string;
   phone: string;
   code: string;
   name: string;
   sex: string;
+  age: number;
   height: number;
   weight: number;
   isEasyAllergy: boolean;
 }
+
+export interface Field {
+  control: {
+    label: string; placeholder?: string;
+    type?: string; min?: number; max?: number; minlength?: number; maxlength?: number;
+    clearable?: boolean; border?: boolean; readonly?: boolean;
+  };
+  component?: |
+    { name: 'radio', options: { label: string; value: string; }[] } |
+    { name: 'code', props?: Partial<PasswordInputProps> };
+  keyboard?: { show: boolean; } & Partial<NumberKeyboardProps>;
+  suffix?: string;
+  rules: FieldRule[];
+}
+
+export type FieldKey = keyof RegisterModel;
+export type Fields = ( Field & { name: FieldKey } )[];
+
+const Fields: Record<FieldKey, Field> = {
+  height: {
+    control: {
+      label: '身高', placeholder: '请输入身高',
+      type: 'number', min: 1, max: 300, clearable: true, readonly: true,
+      maxlength: 5,
+    },
+    keyboard: { show: false, title: '身高', extraKey: '.', closeButtonText: '完成' },
+    suffix: 'cm',
+    rules: [],
+  },
+  weight: {
+    control: {
+      label: '体重', placeholder: '请输入体重',
+      type: 'number', min: 1, max: 300, clearable: true, readonly: true,
+      maxlength: 5,
+    },
+    keyboard: { show: false, title: '体重', extraKey: '.', closeButtonText: '完成' },
+    suffix: 'kg',
+    rules: [],
+  },
+  age: {
+    control: {
+      label: '年龄', placeholder: '请输入年龄',
+      type: 'digit', min: 0, max: 300, clearable: true, readonly: true,
+      maxlength: 3,
+    },
+    keyboard: { show: false, title: '年龄', closeButtonText: '完成' },
+    suffix: '岁',
+    rules: [],
+  },
+  sex: {
+    control: { label: '性别', border: false },
+    component: {
+      name: 'radio' as const,
+      options: [
+        { label: '男', value: '0' },
+        { label: '女', value: '1' },
+      ],
+    },
+    rules: [],
+  },
+  isEasyAllergy: {
+    control: { label: '容易过敏', border: false },
+    component: {
+      name: 'radio' as const,
+      options: [
+        { label: '是', value: 'Y' },
+        { label: '否', value: 'N' },
+      ],
+    },
+    rules: [],
+  },
+  name: {
+    control: {
+      label: '姓名', placeholder: '请输入姓名',
+      type: 'text', maxlength: 10, clearable: true,
+    },
+    rules: [],
+  },
+  cardno: {
+    control: {
+      label: '身份证号', placeholder: '请输入身份证号',
+      type: 'text', maxlength: 18, minlength: 18, clearable: true, readonly: true,
+    },
+    keyboard: { show: false, title: '身份证号', extraKey: 'X', closeButtonText: '完成' },
+    rules: [
+      {
+        validator: (value: string) => value && value.length === 18,
+        message: '请输入正确的身份证',
+        trigger: 'onBlur',
+      },
+    ],
+  },
+  phone: {
+    control: {
+      label: '手机号码', placeholder: '请输入手机号码',
+      type: 'tel', maxlength: 11, minlength: 11, clearable: true, readonly: true,
+    },
+    keyboard: { show: false, title: '手机号码', closeButtonText: '完成' },
+    rules: [
+      {
+        validator: (value: string) => value && value.length === 11,
+        message: '请输入正确的手机号码',
+        trigger: 'onBlur',
+      },
+    ],
+  },
+  code: {
+    control: {
+      label: '验证码', placeholder: '请输入验证码',
+      type: 'digit', maxlength: 6, minlength: 6, clearable: true,
+      border: false,
+    },
+    component: {
+      name: 'code' as const,
+      props: { mask: false },
+    },
+    keyboard: { show: false, title: '验证码', closeButtonText: '完成' },
+    rules: [
+      {
+        validator: (value: string) => value && value.length === 6,
+        message: '请输入验证码',
+        trigger: [ 'onChange', 'onBlur' ],
+      },
+    ],
+  },
+};
+
+export function fromRegisterFields(options: string[]): Fields {
+  // 修正 phone,code
+  const k1 = 'phone';
+  const k2 = 'code';
+  const index = options.findIndex(key => key.startsWith(k1));
+  if ( index !== -1 && !options.find(key => key.startsWith(k2)) ) {
+    const value = options[ index ].replace(k1, k2);
+    options.splice(index + 1, 0, value);
+  }
+
+  return options.map(option => {
+    const values = option.split(':');
+    const name = toCamelCase(values[ 0 ]);
+    const field = Fields[ name as unknown as FieldKey ] ?? {
+      control: { label: name, type: 'text' },
+      rules: [],
+    };
+    if ( values[ 1 ] === 'required' ) field.rules.push({ required: true, message: field.control.placeholder ?? '请补充完整' });
+    return { ...field, name } as Fields[number];
+  });
+}

+ 1 - 25
src/request/model/report.model.ts

@@ -31,29 +31,6 @@ export function fromReportData(data: Record<string, any>) {
     tongue: fromTongueAnalysisModel(data),
     face: fromFaceAnalysisModel(data),
 
-
-    tongueTable: {
-      column: [ '舌象维度', '检测结果', '标准值' ],
-      data: [
-        [ '舌色', fromTongueException(data?.tongueColor), data?.tongueColor?.standardValue ],
-        [ '苔色', fromTongueException(data?.tongueCoatingColor), data?.tongueCoatingColor?.standardValue ],
-        [ '舌形', fromTongueException(data?.tongueShape), data?.tongueShape?.standardValue ],
-        [ '苔质', fromTongueException(data?.tongueCoating), data?.tongueCoating?.standardValue ],
-        [ '津液', fromTongueException(data?.bodyFluid), data?.bodyFluid?.standardValue ],
-        [ '舌下', fromTongueException(data?.sublingualVein), data?.sublingualVein?.standardValue ],
-      ],
-    },
-    tongueException,
-    tongueAnalysis: {
-      [ '结果' ]: data?.tongueAnalysisResult,
-      [ '舌上' ]: data?.upImg,
-      [ '舌下' ]: data?.downImg,
-    },
-    faceAnalysis: {
-      [ '结果' ]: data?.faceAnalysisResult,
-      [ '面象' ]: data?.faceImg,
-    },
-
     [ '中医证素' ]: data?.factorItems?.map?.((item: Record<string, any>) => {
       return { label: item?.factorItemName, value: item?.factorItemDescription, score: +item?.score };
     }),
@@ -65,8 +42,7 @@ export function fromReportData(data: Record<string, any>) {
     factorItemRadarImg: data?.factorItemRadarImg,
     reportURL: data?.reportPdfUrl,
     miniProgramURL: data?.appletImg,
-
-    payLock: data?.payLock ?? false,
+    payLock: data?.payLock,
   };
 }
 

+ 1 - 1
src/stores/platform.store.ts

@@ -4,7 +4,7 @@ import { getSerialNumberSync } from '@/platform';
 export const usePlatformStore = defineStore(
   'platform',
   () => {
-    const serialNumber = ref(getSerialNumberSync() || '45dde49f100eb0cb');
+    const serialNumber = ref(getSerialNumberSync());
     return { serialNumber };
   },
 );

+ 1 - 0
src/tools/index.ts

@@ -1,2 +1,3 @@
 export * from './url.tool';
+export * from './string.tool';
 export * from './polyfills';

+ 3 - 0
src/tools/string.tool.ts

@@ -0,0 +1,3 @@
+export function toCamelCase(value: string): string {
+  return value.replace(/_([a-z])/g, (match, $1) => $1.toUpperCase());
+}