Browse Source

修改配置,支持MSW在浏览器环境下运行;修改注册页面过滤器区域的UI,使正常显示患者类型;修改API,修改为正确的URL path,使能够请求到患者类型

ddx 2 months ago
parent
commit
fc87d13683

+ 19 - 0
config/dev.ts

@@ -1,4 +1,5 @@
 import type { UserConfigExport } from '@tarojs/cli';
 import type { UserConfigExport } from '@tarojs/cli';
+import path from 'path';
 
 
 export default {
 export default {
   logger: {
   logger: {
@@ -18,6 +19,24 @@ export default {
         },
         },
       },
       },
       host: 'localhost', // 这里设置你想要的主机名,
       host: 'localhost', // 这里设置你想要的主机名,
+      static: {
+        directory: path.resolve(__dirname, '../public'),
+      },
+    },
+    // Use webpackChain to customize Webpack
+    // eslint-disable-next-line
+    webpackChain(chain, webpack) {
+      chain.devServer.merge({
+        setupMiddlewares: (middlewares, devServer) => {
+          devServer.app.get('/mockServiceWorker.js', (req, res) => {
+            res.set('Content-Type', 'application/javascript');
+            res.sendFile(
+              path.resolve(__dirname, '../public/mockServiceWorker.js')
+            );
+          });
+          return middlewares;
+        },
+      });
     },
     },
   },
   },
 } satisfies UserConfigExport<'webpack5'>;
 } satisfies UserConfigExport<'webpack5'>;

+ 48 - 0
mocks/handlers.js

@@ -0,0 +1,48 @@
+import { http } from 'msw';
+
+export const handlers = [
+  // Handler 1: 获取患者类型
+  http.get('/dr/api/v1/auth/protocol/patient_type', async () => {
+    // 忽略eslint
+    // eslint-disable-next-line
+    return new Response(
+      JSON.stringify({
+        code: '0x000000',
+        description: 'Success',
+        solution: '',
+        data: {
+          patient_type_list: [
+            {
+              id: '1',
+              patient_type_id: 'Human',
+              patient_type_name: 'Human',
+              patient_type_local: 'Human',
+              patient_type_description: 'Human',
+              sort: 1,
+              is_enabled: true,
+              product: 'DROC',
+              is_pre_install: true,
+            },
+            {
+              id: '2',
+              patient_type_id: 'SpecialType',
+              patient_type_name: 'SpecialType',
+              patient_type_local: 'SpecialType',
+              patient_type_description: 'SpecialType',
+              sort: 2,
+              is_enabled: false,
+              product: 'DROC',
+              is_pre_install: true,
+            },
+          ],
+        },
+      }),
+      {
+        status: 200,
+        headers: { 'Content-Type': 'application/json' },
+        // Simulate 500ms delay
+        delay: 500,
+      }
+    );
+  }),
+];

+ 61 - 0
mocks/handlers.md

@@ -0,0 +1,61 @@
+# 以下是handler描述
+
+## Handler 1: 获取患者类型
+
+### HTTP Method: GET
+
+### Headers
+
+- **Authorization**
+- **Language**: `en` 或 `zh`
+- **Product**: `DROS` 或 `VETDROS`
+- **Source**: `Electron` 或 `Browser` 或 `Android`
+
+### Endpoint URL: /dr/api/v1/auth/protocol/patient_type
+
+### Response Status: 200 (OK)
+
+### Response Data: Returns a JSON
+
+    Example:
+
+    ``` json
+    {
+    "code": "0x000000",
+    "description": "Success",
+    "solution": "",
+    "data": {
+        "patient_type_list": [
+            {
+                "id": "1",
+                "patient_type_id": "Human",
+                "patient_type_name": "Human",
+                "patient_type_local": "Human",
+                "patient_type_description": "Human",
+                "sort": 1,
+                "is_enabled": true,
+                "product": "DROC",
+                "is_pre_install": true
+            },
+            {
+                "id": "2",
+                "patient_type_id": "SpecialType",
+                "patient_type_name": "SpecialType",
+                "patient_type_local": "SpecialType",
+                "patient_type_description": "SpecialType",
+                "sort": 2,
+                "is_enabled": false,
+                "product": "DROC",
+                "is_pre_install": true
+            }
+        ]
+    }
+
+}
+```
+
+### Dynamic Behavior: None
+
+### Delay: 500ms to simulate network latency
+
+### Error Handling: None

+ 4 - 0
mocks/server.js

@@ -0,0 +1,4 @@
+import { setupWorker } from 'msw/browser';
+import { handlers } from './handlers';
+
+export const server = setupWorker(...handlers);

+ 346 - 0
package-lock.json

@@ -57,6 +57,7 @@
         "globals": "^16.2.0",
         "globals": "^16.2.0",
         "husky": "^9.1.7",
         "husky": "^9.1.7",
         "lint-staged": "^16.1.0",
         "lint-staged": "^16.1.0",
