Sfoglia il codice sorgente

优化应用配置 screen

cc12458 2 settimane fa
parent
commit
e242b5d956

+ 2 - 1
@types/loader.d.ts

@@ -1,4 +1,5 @@
 import { App } from 'vue';
+import type { ApplicationConfig } from '@/request/model';
 
 
 export {};
@@ -7,7 +8,7 @@ export {};
 declare global {
   export namespace DEV {
     export interface Loader {
-      (app: App): Promise<any>;
+      (app: App, config: ApplicationConfig): Promise<any>;
     }
   }
 }

+ 41 - 0
src/components/Start.vue

@@ -0,0 +1,41 @@
+<script setup lang="ts">
+import { useRequest } from 'alova/client';
+import { registerVisitorMethod } from '@/request/api';
+import { Notify } from '@/platform';
+import { useVisitor } from '@/stores';
+import { useRouteNext, getRoutePath } from '@/computable/useRouteNext';
+
+const router = useRouter();
+const Visitor = useVisitor();
+
+const { data: visitor, loading: registering, send: register } = useRequest(registerVisitorMethod, { immediate: false });
+const { handle, loading } = useRouteNext({
+  async onSuccess(flow) {
+    Visitor.$reset();
+    await router.push({ path: getRoutePath(flow.next), replace: true });
+    if (visitor.value) Visitor.patientId = visitor.value;
+  },
+  onError(error) {
+    Notify.warning(error.message);
+  },
+});
+
+onBeforeRouteLeave((to, from) => {
+  if (to.path === '/register' || Visitor.patientId) return true;
+  return register().then(
+    (data) => !!data,
+    () => false
+  );
+});
+</script>
+
+<template>
+  <div class="van-button decorate" @click="!(loading || registering) && handle()">
+    <div class="van-button__content">
+      <van-loading v-if="loading || registering" />
+      <span v-else class="van-button__text">开始检测</span>
+    </div>
+  </div>
+</template>
+
+<style scoped lang="scss"></style>

+ 13 - 11
src/loader/bridge.loader.ts

@@ -33,15 +33,17 @@ export default function bridgeLoader(): DEV.Loader {
     (window as any).sixWisdom.printPdfByUrl(value);
   };
 
-  return async function () {
+  return async function (app, config) {
+    const scanned = config.image.el.split('|').includes('scan');
     if (platformIsAIO_1()) window.bridge = new EventTarget() as InstanceType<typeof Bridge>;
-    if (platformIsAIO()) {
-      await waitFor(() => window.bridge != null);
-      window.bridge.addEventListener('scan', async ({ detail }) => {
-        if (detail.code !== 0 || detail.data?.code == null) return;
-        const route = unref(router.currentRoute);
-        if (route.meta?.scan) {
-          const { Toast } = await import(`@/platform/toast.ui`);
+    else if (platformIsAIO()) await waitFor(() => window.bridge != null);
+    window.bridge.addEventListener('scan', async ({ detail }) => {
+      if (detail.code !== 0 || detail.data?.code == null) return;
+      const route = unref(router.currentRoute);
+      if (route.meta?.scan) {
+        const { Toast } = await import(`@/platform/toast.ui`);
+        if (!scanned) Toast.warning(`系统禁止扫码`)
+        else {
           const toast = Toast.loading(100, { message: '加载中' });
           const data = await scanAccountMethod(detail.data.code).catch(() => {});
 
@@ -54,8 +56,8 @@ export default function bridgeLoader(): DEV.Loader {
           }
 
           toast.close();
-        } else import(`@/platform/notify.ui`).then(({ Notify }) => { Notify.warning(`请返回首页后,再进行扫码!`); });
-      });
-    }
+        }
+      } else import(`@/platform/notify.ui`).then(({ Notify }) => { Notify.warning(`请返回首页后,再进行扫码!`); });
+    });
   };
 }

+ 5 - 3
src/loader/debug.loader.ts

