Bläddra i källkod

支持浏览器版本 2.0

cc12458 1 månad sedan
förälder
incheckning
0f338bc45d
8 ändrade filer med 130 tillägg och 26 borttagningar
  1. 28 0
      @types/bridge.d.ts
  2. 24 10
      @types/global.d.ts
  3. 1 1
      src/core/launch/index.ts
  4. 25 4
      src/core/launch/platform.launch.ts
  5. 2 0
      src/main.ts
  6. 30 11
      src/pages/StepPage.vue
  7. 5 0
      src/platform/index.ts
  8. 15 0
      src/polyfill.ts

+ 28 - 0
@types/bridge.d.ts

@@ -0,0 +1,28 @@
+interface ScanData {
+  code: string;
+  state: number;
+  type: number;
+  message?: string;
+}
+
+export interface BridgeEventMap {
+  scan: CustomEvent<{code: number, data?: ScanData, message?: string}>;
+}
+
+export class Bridge extends EventTarget {
+  public static getInstance(): Bridge;
+
+  public static print(): Promise<void>;
+  public static print(params: { url?: string }): Promise<void>;
+
+  public static scan(params: { timeout?: number; signal?: AbortSignal }): Promise<ScanData>;
+
+  /**
+   * 监听扫码事件
+   * @param type 事件类型 'scan'
+   * @param listener 事件回调,参数为 ScanEvent
+   * @param options
+   */
+  addEventListener<T extends keyof BridgeEventMap>(type: T, listener: (event: BridgeEventMap[T]) => void, options?: boolean | AddEventListenerOptions): () => void;
+  removeEventListener<T extends keyof BridgeEventMap>(type: T, listener: (event: BridgeEventMap[T]) => void, options?: boolean | AddEventListenerOptions): () => void;
+}

+ 24 - 10
@types/global.d.ts

@@ -1,18 +1,32 @@
 export {};