+        "msw": "^2.10.2",
         "postcss": "^8.5.4",
         "postcss": "^8.5.4",
         "postcss-loader": "^8.1.1",
         "postcss-loader": "^8.1.1",
         "prettier": "^3.5.3",
         "prettier": "^3.5.3",
@@ -1849,6 +1850,43 @@
         "node": ">=6.9.0"
         "node": ">=6.9.0"
       }
       }
     },
     },
+    "node_modules/@bundled-es-modules/cookie": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/@bundled-es-modules/cookie/-/cookie-2.0.1.tgz",
+      "integrity": "sha512-8o+5fRPLNbjbdGRRmJj3h6Hh1AQJf2dk3qQ/5ZFb+PXkRNiSoMGGUKlsgLfrxneb72axVJyIYji64E2+nNfYyw==",
+      "dev": true,
+      "dependencies": {
+        "cookie": "^0.7.2"
+      }
+    },
+    "node_modules/@bundled-es-modules/cookie/node_modules/cookie": {
+      "version": "0.7.2",
+      "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
+      "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
+      "dev": true,
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/@bundled-es-modules/statuses": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/@bundled-es-modules/statuses/-/statuses-1.0.1.tgz",
+      "integrity": "sha512-yn7BklA5acgcBr+7w064fGV+SGIFySjCKpqjcWgBAIfrAkY+4GQTJJHQMeT3V/sgz23VTEVV8TtOmkvJAhFVfg==",
+      "dev": true,
+      "dependencies": {
+        "statuses": "^2.0.1"
+      }
+    },
+    "node_modules/@bundled-es-modules/tough-cookie": {
+      "version": "0.1.6",
+      "resolved": "https://registry.npmjs.org/@bundled-es-modules/tough-cookie/-/tough-cookie-0.1.6.tgz",
+      "integrity": "sha512-dvMHbL464C0zI+Yqxbz6kZ5TOEp7GLW+pry/RWndAR8MJQAXZ2rPmIs8tziTZjeIyhSNZgZbCePtfSbdWqStJw==",
+      "dev": true,
+      "dependencies": {
+        "@types/tough-cookie": "^4.0.5",
+        "tough-cookie": "^4.1.4"
+      }
+    },
     "node_modules/@csstools/color-helpers": {
     "node_modules/@csstools/color-helpers": {
       "version": "5.0.2",
       "version": "5.0.2",
       "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.0.2.tgz",
       "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.0.2.tgz",
@@ -2694,6 +2732,98 @@
         "url": "https://github.com/sponsors/nzakas"
         "url": "https://github.com/sponsors/nzakas"
       }
       }
     },
     },