@@ -3,11 +3,13 @@ import { getURLSearchParams } from '@/tools';
 type Lib = 'eruda' | 'vconsole';
 
 export default function debugLoader(tag = 'debug', ignoreDevelop = true): DEV.Loader {
-  return async function () {
-    if (import.meta.env.DEV && ignoreDevelop) return;
+  return async function (app, config) {
+    const debug = config.image.el.split('|').includes('debug');
+    if (import.meta.env.DEV && ignoreDevelop && !debug) return;
 
     const query = getURLSearchParams();
-    const lib = query.get(tag) as Lib;
+    let lib = query.get(tag) as Lib;
+    if (!lib && debug) lib = 'eruda';
     switch (lib) {
       case 'eruda':
         const { default: eruda } = await import('eruda');

+ 12 - 3
src/loader/index.ts

@@ -1,12 +1,21 @@
 import type { Component } from 'vue';
 import { createApp } from 'vue';
 
+import { Toast } from '@/platform/toast.ui';
+import { getApplicationMethod } from '@/request/api';
+
 export default async function Loader(component: Component, ...loader: DEV.Loader[]) {
   const app = createApp(component);
-  for (const fn of loader) {
-    await fn(app);
-  }
+  const toast = Toast.loading(500);
 
+  try {
+    const config = await getApplicationMethod().send(true);
+    for (const fn of loader) await fn(app, config);
+  } catch (error) {
+    toast.close();
+    throw error;
+  }
+  toast.close();
   return app;
 }
 

+ 16 - 1
src/loader/launch.loader.ts

@@ -1,10 +1,25 @@
 import router from '@/router';
 import pinia from '@/stores';
 
+const components = import.meta.glob('@/pages/**/*.vue');
+
 export default function launchLoader(container = '#app'): DEV.Loader {
-  return async function (app) {
+  return async function (app, config) {
     app.use(router);
     app.use(pinia);
+
+    if (config.image.com) {
+      const component = components[`/src/pages/${config.image.com}.vue`];
+      if (!component) throw { message: `配置 ${config.image.com} 组件未找到` };
+      router.addRoute({
+        name: 'screen',
+        path: config.image.page,
+        component,
+        meta: { scan: true },
+      });
+    }
+    if (config.image.page && config.image.page !== router.currentRoute.value.fullPath) await router.replace(config.image.page);
+
     app.mount(container);
   };
 }

+ 22 - 9
src/main.ts

@@ -7,12 +7,25 @@ import './polyfill'
 
 import App from './App.vue';
 
-/* prettier-ignore */
-Loader(
-  App,
-  debugLoader('debug', !platformIsAIO()),
-  bridgeLoader(),
-  launchLoader('#app'),
-).then(
-  (app) => {},
-);
+(async function start() {
+  try {
+    /* prettier-ignore */
+    await Loader(
+      App,
+      debugLoader('debug', !platformIsAIO()),
+      bridgeLoader(),
+      launchLoader('#app'),
+    );
+  } catch (error: any) {
+    const { Dialog } = await import('@/platform/dialog.ui');
+    await Dialog.show({
+      title: '请联系管理员',
+      message: error.message,
+      theme: 'round-button',
+      showCancelButton: false,
+      confirmButtonText: '刷新',
+      overlay: false,
+    });
+    await start();
+  }
+})();

+ 19 - 15
src/pages/scan.page.vue

@@ -1,18 +1,13 @@
 <script setup lang="ts">
 import { useRequest } from 'alova/client';
-import { copyrightMethod } from '@/request/api';
-import { Dialog } from '@/platform';
-const title = import.meta.env.SIX_APP_TITLE;
-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();
-});
+import { getApplicationMethod } from '@/request/api';
+
+import Start from '@/components/Start.vue';
+
+const { data } = useRequest(getApplicationMethod, { initialData: { image: { el: '', copyright: '' } } });
+const title = computed(() => data.value.image.title || import.meta.env.SIX_APP_TITLE);
+const copyright = computed(() => data.value.copyright);
+const button = computed(() => data.value.image.el.split('|').includes('btn'));
 </script>
 
 <template>
@@ -20,13 +15,22 @@ const { data: copyright, send: load } = useRequest(copyrightMethod).onError(asyn
     <div class="flex-none h-[106px]">
       <div class="h-full flex justify-center items-center text-4xl" style="letter-spacing: 0.2em">{{ title }}</div>
     </div>
-    <div class="flex-auto"></div>
+    <div class="flex-auto relative">
+      <Start v-if="button" class="decorate" />
+    </div>
     <div class="flex-none text-xl p-2 text-center tracking-widest" v-html="copyright"></div>
   </div>
 </template>
 
 <style scoped lang="scss">
 .wrapper {
-  background: url("@/assets/images/screen.scan.png") no-repeat center / 100%;
+  background: url('@/assets/images/screen.scan.png') no-repeat center / 100%;
+}
+
+.decorate {
+  position: absolute;
+  bottom: 10px;
+  left: 50%;
+  transform: translateX(-50%);
 }
 </style>

+ 7 - 41
src/pages/screen.page.vue

@@ -1,40 +1,17 @@
 <script setup lang="ts">
-import { Dialog, Notify } from '@/platform';
+import { getApplicationMethod } from '@/request/api';
 
-import { copyrightMethod, registerVisitorMethod } from '@/request/api';
-import { useRouteNext, getRoutePath } from '@/computable/useRouteNext';
-
-import { useVisitor }     from '@/stores';
 import getBubbles         from '@/tools/bubble';
 import { useElementSize } from '@vueuse/core';
 import { useRequest }     from 'alova/client';
 import p5                 from 'p5';
 
+import Start from '@/components/Start.vue';
 
-const router = useRouter();
-const Visitor = useVisitor();
-
-const title = import.meta.env.SIX_APP_TITLE;
-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 { handle, loading } = useRouteNext({
-  async onSuccess(flow) {
-    Visitor.$reset();
-    await router.push({ path: getRoutePath(flow.next), replace: true })
-    if (visitor.value) Visitor.patientId = visitor.value;
-  },
-  onError(error) { Notify.warning(error.message); },
-});
+const { data } = useRequest(getApplicationMethod, { initialData: { image: { el: '', copyright: '' } } });
+const title = computed(() => data.value.image.title || import.meta.env.SIX_APP_TITLE);
+const copyright = computed(() => data.value.copyright);
+const button = computed(() => data.value.image.el.split('|').includes('btn'));
 
 const container = useTemplateRef<HTMLDivElement>('container');
 const { width, height } = useElementSize(container);
@@ -174,11 +151,6 @@ function init({ width, height, container }: { width: number; height: number; con
     };
   }, container);
 }
-
-onBeforeRouteLeave((to, from) => {
-  if (to.path === '/register' || Visitor.patientId) return true;
-  return register().then((data) => !!data, () => false);
-});
 </script>
 <template>
   <div class="wrapper">
@@ -189,13 +161,7 @@ onBeforeRouteLeave((to, from) => {
       </div>
       <div class="flex-auto flex flex-col">
         <div class="flex-auto flex justify-center items-center">
-          <!--<van-button class="decorate" :loading="loading || registering" @click="handle()">开始检测</van-button>-->
-          <div class="van-button decorate" @click="!(loading || registering) && handle()">
-            <div class="van-button__content">
-              <van-loading v-if="loading || registering" />
-              <span v-else class="van-button__text">开始检测</span>
-            </div>
-          </div>
+          <Start v-if="button" />
         </div>
         <div class="flex-none text-xl p-8 text-center" v-html="copyright"></div>
       </div>

+ 10 - 0
src/request/api/index.ts

@@ -1,7 +1,17 @@
+import HTTP from '@/request/alova';
+import { application } from '@/request/model';
+
 export * from './account.api';
 export * from './flow.api';
 export * from './questionnaire.api';
 export * from './report.api';
 
+export function getApplicationMethod() {
+  return HTTP.Post(`/fdhb-tablet/warrantManage/getPageSets`, void 0, {
+    cacheFor,
+    shareRequest: true,
+    transform: application,
+  });
+}
 
 export const cacheFor = 24 * 60 * 60 * 1000;

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

@@ -1,6 +1,37 @@
+import { analysis } from '@/tools/regex';
+
 export * from './flow.model';
 export * from './register.model';
 export * from './analysis.model';
 export * from './questionnaire.model';
 export * from './report.model';
 export * from './scheme.model';
+
+export function application(data: Record<string, any>) {
+  const image = analysis(
+    [
+      ['preset', '\\d+', '1'],
+      ['el', '[\\w|]+', 'scan|btn'],
+      ['page', '/\\w+', '/screen'],
+      ['com', '[^;]+'],
+      ['title', '[^;]+'],
+    ] as const,
+    data?.tabletDrainageImage
+  );
+  if (image.preset === '1') image.el ??= `scan|btn`;
+  else if (image.preset === '2') {
+    image.el ??= `scan`;
+    image.com ??= `scan.page`
+    image.title ??= `萧山区中医智能辅诊系统`
+  }
+
+  return {
+    copyright: [data?.partner, data?.technicalSupporter].filter(Boolean).join('<br>'),
+    registerFields: Array.isArray(data?.tabletFileFields) ? data.tabletFileFields : [],
+    flowConfig: Array.isArray(data?.tabletProcessModules) ? data?.tabletProcessModules : [],
+    pageElements: Array.isArray(data?.tabletRequiredPageOperationElements) ? data?.tabletRequiredPageOperationElements : [],
+    image,
+  };
+}
+
+export type ApplicationConfig = ReturnType<typeof application>;

+ 1 - 1
src/router/index.ts

@@ -4,7 +4,7 @@ import { createRouter, createWebHistory } from 'vue-router';
 const router = createRouter({
   history: createWebHistory(import.meta.env.BASE_URL),
   routes: [
-    { path: '/screen', component: () => import('@/pages/screen.page.vue'), meta: { scan: true } },
+    { path: '/screen', name: 'screen', component: () => import('@/pages/screen.page.vue'), meta: { scan: true } },
     { path: '/register', component: () => import('@/pages/register.page.vue'), meta: { title: '建档', scan: true } },
     { path: '/pulse', component: () => import('@/modules/pulse/pulse.page.vue'), meta: { title: '脉诊' } },
     { path: '/pulse/result', component: () => import('@/modules/pulse/pulse-result.page.vue'), meta: { title: '脉象分析报告' } },

+ 1 - 1
src/themes/vant.scss

@@ -15,7 +15,7 @@
 :root:root {
   --van-primary-color: #38ff6e;
   --van-background-2: #0f2925;
-
+  --van-text-color: #fff;
   --van-notify-font-size: 1.6rem;
   --van-notify-line-height: 2rem;
 

+ 31 - 0
src/tools/regex.ts

@@ -0,0 +1,31 @@
+/**
+ * 模式配置类型定义
+ */
+type PatternConfig = readonly [key: string, pattern: string, defaultValue?: string];
+
+/**
+ * 根据模式配置推断返回类型
+ */
+type InferResultType<T extends readonly PatternConfig[]> = {
+  [K in T[number] as K extends readonly [infer Key, ...any[]] ? Key : never]: K extends readonly [any, any, infer Default]
+    ? Default extends string
+      ? string
+      : string | undefined
+    : string | undefined;
+};
+
+/**
+ * 分析函数 - 根据 patterns 参数类型确定返回类型
+ * @param patterns 模式配置数组
+ * @param value 要解析的字符串
+ * @returns 解析后的对象,类型根据patterns自动推断
+ */
+export function analysis<T extends readonly PatternConfig[]>(patterns: T, value?: string): InferResultType<T> {
+  const regex = patterns.map(([key, pattern]) => `(?:${key}:(?<${key}>${pattern})[;$]?)?`).join('');
+  const groups = (value || '').match(new RegExp(`^${regex}`))?.groups ?? {};
+
+  return patterns.reduce((values, [key, _, defaultValue]) => {
+    values[key as keyof InferResultType<T>] = (groups[key] ?? defaultValue) as any;
+    return values;
+  }, {} as InferResultType<T>);
+}