+
 declare global {
-  export interface Platform extends EventTarget {
-    addEventListener<K extends keyof PlatformEventMap>(type: K, listener: (this: Platform, ev: PlatformEventMap[K]) => void): void;
+  declare const Bridge: typeof import('./bridge').Bridge;
 
-    removeEventListener<K extends keyof PlatformEventMap>(type: K, listener: (this: Platform, ev: PlatformEventMap[K]) => void): void;
+  interface Window {
+    /* six-pda 设备注入 */
+    bridge: InstanceType<typeof import('./bridge').Bridge>;
+    /**
+     * webview 设备注入的 全局对象(历史遗留)
+     * @deprecated 使用 bridge
+     */
+    platform: InstanceType<typeof import('./bridge').Bridge>;
   }
 
-  export interface PlatformEventMap {
-    scan: CustomEvent<{ code: string; type?: number; message?: string }>;
-  }
+  /**
+   * webview 设备注入的 全局对象(历史遗留)
+   * @deprecated 使用 bridge
+   */
+  declare const platform: InstanceType<typeof import('./bridge').Bridge>;
 
-  interface Window {
-    platform: Platform;
+  /**
+   * Promise 扩展
+   */
+  interface PromiseConstructor {
+    withResolvers<T>(): {
+      promise: Promise<T>;
+      resolve: (value: T | PromiseLike<T>) => void;
+      reject: (reason?: unknown) => void;
+    };
   }
-
-  declare const platform: Platform;
 }

+ 1 - 1
src/core/launch/index.ts

@@ -23,4 +23,4 @@ export default async function launch(component: Component, ...launcher: (Launche
 }
 
 export { debugLaunch } from './debug.launch.ts';
-export { platformLaunch } from './platform.launch.ts';
+export { default as platformLaunch } from './platform.launch.ts';

+ 25 - 4
src/core/launch/platform.launch.ts

@@ -1,7 +1,28 @@
-import type { Launcher } from '@/core/launch';
+import { platformIsPDA } from '@/platform';
+import type { Launcher } from '@/core/launch/index.ts';
 
-export function platformLaunch(): Launcher {
-  return async () => {
-    window.platform ??= new EventTarget();
+export function waitFor(condition: () => boolean | Promise<boolean>, timeout: number = 300 * 1000) {
+  const start = Date.now();
+  const { promise, resolve, reject } = Promise.withResolvers<void>();
+  const check = async () => {
+    try {
+      if (await condition()) resolve();
+      else if (timeout && Date.now() - start >= timeout) reject({ message: 'waitForBridge timeout' });
+      else requestAnimationFrame(check);
+    } catch (e) {
+      reject(e);
+    }
+  };
+  return check().then(
+    () => promise,
+    () => promise,
+  );
+}
+
+export default function bridgeLoader(): Launcher {
+  return async function () {
+    if (platformIsPDA()) {
+      await waitFor(() => window.bridge != null);
+    }
   };
 }

+ 2 - 0
src/main.ts

@@ -3,6 +3,8 @@ import { Lazyload } from 'vant';
 import 'vant/es/toast/style'
 import 'vant/es/notify/style'
 
+import './polyfill'
+
 import App from './App.vue';
 import launch, { debugLaunch, platformLaunch } from '@/core/launch';
 

+ 30 - 11
src/pages/StepPage.vue

@@ -20,18 +20,37 @@ const keyword = ref<string>(dataset.value?.no ?? '');
 
 const ignoreParentScanner = ref(false);
 provide('ignoreParentScanner', ignoreParentScanner);
-const update = (event: PlatformEventMap['scan']) => {
-  if (ignoreParentScanner.value) {
-    event.stopPropagation();
-    event.preventDefault();
-  } else {
-    keyword.value = event.detail.code;
-    const toast = showLoadingToast({ message: '查询中...', duration: 0 });
-    search().finally(() => toast.close());
+let onCleanup = () => {};
+tryOnBeforeMount(() => {
+  if (window.bridge) {
+    onCleanup = window.bridge.addEventListener('scan', event => {
+      const detail = event.detail;
+      if ( detail.code !== 0 || detail.data?.code == null ) return;
+      if (ignoreParentScanner.value) {
+        event.stopPropagation();
+        event.preventDefault();
+      } else {
+        keyword.value = event.detail.data?.code ?? '';
+        const toast = showLoadingToast({ message: '查询中...', duration: 0 });
+        search().finally(() => toast.close());
+      }
+    });
+  } else if (window.platform) {
+    const update = (event: CustomEvent) => {
+      if (ignoreParentScanner.value) {
+        event.stopPropagation();
+        event.preventDefault();
+      } else {
+        keyword.value = event.detail.code;
+        const toast = showLoadingToast({ message: '查询中...', duration: 0 });
+        search().finally(() => toast.close());
+      }
+    };
+    platform.addEventListener('scan', update)
+    onCleanup = () => { platform.removeEventListener('scan', update) }
   }
-};
-tryOnBeforeMount(() => platform?.addEventListener('scan', update));
-tryOnUnmounted(() => platform?.removeEventListener('scan', update));
+});
+tryOnUnmounted(() => onCleanup?.());
 
 const {
   data,

+ 5 - 0
src/platform/index.ts

@@ -0,0 +1,5 @@
+const userAgent = navigator.userAgent;
+
+export function platformIsPDA() {
+  return /Six\/applet \(PDA;.+\)/i.test(userAgent);
+}

+ 15 - 0
src/polyfill.ts

@@ -0,0 +1,15 @@
+if (typeof Promise.withResolvers !== 'function') {
+  Promise.withResolvers = function <T>() {
+    let resolve!: (value: T | PromiseLike<T>) => void;
+    let reject!: (reason?: any) => void;
+
+    const promise = new Promise<T>((res, rej) => {
+      resolve = res;
+      reject = rej;
+    });
+
+    return { promise, resolve, reject };
+  };
+}
+
+export {};