+    "node_modules/@inquirer/confirm": {
+      "version": "5.1.12",
+      "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-5.1.12.tgz",
+      "integrity": "sha512-dpq+ielV9/bqgXRUbNH//KsY6WEw9DrGPmipkpmgC1Y46cwuBTNx7PXFWTjc3MQ+urcc0QxoVHcMI0FW4Ok0hg==",
+      "dev": true,
+      "dependencies": {
+        "@inquirer/core": "^10.1.13",
+        "@inquirer/type": "^3.0.7"
+      },
+      "engines": {
+        "node": ">=18"
+      },
+      "peerDependencies": {
+        "@types/node": ">=18"
+      },
+      "peerDependenciesMeta": {
+        "@types/node": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/@inquirer/core": {
+      "version": "10.1.13",
+      "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-10.1.13.tgz",
+      "integrity": "sha512-1viSxebkYN2nJULlzCxES6G9/stgHSepZ9LqqfdIGPHj5OHhiBUXVS0a6R0bEC2A+VL4D9w6QB66ebCr6HGllA==",
+      "dev": true,
+      "dependencies": {
+        "@inquirer/figures": "^1.0.12",
+        "@inquirer/type": "^3.0.7",
+        "ansi-escapes": "^4.3.2",
+        "cli-width": "^4.1.0",
+        "mute-stream": "^2.0.0",
+        "signal-exit": "^4.1.0",
+        "wrap-ansi": "^6.2.0",
+        "yoctocolors-cjs": "^2.1.2"
+      },
+      "engines": {
+        "node": ">=18"
+      },
+      "peerDependencies": {
+        "@types/node": ">=18"
+      },
+      "peerDependenciesMeta": {
+        "@types/node": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/@inquirer/core/node_modules/cli-width": {
+      "version": "4.1.0",
+      "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz",
+      "integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==",
+      "dev": true,
+      "engines": {
+        "node": ">= 12"
+      }
+    },
+    "node_modules/@inquirer/core/node_modules/mute-stream": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-2.0.0.tgz",
+      "integrity": "sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==",
+      "dev": true,
+      "engines": {
+        "node": "^18.17.0 || >=20.5.0"
+      }
+    },
+    "node_modules/@inquirer/figures": {
+      "version": "1.0.12",
+      "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.12.tgz",
+      "integrity": "sha512-MJttijd8rMFcKJC8NYmprWr6hD3r9Gd9qUC0XwPNwoEPWSMVJwA2MlXxF+nhZZNMY+HXsWa+o7KY2emWYIn0jQ==",
+      "dev": true,
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@inquirer/type": {
+      "version": "3.0.7",
+      "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-3.0.7.tgz",
+      "integrity": "sha512-PfunHQcjwnju84L+ycmcMKB/pTPIngjUJvfnRhKY6FKPuYXlM4aQCb/nIdTFR6BEhMjFvngzvng/vBAJMZpLSA==",
+      "dev": true,
+      "engines": {
+        "node": ">=18"
+      },
+      "peerDependencies": {
+        "@types/node": ">=18"
+      },
+      "peerDependenciesMeta": {
+        "@types/node": {
+          "optional": true
+        }
+      }
+    },
     "node_modules/@isaacs/cliui": {
     "node_modules/@isaacs/cliui": {
       "version": "8.0.2",
       "version": "8.0.2",
       "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
       "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
@@ -2911,6 +3041,23 @@
       "integrity": "sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==",
       "integrity": "sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==",
       "devOptional": true
       "devOptional": true
     },
     },
+    "node_modules/@mswjs/interceptors": {
+      "version": "0.39.2",
+      "resolved": "https://registry.npmjs.org/@mswjs/interceptors/-/interceptors-0.39.2.tgz",
+      "integrity": "sha512-RuzCup9Ct91Y7V79xwCb146RaBRHZ7NBbrIUySumd1rpKqHL5OonaqrGIbug5hNwP/fRyxFMA6ISgw4FTtYFYg==",
+      "dev": true,
+      "dependencies": {
+        "@open-draft/deferred-promise": "^2.2.0",
+        "@open-draft/logger": "^0.3.0",
+        "@open-draft/until": "^2.0.0",
+        "is-node-process": "^1.2.0",
+        "outvariant": "^1.4.3",
+        "strict-event-emitter": "^0.5.1"
+      },
+      "engines": {
+        "node": ">=18"
+      }
+    },
     "node_modules/@napi-rs/triples": {
     "node_modules/@napi-rs/triples": {
       "version": "1.2.0",
       "version": "1.2.0",
       "resolved": "https://registry.npmjs.org/@napi-rs/triples/-/triples-1.2.0.tgz",
       "resolved": "https://registry.npmjs.org/@napi-rs/triples/-/triples-1.2.0.tgz",
@@ -2952,6 +3099,28 @@
         "node": ">= 8"
         "node": ">= 8"
       }
       }
     },
     },
+    "node_modules/@open-draft/deferred-promise": {
+      "version": "2.2.0",
+      "resolved": "https://registry.npmjs.org/@open-draft/deferred-promise/-/deferred-promise-2.2.0.tgz",
+      "integrity": "sha512-CecwLWx3rhxVQF6V4bAgPS5t+So2sTbPgAzafKkVizyi7tlwpcFpdFqq+wqF2OwNBmqFuu6tOyouTuxgpMfzmA==",
+      "dev": true
+    },
+    "node_modules/@open-draft/logger": {
+      "version": "0.3.0",
+      "resolved": "https://registry.npmjs.org/@open-draft/logger/-/logger-0.3.0.tgz",
+      "integrity": "sha512-X2g45fzhxH238HKO4xbSr7+wBS8Fvw6ixhTDuvLd5mqh6bJJCFAPwU9mPDxbcrRtfxv4u5IHCEH77BmxvXmmxQ==",
+      "dev": true,
+      "dependencies": {
+        "is-node-process": "^1.2.0",
+        "outvariant": "^1.4.0"
+      }
+    },
+    "node_modules/@open-draft/until": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/@open-draft/until/-/until-2.1.0.tgz",
+      "integrity": "sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg==",
+      "dev": true
+    },
     "node_modules/@parcel/watcher": {
     "node_modules/@parcel/watcher": {
       "version": "2.5.1",
       "version": "2.5.1",
       "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.1.tgz",
       "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.1.tgz",
@@ -4944,6 +5113,12 @@
         "@types/node": "*"
         "@types/node": "*"
       }
       }
     },
     },
+    "node_modules/@types/cookie": {
+      "version": "0.6.0",
+      "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz",
+      "integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==",
+      "dev": true
+    },
     "node_modules/@types/debug": {
     "node_modules/@types/debug": {
       "version": "4.1.12",
       "version": "4.1.12",
       "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz",
       "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz",
@@ -5226,6 +5401,18 @@
         "@types/node": "*"
         "@types/node": "*"
       }
       }
     },
     },
+    "node_modules/@types/statuses": {
+      "version": "2.0.6",
+      "resolved": "https://registry.npmjs.org/@types/statuses/-/statuses-2.0.6.tgz",
+      "integrity": "sha512-xMAgYwceFhRA2zY+XbEA7mxYbA093wdiW8Vu6gZPGWy9cmOyU9XesH1tNcEWsKFd5Vzrqx5T3D38PWx1FIIXkA==",
+      "dev": true
+    },
+    "node_modules/@types/tough-cookie": {
+      "version": "4.0.5",
+      "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.5.tgz",
+      "integrity": "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==",
+      "dev": true
+    },
     "node_modules/@types/use-sync-external-store": {
     "node_modules/@types/use-sync-external-store": {
       "version": "0.0.6",
       "version": "0.0.6",
       "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz",
       "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz",
@@ -10458,6 +10645,15 @@
       "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==",
       "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==",
       "dev": true
       "dev": true
     },
     },
+    "node_modules/graphql": {
+      "version": "16.11.0",
+      "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.11.0.tgz",
+      "integrity": "sha512-mS1lbMsxgQj6hge1XZ6p7GPhbrtFwUFYi3wRzXAC/FmYnyXMTvvI3td3rjmQ2u8ewXueaSvRPWaEcgVVOT9Jnw==",
+      "dev": true,
+      "engines": {
+        "node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0"
+      }
+    },
     "node_modules/hammerjs": {
     "node_modules/hammerjs": {
       "version": "2.0.8",
       "version": "2.0.8",
       "resolved": "https://registry.npmjs.org/hammerjs/-/hammerjs-2.0.8.tgz",
       "resolved": "https://registry.npmjs.org/hammerjs/-/hammerjs-2.0.8.tgz",
@@ -10567,6 +10763,12 @@
         "tslib": "^2.0.3"
         "tslib": "^2.0.3"
       }
       }
     },
     },
+    "node_modules/headers-polyfill": {
+      "version": "4.0.3",
+      "resolved": "https://registry.npmjs.org/headers-polyfill/-/headers-polyfill-4.0.3.tgz",
+      "integrity": "sha512-IScLbePpkvO846sIwOtOTDjutRMWdXdJmXdMvk6gCBHxFO8d+QKOQedyZSxFTTFYRSmlgSTDtXqqq4pcenBXLQ==",
+      "dev": true
+    },
     "node_modules/highlight.js": {
     "node_modules/highlight.js": {
       "version": "10.7.3",
       "version": "10.7.3",
       "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz",
       "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz",
@@ -11264,6 +11466,12 @@
       "integrity": "sha512-Y4LTamMe0DDQIIAlaer9eKebAlDSV6huy+TWhJVPlzZh2o4tRP5SQWFlLn5N0To4mDD22/qdOq+veo1cSISLgQ==",
       "integrity": "sha512-Y4LTamMe0DDQIIAlaer9eKebAlDSV6huy+TWhJVPlzZh2o4tRP5SQWFlLn5N0To4mDD22/qdOq+veo1cSISLgQ==",
       "dev": true
       "dev": true
     },
     },
+    "node_modules/is-node-process": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/is-node-process/-/is-node-process-1.2.0.tgz",
+      "integrity": "sha512-Vg4o6/fqPxIjtxgUH5QLJhwZ7gW5diGCVlXpuUfELC62CuxM1iHcRe51f2W1FDy04Ai4KJkagKjx3XaqyfRKXw==",
+      "dev": true
+    },
     "node_modules/is-number": {
     "node_modules/is-number": {
       "version": "7.0.0",
       "version": "7.0.0",
       "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
       "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
@@ -12942,6 +13150,120 @@
       "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
       "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
       "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
       "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
     },
     },
+    "node_modules/msw": {
+      "version": "2.10.2",
+      "resolved": "https://registry.npmjs.org/msw/-/msw-2.10.2.tgz",
+      "integrity": "sha512-RCKM6IZseZQCWcSWlutdf590M8nVfRHG1ImwzOtwz8IYxgT4zhUO0rfTcTvDGiaFE0Rhcc+h43lcF3Jc9gFtwQ==",
+      "dev": true,
+      "hasInstallScript": true,
+      "dependencies": {
+        "@bundled-es-modules/cookie": "^2.0.1",
+        "@bundled-es-modules/statuses": "^1.0.1",
+        "@bundled-es-modules/tough-cookie": "^0.1.6",
+        "@inquirer/confirm": "^5.0.0",
+        "@mswjs/interceptors": "^0.39.1",
+        "@open-draft/deferred-promise": "^2.2.0",
+        "@open-draft/until": "^2.1.0",
+        "@types/cookie": "^0.6.0",
+        "@types/statuses": "^2.0.4",
+        "graphql": "^16.8.1",
+        "headers-polyfill": "^4.0.2",
+        "is-node-process": "^1.2.0",
+        "outvariant": "^1.4.3",
+        "path-to-regexp": "^6.3.0",
+        "picocolors": "^1.1.1",
+        "strict-event-emitter": "^0.5.1",
+        "type-fest": "^4.26.1",
+        "yargs": "^17.7.2"
+      },
+      "bin": {
+        "msw": "cli/index.js"
+      },
+      "engines": {
+        "node": ">=18"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/mswjs"
+      },
+      "peerDependencies": {
+        "typescript": ">= 4.8.x"
+      },
+      "peerDependenciesMeta": {
+        "typescript": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/msw/node_modules/cliui": {
+      "version": "8.0.1",
+      "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
+      "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==",
+      "dev": true,
+      "dependencies": {
+        "string-width": "^4.2.0",
+        "strip-ansi": "^6.0.1",
+        "wrap-ansi": "^7.0.0"
+      },
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/msw/node_modules/type-fest": {
+      "version": "4.41.0",
+      "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz",
+      "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==",
+      "dev": true,
+      "engines": {
+        "node": ">=16"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/msw/node_modules/wrap-ansi": {
+      "version": "7.0.0",
+      "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
+      "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
+      "dev": true,
+      "dependencies": {
+        "ansi-styles": "^4.0.0",
+        "string-width": "^4.1.0",
+        "strip-ansi": "^6.0.0"
+      },
+      "engines": {
+        "node": ">=10"
+      },
+      "funding": {
+        "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
+      }
+    },
+    "node_modules/msw/node_modules/yargs": {
+      "version": "17.7.2",
+      "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
+      "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==",
+      "dev": true,
+      "dependencies": {
+        "cliui": "^8.0.1",
+        "escalade": "^3.1.1",
+        "get-caller-file": "^2.0.5",
+        "require-directory": "^2.1.1",
+        "string-width": "^4.2.3",
+        "y18n": "^5.0.5",
+        "yargs-parser": "^21.1.1"
+      },
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/msw/node_modules/yargs-parser": {
+      "version": "21.1.1",
+      "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
+      "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
+      "dev": true,
+      "engines": {
+        "node": ">=12"
+      }
+    },
     "node_modules/multicast-dns": {
     "node_modules/multicast-dns": {
       "version": "7.2.5",
       "version": "7.2.5",
       "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-7.2.5.tgz",
       "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-7.2.5.tgz",
@@ -13373,6 +13695,12 @@
         "node": ">=0.10.0"
         "node": ">=0.10.0"
       }
       }
     },
     },
+    "node_modules/outvariant": {
+      "version": "1.4.3",
+      "resolved": "https://registry.npmjs.org/outvariant/-/outvariant-1.4.3.tgz",
+      "integrity": "sha512-+Sl2UErvtsoajRDKCE5/dBz4DIvHXQQnAxtQTF04OJxY0+DyZXSo5P5Bb7XYWOh81syohlYL24hbDwxedPUJCA==",
+      "dev": true
+    },
     "node_modules/p-cancelable": {
     "node_modules/p-cancelable": {
       "version": "0.4.1",
       "version": "0.4.1",
       "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-0.4.1.tgz",
       "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-0.4.1.tgz",
@@ -17125,6 +17453,12 @@
       "integrity": "sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==",
       "integrity": "sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==",
       "dev": true
       "dev": true
     },
     },
+    "node_modules/strict-event-emitter": {
+      "version": "0.5.1",
+      "resolved": "https://registry.npmjs.org/strict-event-emitter/-/strict-event-emitter-0.5.1.tgz",
+      "integrity": "sha512-vMgjE/GGEPEFnhFub6pa4FmJBRBVOLpIII2hvCZ8Kzb7K0hlHo7mQv6xYrBvCL2LtAIBwFUK8wvuJgTVSQ5MFQ==",
+      "dev": true
+    },
     "node_modules/strict-uri-encode": {
     "node_modules/strict-uri-encode": {
       "version": "1.1.0",
       "version": "1.1.0",
       "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz",
       "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz",
@@ -19371,6 +19705,18 @@
         "url": "https://github.com/sponsors/sindresorhus"
         "url": "https://github.com/sponsors/sindresorhus"
       }
       }
     },
     },
+    "node_modules/yoctocolors-cjs": {
+      "version": "2.1.2",
+      "resolved": "https://registry.npmjs.org/yoctocolors-cjs/-/yoctocolors-cjs-2.1.2.tgz",
+      "integrity": "sha512-cYVsTjKl8b+FrnidjibDWskAv7UKOfcwaVZdp/it9n1s9fU3IkgDbhdIRKCW4JDsAlECJY0ytoVPT3sK6kideA==",
+      "dev": true,
+      "engines": {
+        "node": ">=18"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
     "node_modules/yup": {
     "node_modules/yup": {
       "version": "1.6.1",
       "version": "1.6.1",
       "resolved": "https://registry.npmjs.org/yup/-/yup-1.6.1.tgz",
       "resolved": "https://registry.npmjs.org/yup/-/yup-1.6.1.tgz",

+ 6 - 0
package.json

@@ -88,6 +88,7 @@
     "globals": "^16.2.0",
     "globals": "^16.2.0",
     "husky": "^9.1.7",
     "husky": "^9.1.7",
     "lint-staged": "^16.1.0",
     "lint-staged": "^16.1.0",
+    "msw": "^2.10.2",
     "postcss": "^8.5.4",
     "postcss": "^8.5.4",
     "postcss-loader": "^8.1.1",
     "postcss-loader": "^8.1.1",
     "prettier": "^3.5.3",
     "prettier": "^3.5.3",
@@ -98,5 +99,10 @@
     "typescript": "^5.4.5",
     "typescript": "^5.4.5",
     "typescript-eslint": "^8.33.1",
     "typescript-eslint": "^8.33.1",
     "webpack": "5.91.0"
     "webpack": "5.91.0"
+  },
+  "msw": {
+    "workerDirectory": [
+      "public"
+    ]
   }
   }
 }
 }

+ 344 - 0
public/mockServiceWorker.js

@@ -0,0 +1,344 @@
+/* eslint-disable */
+/* tslint:disable */
+
+/**
+ * Mock Service Worker.
+ * @see https://github.com/mswjs/msw
+ * - Please do NOT modify this file.
+ */
+
+const PACKAGE_VERSION = '2.10.2'
+const INTEGRITY_CHECKSUM = 'f5825c521429caf22a4dd13b66e243af'
+const IS_MOCKED_RESPONSE = Symbol('isMockedResponse')
+const activeClientIds = new Set()
+
+addEventListener('install', function () {
+  self.skipWaiting()
+})
+
+addEventListener('activate', function (event) {
+  event.waitUntil(self.clients.claim())
+})
+
+addEventListener('message', async function (event) {
+  const clientId = Reflect.get(event.source || {}, 'id')
+
+  if (!clientId || !self.clients) {
+    return
+  }
+
+  const client = await self.clients.get(clientId)
+
+  if (!client) {
+    return
+  }
+
+  const allClients = await self.clients.matchAll({
+    type: 'window',
+  })
+
+  switch (event.data) {
+    case 'KEEPALIVE_REQUEST': {
+      sendToClient(client, {
+        type: 'KEEPALIVE_RESPONSE',
+      })
+      break
+    }
+
+    case 'INTEGRITY_CHECK_REQUEST': {
+      sendToClient(client, {
+        type: 'INTEGRITY_CHECK_RESPONSE',
+        payload: {
+          packageVersion: PACKAGE_VERSION,
+          checksum: INTEGRITY_CHECKSUM,
+        },
+      })
+      break
+    }
+
+    case 'MOCK_ACTIVATE': {
+      activeClientIds.add(clientId)
+
+      sendToClient(client, {
+        type: 'MOCKING_ENABLED',
+        payload: {
+          client: {
+            id: client.id,
+            frameType: client.frameType,
+          },
+        },
+      })
+      break
+    }
+
+    case 'MOCK_DEACTIVATE': {
+      activeClientIds.delete(clientId)
+      break
+    }
+
+    case 'CLIENT_CLOSED': {
+      activeClientIds.delete(clientId)
+
+      const remainingClients = allClients.filter((client) => {
+        return client.id !== clientId
+      })
+
+      // Unregister itself when there are no more clients
+      if (remainingClients.length === 0) {
+        self.registration.unregister()
+      }
+
+      break
+    }
+  }
+})
+
+addEventListener('fetch', function (event) {
+  // Bypass navigation requests.
+  if (event.request.mode === 'navigate') {
+    return
+  }
+
+  // Opening the DevTools triggers the "only-if-cached" request
+  // that cannot be handled by the worker. Bypass such requests.
+  if (
+    event.request.cache === 'only-if-cached' &&
+    event.request.mode !== 'same-origin'
+  ) {
+    return
+  }
+
+  // Bypass all requests when there are no active clients.
+  // Prevents the self-unregistered worked from handling requests
+  // after it's been deleted (still remains active until the next reload).
+  if (activeClientIds.size === 0) {
+    return
+  }
+
+  const requestId = crypto.randomUUID()
+  event.respondWith(handleRequest(event, requestId))
+})
+
+/**
+ * @param {FetchEvent} event
+ * @param {string} requestId
+ */
+async function handleRequest(event, requestId) {
+  const client = await resolveMainClient(event)
+  const requestCloneForEvents = event.request.clone()
+  const response = await getResponse(event, client, requestId)
+
+  // Send back the response clone for the "response:*" life-cycle events.
+  // Ensure MSW is active and ready to handle the message, otherwise
+  // this message will pend indefinitely.
+  if (client && activeClientIds.has(client.id)) {
+    const serializedRequest = await serializeRequest(requestCloneForEvents)
+
+    // Clone the response so both the client and the library could consume it.
+    const responseClone = response.clone()
+
+    sendToClient(
+      client,
+      {
+        type: 'RESPONSE',
+        payload: {
+          isMockedResponse: IS_MOCKED_RESPONSE in response,
+          request: {
+            id: requestId,
+            ...serializedRequest,
+          },
+          response: {
+            type: responseClone.type,
+            status: responseClone.status,
+            statusText: responseClone.statusText,
+            headers: Object.fromEntries(responseClone.headers.entries()),
+            body: responseClone.body,
+          },
+        },
+      },
+      responseClone.body ? [serializedRequest.body, responseClone.body] : [],
+    )
+  }
+
+  return response
+}
+
+/**
+ * Resolve the main client for the given event.
+ * Client that issues a request doesn't necessarily equal the client
+ * that registered the worker. It's with the latter the worker should
+ * communicate with during the response resolving phase.
+ * @param {FetchEvent} event
+ * @returns {Promise<Client | undefined>}
+ */
+async function resolveMainClient(event) {
+  const client = await self.clients.get(event.clientId)
+
+  if (activeClientIds.has(event.clientId)) {
+    return client
+  }
+
+  if (client?.frameType === 'top-level') {
+    return client
+  }
+
+  const allClients = await self.clients.matchAll({
+    type: 'window',
+  })
+
+  return allClients
+    .filter((client) => {
+      // Get only those clients that are currently visible.
+      return client.visibilityState === 'visible'
+    })
+    .find((client) => {
+      // Find the client ID that's recorded in the
+      // set of clients that have registered the worker.
+      return activeClientIds.has(client.id)
+    })
+}
+
+/**
+ * @param {FetchEvent} event
+ * @param {Client | undefined} client
+ * @param {string} requestId
+ * @returns {Promise<Response>}
+ */
+async function getResponse(event, client, requestId) {
+  // Clone the request because it might've been already used
+  // (i.e. its body has been read and sent to the client).
+  const requestClone = event.request.clone()
+
+  function passthrough() {
+    // Cast the request headers to a new Headers instance
+    // so the headers can be manipulated with.
+    const headers = new Headers(requestClone.headers)
+
+    // Remove the "accept" header value that marked this request as passthrough.
+    // This prevents request alteration and also keeps it compliant with the
+    // user-defined CORS policies.
+    const acceptHeader = headers.get('accept')
+    if (acceptHeader) {
+      const values = acceptHeader.split(',').map((value) => value.trim())
+      const filteredValues = values.filter(
+        (value) => value !== 'msw/passthrough',
+      )
+
+      if (filteredValues.length > 0) {
+        headers.set('accept', filteredValues.join(', '))
+      } else {
+        headers.delete('accept')
+      }
+    }
+
+    return fetch(requestClone, { headers })
+  }
+
+  // Bypass mocking when the client is not active.
+  if (!client) {
+    return passthrough()
+  }
+
+  // Bypass initial page load requests (i.e. static assets).
+  // The absence of the immediate/parent client in the map of the active clients
+  // means that MSW hasn't dispatched the "MOCK_ACTIVATE" event yet
+  // and is not ready to handle requests.
+  if (!activeClientIds.has(client.id)) {
+    return passthrough()
+  }
+
+  // Notify the client that a request has been intercepted.
+  const serializedRequest = await serializeRequest(event.request)
+  const clientMessage = await sendToClient(
+    client,
+    {
+      type: 'REQUEST',
+      payload: {
+        id: requestId,
+        ...serializedRequest,
+      },
+    },
+    [serializedRequest.body],
+  )
+
+  switch (clientMessage.type) {
+    case 'MOCK_RESPONSE': {
+      return respondWithMock(clientMessage.data)
+    }
+
+    case 'PASSTHROUGH': {
+      return passthrough()
+    }
+  }
+
+  return passthrough()
+}
+
+/**
+ * @param {Client} client
+ * @param {any} message
+ * @param {Array<Transferable>} transferrables
+ * @returns {Promise<any>}
+ */
+function sendToClient(client, message, transferrables = []) {
+  return new Promise((resolve, reject) => {
+    const channel = new MessageChannel()
+
+    channel.port1.onmessage = (event) => {
+      if (event.data && event.data.error) {
+        return reject(event.data.error)
+      }
+
+      resolve(event.data)
+    }
+
+    client.postMessage(message, [
+      channel.port2,
+      ...transferrables.filter(Boolean),
+    ])
+  })
+}
+
+/**
+ * @param {Response} response
+ * @returns {Response}
+ */
+function respondWithMock(response) {
+  // Setting response status code to 0 is a no-op.
+  // However, when responding with a "Response.error()", the produced Response
+  // instance will have status code set to 0. Since it's not possible to create
+  // a Response instance with status code 0, handle that use-case separately.
+  if (response.status === 0) {
+    return Response.error()
+  }
+
+  const mockedResponse = new Response(response.body, response)
+
+  Reflect.defineProperty(mockedResponse, IS_MOCKED_RESPONSE, {
+    value: true,
+    enumerable: true,
+  })
+
+  return mockedResponse
+}
+
+/**
+ * @param {Request} request
+ */
+async function serializeRequest(request) {
+  return {
+    url: request.url,
+    mode: request.mode,
+    method: request.method,
+    headers: Object.fromEntries(request.headers.entries()),
+    cache: request.cache,
+    credentials: request.credentials,
+    destination: request.destination,
+    integrity: request.integrity,
+    redirect: request.redirect,
+    referrer: request.referrer,
+    referrerPolicy: request.referrerPolicy,
+    body: await request.arrayBuffer(),
+    keepalive: request.keepalive,
+  }
+}

+ 1 - 1
src/API/patientType.ts

@@ -32,7 +32,7 @@ export async function fetchPatientTypes(
   product = 'DROS',
   product = 'DROS',
   source = 'Electron'
   source = 'Electron'
 ): Promise<PatientType[]> {
 ): Promise<PatientType[]> {
-  const response = await axios.get('/auth/protocol/patient_type', {
+  const response = await axios.get('/dr/api/v1/auth/protocol/patient_type', {
     params,
     params,
     headers: {
     headers: {
       Authorization: `Bearer ${token}`,
       Authorization: `Bearer ${token}`,

+ 12 - 0
src/app.tsx

@@ -11,6 +11,18 @@ import messages_zh from './assets/i18n/messages/zh';
 
 
 const messages = locale === 'zh' ? messages_zh : messages_en;
 const messages = locale === 'zh' ? messages_zh : messages_en;
 
 
+if (process.env.NODE_ENV === 'development') {
+  import('../mocks/server')
+    .then(({ server }) => {
+      server.start({
+        onUnhandledRequest: 'bypass', // Ignore unhandled requests
+      });
+    })
+    .catch((err) => {
+      console.warn('Mock server module not found:', err);
+    });
+}
+
 function App({ children }: PropsWithChildren<React.ReactNode>) {
 function App({ children }: PropsWithChildren<React.ReactNode>) {
   useLaunch(() => {
   useLaunch(() => {
     console.log('App launched.');
     console.log('App launched.');

+ 5 - 1
src/pages/patient/components/RegisterAvailableFilterBar.tsx

@@ -2,6 +2,7 @@ import React from 'react';
 import { Row, Col, Select, Segmented } from 'antd';
 import { Row, Col, Select, Segmented } from 'antd';
 import { useSelector } from 'react-redux';
 import { useSelector } from 'react-redux';
 import { RootState } from '@/states/store';
 import { RootState } from '@/states/store';
+import { PatientType } from '@/API/patientType';
 
 
 interface Props {
 interface Props {
   selected: 'protocol' | 'view';
   selected: 'protocol' | 'view';
@@ -63,7 +64,10 @@ const RegisterAvailableFilterBar: React.FC<Props> = ({
             allowClear
             allowClear
             style={{ width: '100%' }}
             style={{ width: '100%' }}
             placeholder="患者类型"
             placeholder="患者类型"
-            options={patientTypes}
+            options={patientTypes.map((item: PatientType) => ({
+              label: item.patient_type_name,
+              value: item.patient_type_id,
+            }))}
             value={patientType}
             value={patientType}
             onChange={setPatientType}
             onChange={setPatientType}
           />
           />

+ 2 - 0
src/states/patientTypeSlice.ts

@@ -25,6 +25,8 @@ export const getPatientTypes = createAsyncThunk(
     { rejectWithValue }
     { rejectWithValue }
   ) => {
   ) => {
     try {
     try {
+      console.log('Fetching patient types with params:');
+
       const data = await fetchPatientTypes(params, token);
       const data = await fetchPatientTypes(params, token);
       return data;
       return data;
     } catch (err: unknown) {
     } catch (err: unknown) {

+ 2 - 4
tsconfig.json

@@ -17,14 +17,12 @@
     "jsx": "react-jsx",
     "jsx": "react-jsx",
     "allowJs": true,
     "allowJs": true,
     "resolveJsonModule": true,
     "resolveJsonModule": true,
-    "typeRoots": [
-      "node_modules/@types"
-    ],
+    "typeRoots": ["node_modules/@types"],
     "paths": {
     "paths": {
       // TS5090 leading './'
       // TS5090 leading './'
       "@/*": ["./src/*"]
       "@/*": ["./src/*"]
     }
     }
   },
   },
-  "include": ["./src", "./types", "./config"],
+  "include": ["./src", "./types", "./config", "public/mockServiceWorker.js"],
   "compileOnSave": false
   "compileOnSave": false
 }
 }