ソースを参照

登录 & 接口

cc12458 1 年間 前
コミット
8113a4a760

+ 2 - 0
.env/.env

@@ -0,0 +1,2 @@
+SIX_APP_NAME=@six/hms
+SIX_TITLE=中医健康管理平台

+ 1 - 0
.env/.env.development

@@ -0,0 +1 @@
+REQUEST_API_PROXY_URL=http://121.43.162.141:8001

+ 1 - 0
.env/.env.production

@@ -0,0 +1 @@
+REQUEST_API_PROXY_URL=http://121.43.162.141:8001

+ 3 - 3
.prettierrc.json

@@ -1,8 +1,8 @@
 {
   "$schema": "https://json.schemastore.org/prettierrc",
-  "semi": false,
+  "semi": true,
   "tabWidth": 2,
   "singleQuote": true,
   "printWidth": 100,
-  "trailingComma": "none"
-}
+  "trailingComma": "es5"
+}

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

@@ -5,7 +5,8 @@ declare module 'alova' {
   export interface AlovaCustomTypes {
     meta: {
       ignoreToken?: boolean;
-      errorModal?: boolean;
+      ignoreException?: boolean;
+      unconvert?: boolean;
     };
   }
 }

+ 1 - 0
@types/components.d.ts

@@ -7,6 +7,7 @@ export {}
 /* prettier-ignore */
 declare module 'vue' {
   export interface GlobalComponents {
+    AAvatar: typeof import('ant-design-vue/es')['Avatar']
     AButton: typeof import('ant-design-vue/es')['Button']
     ACard: typeof import('ant-design-vue/es')['Card']
     ACollapse: typeof import('ant-design-vue/es')['Collapse']

+ 1 - 3
@types/typed-router.d.ts

@@ -19,9 +19,7 @@ declare module 'vue-router/auto-routes' {
    */
   export interface RouteNamedMap {
     '/': RouteRecordInfo<'/', '/', Record<never, never>, Record<never, never>>,
-    '//patient/today': RouteRecordInfo<'//patient/today', '/patient/today', Record<never, never>, Record<never, never>>,
-    '//patient/today/[patientId].record.[[recordId]]': RouteRecordInfo<'//patient/today/[patientId].record.[[recordId]]', '/patient/today/:patientId/record/:recordId?', { patientId: ParamValue<true>, recordId?: ParamValueZeroOrOne<true> }, { patientId: ParamValue<false>, recordId?: ParamValueZeroOrOne<false> }>,
-    '//system/users/': RouteRecordInfo<'//system/users/', '/system/users', Record<never, never>, Record<never, never>>,
+    '//system/role': RouteRecordInfo<'//system/role', '/system/role', Record<never, never>, Record<never, never>>,
     '/login': RouteRecordInfo<'/login', '/login', Record<never, never>, Record<never, never>>,
   }
 }

+ 4 - 3
package.json

@@ -17,16 +17,17 @@
     "@unocss/reset": "^0.61.0",
     "@vueuse/components": "^10.11.0",
     "@vueuse/core": "^10.11.0",
-    "alova": "3.0.0-beta.3",
+    "alova": "3.0.5",
     "ant-design-vue": "4.x",
     "dayjs": "^1.11.11",
     "pinia": "^2.1.7",
+    "pinia-plugin-persistedstate": "^3.2.1",
     "vue": "^3.4.29",
     "vue-router": "^4.3.3",
     "vue-virtual-scroller": "2.0.0-beta.8"
   },
   "devDependencies": {
-    "@alova/mock": "^1.5.2",
+    "@alova/mock": "^2.0.4",
     "@iconify/json": "^2.2.222",
     "@rushstack/eslint-patch": "^1.8.0",
     "@tsconfig/node20": "^20.1.4",
@@ -49,7 +50,7 @@
     "unocss": "^0.61.0",
     "unplugin-auto-import": "^0.17.6",
     "unplugin-vue-components": "^0.27.0",
-    "unplugin-vue-router": "^0.10.0",
+    "unplugin-vue-router": "^0.10.2",
     "vite": "^5.3.1",
     "vite-plugin-vue-devtools": "^7.3.1",
     "vue-tsc": "^2.0.21"

+ 156 - 76
pnpm-lock.yaml

@@ -18,8 +18,8 @@ dependencies:
     specifier: ^10.11.0
     version: 10.11.0(vue@3.4.30)
   alova:
-    specifier: 3.0.0-beta.3
-    version: 3.0.0-beta.3
+    specifier: 3.0.5
+    version: 3.0.5
   ant-design-vue:
     specifier: 4.x
     version: 4.2.3(vue@3.4.30)
@@ -29,6 +29,9 @@ dependencies:
   pinia:
     specifier: ^2.1.7
     version: 2.1.7(typescript@5.4.5)(vue@3.4.30)
+  pinia-plugin-persistedstate:
+    specifier: ^3.2.1
+    version: 3.2.1(pinia@2.1.7)
   vue:
     specifier: ^3.4.29
     version: 3.4.30(typescript@5.4.5)
@@ -41,8 +44,8 @@ dependencies:
 
 devDependencies:
   '@alova/mock':
-    specifier: ^1.5.2
-    version: 1.5.2
+    specifier: ^2.0.4
+    version: 2.0.4(alova@3.0.5)
   '@iconify/json':
     specifier: ^2.2.222
     version: 2.2.222
@@ -102,7 +105,7 @@ devDependencies:
     version: 5.4.5
   unocss:
     specifier: ^0.61.0
-    version: 0.61.0(postcss@8.4.38)(vite@5.3.1)
+    version: 0.61.0(postcss@8.4.41)(vite@5.3.1)
   unplugin-auto-import:
     specifier: ^0.17.6
     version: 0.17.6(@vueuse/core@10.11.0)
@@ -110,8 +113,8 @@ devDependencies:
     specifier: ^0.27.0
     version: 0.27.0(vue@3.4.30)
   unplugin-vue-router:
-    specifier: ^0.10.0
-    version: 0.10.0(vue-router@4.4.0)(vue@3.4.30)
+    specifier: ^0.10.2
+    version: 0.10.2(vue-router@4.4.0)(vue@3.4.30)
   vite:
     specifier: ^5.3.1
     version: 5.3.1(@types/node@20.14.8)(sass@1.77.6)
@@ -124,13 +127,17 @@ devDependencies:
 
 packages:
 
-  /@alova/mock@1.5.2:
-    resolution: {integrity: sha512-qEkOgTgzbltkTG/3bn1cvekP6AlPuqpv/N3s5dpu15neMGk24p1qnruz+aeNtg4rMU92zNJ+FtGZF4QRd/vxog==}
+  /@alova/mock@2.0.4(alova@3.0.5):
+    resolution: {integrity: sha512-mBqzwtt0PUa41W8lAxacqnttfiqccCRldPSqyOzFDGD9n63sBGV3CFcne4oUe9YYwjgrXYqsWVY4FPWqltqLhw==}
+    peerDependencies:
+      alova: ^3.0.5
+    dependencies:
+      '@alova/shared': 1.0.4
+      alova: 3.0.5
     dev: true
 
-  /@alova/shared@1.0.0-beta.2:
-    resolution: {integrity: sha512-5tUUFIT8h5O2tcFDX/mwwRwqGHM/QZojfdoLsh0QEU7MdJcM8U9lcRTvQBQghsqnrBqYMIaZX/OwP/Dw8Rme2w==}
-    dev: false
+  /@alova/shared@1.0.4:
+    resolution: {integrity: sha512-Tq47Wd5q76kPmGLXmPijb0AfsXW2aWR9Pid1KO1nz96BdWiKstx2t/ZLTNaGtQzYyB6M+puunaTTJbusJQPmkQ==}
 
   /@ampproject/remapping@2.3.0:
     resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==}
@@ -377,6 +384,11 @@ packages:
     resolution: {integrity: sha512-7MbVt6xrwFQbunH2DNQsAP5sTGxfqQtErvBIvIMi6EQnbgUOuVYanvREcmFrOPhoXBrTtjhhP+lW+o5UfK+tDg==}
     engines: {node: '>=6.9.0'}
 
+  /@babel/helper-string-parser@7.24.8:
+    resolution: {integrity: sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ==}
+    engines: {node: '>=6.9.0'}
+    dev: true
+
   /@babel/helper-validator-identifier@7.24.7:
     resolution: {integrity: sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==}
     engines: {node: '>=6.9.0'}
@@ -411,6 +423,14 @@ packages:
     dependencies:
       '@babel/types': 7.24.7
 
+  /@babel/parser@7.25.3:
+    resolution: {integrity: sha512-iLTJKDbJ4hMvFPgQwwsVoxtHyWpKKPBrxkANrSYewDPaPpT5py5yeVkgPIJ7XYXhndxJpaA3PyALSXQ7u8e/Dw==}
+    engines: {node: '>=6.0.0'}
+    hasBin: true
+    dependencies:
+      '@babel/types': 7.25.2
+    dev: true
+
   /@babel/plugin-proposal-decorators@7.24.7(@babel/core@7.24.7):
     resolution: {integrity: sha512-RL9GR0pUG5Kc8BUWLNDm2T5OpYwSX15r98I0IkgmRQTXuELq/OynH8xtMTMvTJFjXbMWFVTKtYkTaYQsuAwQlQ==}
     engines: {node: '>=6.9.0'}
@@ -561,6 +581,15 @@ packages:
       '@babel/helper-validator-identifier': 7.24.7
       to-fast-properties: 2.0.0
 
+  /@babel/types@7.25.2:
+    resolution: {integrity: sha512-YTnYtra7W9e6/oAZEHj0bJehPRUlLH9/fbpT5LfB0NhQXyALCRkRs3zH9v07IYhkgpqX6Z78FnuccZr/l4Fs4Q==}
+    engines: {node: '>=6.9.0'}
+    dependencies:
+      '@babel/helper-string-parser': 7.24.8
+      '@babel/helper-validator-identifier': 7.24.7
+      to-fast-properties: 2.0.0
+    dev: true
+
   /@ctrl/tinycolor@3.6.1:
     resolution: {integrity: sha512-SITSV6aIXsuVNV3f3O0f2n/cgyEDWoSqtZMYiAmcsYHydcKrOz3gUxB/iXd/Qf08+IZX4KpgNbvUdMBmWz+kcA==}
     engines: {node: '>=10'}
@@ -894,30 +923,6 @@ packages:
       '@jridgewell/sourcemap-codec': 1.4.15
     dev: true
 
-  /@node-ipc/event-pubsub@6.0.2:
-    resolution: {integrity: sha512-vdpnkrcBOHj/yQlSi78HiaPLWZFI/PdBRN34T64Th6Z4G600A7IDdb47/EFwKJh1trcR6q0ednyR8d8vJFsTcw==}
-    engines: {node: '>=13.0.0'}
-    dependencies:
-      strong-type: 1.1.0
-    dev: false
-
-  /@node-ipc/js-queue@2.0.3:
-    resolution: {integrity: sha512-fL1wpr8hhD5gT2dA1qifeVaoDFlQR5es8tFuKqjHX+kdOtdNHnxkVZbtIrR2rxnMFvehkjaZRNV2H/gPXlb0hw==}
-    engines: {node: '>=1.0.0'}
-    dependencies:
-      easy-stack: 1.0.1
-    dev: false
-
-  /@node-ipc/node-ipc@11.0.3:
-    resolution: {integrity: sha512-7u4ViA2IdkdX1nlorjouR1x6rIoeEGTOaWyvvvKZhmWyifv7Q5gUby54tSyG1ydHmNrS+4BnZqPf7x8rV9Zklw==}
-    engines: {node: '>=14'}
-    dependencies:
-      '@node-ipc/event-pubsub': 6.0.2
-      '@node-ipc/js-queue': 2.0.3
-      js-message: 1.0.7
-      strong-type: 1.1.0
-    dev: false
-
   /@nodelib/fs.scandir@2.1.5:
     resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
     engines: {node: '>= 8'}
@@ -1327,7 +1332,7 @@ packages:
       sirv: 2.0.4
     dev: true
 
-  /@unocss/postcss@0.61.0(postcss@8.4.38):
+  /@unocss/postcss@0.61.0(postcss@8.4.41):
     resolution: {integrity: sha512-0ZHUeLYu057xL1vXg2coV62ly6zaCgYdA/oHKCMaU9KT0TI49+DE73GouHypRNM5YXfuUPfXhPGGUuFWkAbI1A==}
     engines: {node: '>=14'}
     peerDependencies:
@@ -1339,7 +1344,7 @@ packages:
       css-tree: 2.3.1
       fast-glob: 3.3.2
       magic-string: 0.30.10
-      postcss: 8.4.38
+      postcss: 8.4.41
     dev: true
 
   /@unocss/preset-attributify@0.61.0:
@@ -1520,8 +1525,8 @@ packages:
       vscode-uri: 3.0.8
     dev: true
 
-  /@vue-macros/common@1.10.4(vue@3.4.30):
-    resolution: {integrity: sha512-akO6Bd6U4jP0+ZKbHq6mbYkw1coOrJpLeVmkuMlUsT5wZRi11BjauGcZHusBSzUjgCBsa1kZTyipxrxrWB54Hw==}
+  /@vue-macros/common@1.12.2(vue@3.4.30):
+    resolution: {integrity: sha512-+NGfhrPvPNOb3Wg9PNPEXPe0HTXmVe6XJawL1gi3cIjOSGIhpOdvmMT2cRuWb265IpA/PeL5Sqo0+DQnEDxLvw==}
     engines: {node: '>=16.14.0'}
     peerDependencies:
       vue: ^2.7.0 || ^3.2.25
@@ -1529,12 +1534,12 @@ packages:
       vue:
         optional: true
     dependencies:
-      '@babel/types': 7.24.7
+      '@babel/types': 7.25.2
       '@rollup/pluginutils': 5.1.0
-      '@vue/compiler-sfc': 3.4.30
-      ast-kit: 0.12.2
+      '@vue/compiler-sfc': 3.4.36
+      ast-kit: 1.0.1
       local-pkg: 0.5.0
-      magic-string-ast: 0.6.1
+      magic-string-ast: 0.6.2
       vue: 3.4.30(typescript@5.4.5)
     transitivePeerDependencies:
       - rollup
@@ -1590,12 +1595,29 @@ packages:
       estree-walker: 2.0.2
       source-map-js: 1.2.0
 
+  /@vue/compiler-core@3.4.36:
+    resolution: {integrity: sha512-qBkndgpwFKdupmOPoiS10i7oFdN7a+4UNDlezD0GlQ1kuA1pNrscg9g12HnB5E8hrWSuEftRsbJhL1HI2zpJhg==}
+    dependencies:
+      '@babel/parser': 7.24.7
+      '@vue/shared': 3.4.36
+      entities: 5.0.0
+      estree-walker: 2.0.2
+      source-map-js: 1.2.0
+    dev: true
+
   /@vue/compiler-dom@3.4.30:
     resolution: {integrity: sha512-+16Sd8lYr5j/owCbr9dowcNfrHd+pz+w2/b5Lt26Oz/kB90C9yNbxQ3bYOvt7rI2bxk0nqda39hVcwDFw85c2Q==}
     dependencies:
       '@vue/compiler-core': 3.4.30
       '@vue/shared': 3.4.30
 
+  /@vue/compiler-dom@3.4.36:
+    resolution: {integrity: sha512-eEIjy4GwwZTFon/Y+WO8tRRNGqylaRlA79T1RLhUpkOzJ7EtZkkb8MurNfkqY6x6Qiu0R7ESspEF7GkPR/4yYg==}
+    dependencies:
+      '@vue/compiler-core': 3.4.36
+      '@vue/shared': 3.4.36
+    dev: true
+
   /@vue/compiler-sfc@3.4.30:
     resolution: {integrity: sha512-8vElKklHn/UY8+FgUFlQrYAPbtiSB2zcgeRKW7HkpSRn/JjMRmZvuOtwDx036D1aqKNSTtXkWRfqx53Qb+HmMg==}
     dependencies:
@@ -1609,12 +1631,33 @@ packages:
       postcss: 8.4.38
       source-map-js: 1.2.0
 
+  /@vue/compiler-sfc@3.4.36:
+    resolution: {integrity: sha512-rhuHu7qztt/rNH90dXPTzhB7hLQT2OC4s4GrPVqmzVgPY4XBlfWmcWzn4bIPEWNImt0CjO7kfHAf/1UXOtx3vw==}
+    dependencies:
+      '@babel/parser': 7.24.7
+      '@vue/compiler-core': 3.4.36
+      '@vue/compiler-dom': 3.4.36
+      '@vue/compiler-ssr': 3.4.36
+      '@vue/shared': 3.4.36
+      estree-walker: 2.0.2
+      magic-string: 0.30.10
+      postcss: 8.4.41
+      source-map-js: 1.2.0
+    dev: true
+
   /@vue/compiler-ssr@3.4.30:
     resolution: {integrity: sha512-ZJ56YZGXJDd6jky4mmM0rNaNP6kIbQu9LTKZDhcpddGe/3QIalB1WHHmZ6iZfFNyj5mSypTa4+qDJa5VIuxMSg==}
     dependencies:
       '@vue/compiler-dom': 3.4.30
       '@vue/shared': 3.4.30
 
+  /@vue/compiler-ssr@3.4.36:
+    resolution: {integrity: sha512-Wt1zyheF0zVvRJyhY74uxQbnkXV2Le/JPOrAxooR4rFYKC7cFr+cRqW6RU3cM/bsTy7sdZ83IDuy/gLPSfPGng==}
+    dependencies:
+      '@vue/compiler-dom': 3.4.36
+      '@vue/shared': 3.4.36
+    dev: true
+
   /@vue/devtools-api@6.6.3:
     resolution: {integrity: sha512-0MiMsFma/HqA6g3KLKn+AGpL1kgKhFWszC9U29NfpWK5LE7bjeXxySWJrOJ77hBz+TBrBQ7o4QJqbPbqbs8rJw==}
 
@@ -1737,6 +1780,10 @@ packages:
   /@vue/shared@3.4.30:
     resolution: {integrity: sha512-CLg+f8RQCHQnKvuHY9adMsMaQOcqclh6Z5V9TaoMgy0ut0tz848joZ7/CYFFyF/yZ5i2yaw7Fn498C+CNZVHIg==}
 
+  /@vue/shared@3.4.36:
+    resolution: {integrity: sha512-fdPLStwl1sDfYuUftBaUVn2pIrVFDASYerZSrlBvVBfylObPA1gtcWJHy5Ox8jLEJ524zBibss488Q3SZtU1uA==}
+    dev: true
+
   /@vue/tsconfig@0.5.1:
     resolution: {integrity: sha512-VcZK7MvpjuTPx2w6blwnwZAu5/LgBUtejFOi3pPGQFXQN5Ela03FUtd2Qtg4yWGGissVL0dr6Ro1LfOFh+PCuQ==}
     dev: true
@@ -1788,6 +1835,12 @@ packages:
     hasBin: true
     dev: true
 
+  /acorn@8.12.1:
+    resolution: {integrity: sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==}
+    engines: {node: '>=0.4.0'}
+    hasBin: true
+    dev: true
+
   /ajv@6.12.6:
     resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==}
     dependencies:
@@ -1797,13 +1850,12 @@ packages:
       uri-js: 4.4.1
     dev: true
 
-  /alova@3.0.0-beta.3:
-    resolution: {integrity: sha512-fpietXM0T0/z8vgfyZ1PSM1R+/x6zJY4/PEHTI2JdSqKX3CVaSbzSib81HZMdgu2K60a8XHCDzjKBX97x3vF0Q==}
+  /alova@3.0.5:
+    resolution: {integrity: sha512-cOE2nTPOp7sXLhf9cthdh90lT389C1akgJULMytuFeV1loriJr1YbT3LCw3qb/P3N+o/QtJ9WLH2ccr0vJ380A==}
     engines: {node: '>= 18.0.0'}
     dependencies:
-      '@alova/shared': 1.0.0-beta.2
-      '@node-ipc/node-ipc': 11.0.3
-    dev: false
+      '@alova/shared': 1.0.4
+      rate-limiter-flexible: 5.0.3
 
   /ansi-regex@5.0.1:
     resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==}
@@ -1889,6 +1941,14 @@ packages:
       pathe: 1.1.2
     dev: true
 
+  /ast-kit@1.0.1:
+    resolution: {integrity: sha512-XdXKlmX3YIrGKJS7d324CAbswH+C1klMCIRQ4VRy0+iPxGeP2scVOoYd09/V6uGjGAi/ZuEwBLzT7xBerSKNQg==}
+    engines: {node: '>=16.14.0'}
+    dependencies:
+      '@babel/parser': 7.25.3
+      pathe: 1.1.2
+    dev: true
+
   /ast-walker-scope@0.6.1:
     resolution: {integrity: sha512-0ZdQEsSfH3mX4BFbRCc3xOBjx5bDbm73+aAdQOHerPQNf8K0XFMAv79ucd2BpnSc4UMyvBDixiroT8yjm2Y6bw==}
     engines: {node: '>=16.14.0'}
@@ -2176,11 +2236,6 @@ packages:
     resolution: {integrity: sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==}
     dev: true
 
-  /easy-stack@1.0.1:
-    resolution: {integrity: sha512-wK2sCs4feiiJeFXn3zvY0p41mdU5VUgbgs1rNsc/y5ngFUijdWd+iIN8eoyuZHKB8xN6BL4PdWmzqFmxNg6V2w==}
-    engines: {node: '>=6.0.0'}
-    dev: false
-
   /electron-to-chromium@1.4.810:
     resolution: {integrity: sha512-Kaxhu4T7SJGpRQx99tq216gCq2nMxJo+uuT6uzz9l8TVN2stL7M06MIIXAtr9jsrLs2Glflgf2vMQRepxawOdQ==}
     dev: true
@@ -2189,6 +2244,11 @@ packages:
     resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==}
     engines: {node: '>=0.12'}
 
+  /entities@5.0.0:
+    resolution: {integrity: sha512-BeJFvFRJddxobhvEdm5GqHzRV/X+ACeuw0/BuuxsCh1EUZcAIz8+kYmBp/LrQuloy6K1f3a0M7+IhmZ7QnkISA==}
+    engines: {node: '>=0.12'}
+    dev: true
+
   /error-stack-parser-es@0.1.4:
     resolution: {integrity: sha512-l0uy0kAoo6toCgVOYaAayqtPa2a1L15efxUMEnQebKwLQX2X0OpS6wMMQdc4juJXmxd9i40DuaUHq+mjIya9TQ==}
     dev: true
@@ -2759,11 +2819,6 @@ packages:
     hasBin: true
     dev: true
 
-  /js-message@1.0.7:
-    resolution: {integrity: sha512-efJLHhLjIyKRewNS9EGZ4UpI8NguuL6fKkhRxVuMmrGV2xN/0APGdQYwLFky5w9naebSZ0OwAGp0G6/2Cg90rA==}
-    engines: {node: '>=0.6.0'}
-    dev: false
-
   /js-tokens@4.0.0:
     resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
 
@@ -2872,8 +2927,8 @@ packages:
       yallist: 3.1.1
     dev: true
 
-  /magic-string-ast@0.6.1:
-    resolution: {integrity: sha512-eczKQUDaBpB/mcEqZZNGEUG1FQNsXCuk3uOrCpu6y7qTygIy6jnpqDa62j9MGKSoqlXhM1lCFQv1THuGDQtvUA==}
+  /magic-string-ast@0.6.2:
+    resolution: {integrity: sha512-oN3Bcd7ZVt+0VGEs7402qR/tjgjbM7kPlH/z7ufJnzTLVBzXJITRHOJiwMmmYMgZfdoWQsfQcY+iKlxiBppnMA==}
     engines: {node: '>=16.14.0'}
     dependencies:
       magic-string: 0.30.10
@@ -3161,6 +3216,14 @@ packages:
     hasBin: true
     dev: true
 
+  /pinia-plugin-persistedstate@3.2.1(pinia@2.1.7):
+    resolution: {integrity: sha512-MK++8LRUsGF7r45PjBFES82ISnPzyO6IZx3CH5vyPseFLZCk1g2kgx6l/nW8pEBKxxd4do0P6bJw+mUSZIEZUQ==}
+    peerDependencies:
+      pinia: ^2.0.0
+    dependencies:
+      pinia: 2.1.7(typescript@5.4.5)(vue@3.4.30)
+    dev: false
+
   /pinia@2.1.7(typescript@5.4.5)(vue@3.4.30):
     resolution: {integrity: sha512-+C2AHFtcFqjPih0zpYuvof37SFxMQ7OEG2zV9jRI12i9BOy3YQVAHwdKtyyc8pDcDyIc33WCIsZaCFWU7WWxGQ==}
     peerDependencies:
@@ -3203,6 +3266,15 @@ packages:
       picocolors: 1.0.1
       source-map-js: 1.2.0
 
+  /postcss@8.4.41:
+    resolution: {integrity: sha512-TesUflQ0WKZqAvg52PWL6kHgLKP6xB6heTOdoYM0Wt2UHyxNa4K25EZZMgKns3BH1RLVbZCREPpLY0rhnNoHVQ==}
+    engines: {node: ^10 || ^12 || >=14}
+    dependencies:
+      nanoid: 3.3.7
+      picocolors: 1.0.1
+      source-map-js: 1.2.0
+    dev: true
+
   /prelude-ls@1.2.1:
     resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==}
     engines: {node: '>= 0.8.0'}
@@ -3230,6 +3302,9 @@ packages:
     resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
     dev: true
 
+  /rate-limiter-flexible@5.0.3:
+    resolution: {integrity: sha512-lWx2y8NBVlTOLPyqs+6y7dxfEpT6YFqKy3MzWbCy95sTTOhOuxufP2QvRyOHpfXpB9OUJPbVLybw3z3AVAS5fA==}
+
   /read-package-json-fast@3.0.2:
     resolution: {integrity: sha512-0J+Msgym3vrLOUB3hzQCuZHII0xkNGCtz/HJH9xZshwv9DbDwkw1KaE3gx/e2J5rpEY5rtOy6cyhKOPrkP7FZw==}
     engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
@@ -3432,11 +3507,6 @@ packages:
       js-tokens: 9.0.0
     dev: true
 
-  /strong-type@1.1.0:
-    resolution: {integrity: sha512-X5Z6riticuH5GnhUyzijfDi1SoXas8ODDyN7K8lJeQK+Jfi4dKdoJGL4CXTskY/ATBcN+rz5lROGn1tAUkOX7g==}
-    engines: {node: '>=12.21.0'}
-    dev: false
-
   /stylis@4.3.2:
     resolution: {integrity: sha512-bhtUjWd/z6ltJiQwg0dUfxEJ+W+jdqQd8TbWLWyeIJHlnsqmGLRFFd8e5mA0AZi/zx90smXRlN66YMTcaSFifg==}
     dev: false
@@ -3575,7 +3645,7 @@ packages:
     engines: {node: '>= 10.0.0'}
     dev: true
 
-  /unocss@0.61.0(postcss@8.4.38)(vite@5.3.1):
+  /unocss@0.61.0(postcss@8.4.41)(vite@5.3.1):
     resolution: {integrity: sha512-7642v5tHpEpHO9dl9sqYbKT/Ri4X4lmGHhj/znE4uheEfXcptPPiZ1/hVmQVciHUSI8CnQBqDwkZuxNPDG3bTQ==}
     engines: {node: '>=14'}
     peerDependencies:
@@ -3591,7 +3661,7 @@ packages:
       '@unocss/cli': 0.61.0
       '@unocss/core': 0.61.0
       '@unocss/extractor-arbitrary-variants': 0.61.0
-      '@unocss/postcss': 0.61.0(postcss@8.4.38)
+      '@unocss/postcss': 0.61.0(postcss@8.4.41)
       '@unocss/preset-attributify': 0.61.0
       '@unocss/preset-icons': 0.61.0
       '@unocss/preset-mini': 0.61.0
@@ -3668,17 +3738,17 @@ packages:
       - supports-color
     dev: true
 
-  /unplugin-vue-router@0.10.0(vue-router@4.4.0)(vue@3.4.30):
-    resolution: {integrity: sha512-t9cwRvNONcrh7CZLUYrd4kGOH4xZRhsHeT+exaAuYFn7z87pkTHiHh3wBnGerfKGs22SnmJIIjcKyEa62CO+4w==}
+  /unplugin-vue-router@0.10.2(vue-router@4.4.0)(vue@3.4.30):
+    resolution: {integrity: sha512-aG1UzB96cu4Lu+EdQxl22NIKFrde5b+k568JdsaJ2gzPqnQufPk2j1gCA5DxFfGz9zg4tYTqy2A2JHForVbyXg==}
     peerDependencies:
       vue-router: ^4.4.0
     peerDependenciesMeta:
       vue-router:
         optional: true
     dependencies:
-      '@babel/types': 7.24.7
+      '@babel/types': 7.25.2
       '@rollup/pluginutils': 5.1.0
-      '@vue-macros/common': 1.10.4(vue@3.4.30)
+      '@vue-macros/common': 1.12.2(vue@3.4.30)
       ast-walker-scope: 0.6.1
       chokidar: 3.6.0
       fast-glob: 3.3.2
@@ -3687,9 +3757,9 @@ packages:
       mlly: 1.7.1
       pathe: 1.1.2
       scule: 1.3.0
-      unplugin: 1.10.1
+      unplugin: 1.12.0
       vue-router: 4.4.0(vue@3.4.30)
-      yaml: 2.4.5
+      yaml: 2.5.0
     transitivePeerDependencies:
       - rollup
       - vue
@@ -3705,6 +3775,16 @@ packages:
       webpack-virtual-modules: 0.6.2
     dev: true
 
+  /unplugin@1.12.0:
+    resolution: {integrity: sha512-KeczzHl2sATPQUx1gzo+EnUkmN4VmGBYRRVOZSGvGITE9rGHRDGqft6ONceP3vgXcyJ2XjX5axG5jMWUwNCYLw==}
+    engines: {node: '>=14.0.0'}
+    dependencies:
+      acorn: 8.12.1
+      chokidar: 3.6.0
+      webpack-sources: 3.2.3
+      webpack-virtual-modules: 0.6.2
+    dev: true
+
   /update-browserslist-db@1.0.16(browserslist@4.23.1):
     resolution: {integrity: sha512-KVbTxlBYlckhF5wgfyZXTWnMn7MMZjMu9XG8bPlliUOP9ThaF4QnhP8qrjrH7DRzHfSk0oQv1wToW+iA5GajEQ==}
     hasBin: true
@@ -3992,8 +4072,8 @@ packages:
     resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==}
     dev: true
 
-  /yaml@2.4.5:
-    resolution: {integrity: sha512-aBx2bnqDzVOyNKfsysjA2ms5ZlnjSAW2eG3/L5G/CSujfjLJTJsEw1bGw8kCf04KodQWk1pxlGnZ56CRxiawmg==}
+  /yaml@2.5.0:
+    resolution: {integrity: sha512-2wWLbGbYDiSqqIKoPjar3MPgB94ErzCtrNE1FdqGuaO0pi2JGjmE8aW8TDZwzU7vuxcGRdL/4gPQwQ7hD5AMSw==}
     engines: {node: '>= 14'}
     hasBin: true
     dev: true

+ 2 - 1
src/App.vue

@@ -1,7 +1,8 @@
 <script setup lang="ts">
+import theme from '@/themes'
 </script>
 <template>
-  <a-config-provider>
+  <a-config-provider :theme="theme">
     <RouterView name="header" />
     <RouterView />
   </a-config-provider>

BIN
src/assets/images/name.png


BIN
src/assets/images/page-login-form.bg.png


BIN
src/assets/images/page-login.bg.jpg


+ 5 - 0
src/model/account.model.ts

@@ -0,0 +1,5 @@
+export interface AccountFormState {
+  username: string;
+  password: string;
+  remember?: boolean;
+}

+ 2 - 0
src/model/index.ts

@@ -0,0 +1,2 @@
+export * from './account.model'
+export * from './people.model'

+ 13 - 0
src/model/people.model.ts

@@ -0,0 +1,13 @@
+export interface PeopleModel {
+  id: string;
+  name: string;
+  avatar?: string;
+}
+
+export function transformAccount(data: any): PeopleModel {
+  return {
+    id: data?.userId,
+    name: data?.nickName,
+    avatar: data?.avatar,
+  };
+}

+ 59 - 0
src/pages/index.vue

@@ -0,0 +1,59 @@
+<script setup lang="ts">
+import router              from '@/router';
+import { useAccountStore } from '@/stores';
+import { LogoutOutlined }  from '@ant-design/icons-vue';
+import { useElementSize }  from '@vueuse/core';
+
+import { storeToRefs } from 'pinia';
+
+
+const title = import.meta.env.SIX_TITLE;
+const Account = useAccountStore();
+const { local } = storeToRefs(Account);
+
+const titleRef = ref();
+const { width } = useElementSize(titleRef, void 0, { box: 'border-box' });
+
+function handleLogout() {
+  Account.$reset();
+  router.replace({ path: '/login' });
+}
+</script>
+<template>
+  <div class="page-container flex flex-col h-vh w-vw">
+    <header class="flex-none flex items-center h-60px">
+      <div ref="titleRef" class="title flex-none">{{ title }}</div>
+      <div class="menus flex-auto"></div>
+      <div class="account">
+        <a-avatar size="large" :src="local?.avatar">{{ local?.name?.slice(0, 1) }}</a-avatar>
+        <span class="m-x-2">{{ local?.name }}</span>
+        <LogoutOutlined class="cursor-pointer" @click="handleLogout" />
+      </div>
+    </header>
+    <main class="flex-auto flex flex-row">
+      <RouterView name="aside" class="flex-none" :style="{width: `${width}px`}" />
+      <RouterView class="flex-auto" />
+    </main>
+  </div>
+</template>
+<style scoped lang="scss">
+.page-container {
+  > header {
+    background-color: var(--color-primary);
+    padding-right: 12px;
+    color: #fff;
+
+    > .title {
+      min-width: 256px;
+      color: #fff;
+      font-size: 24px;
+      font-weight: 700;
+      letter-spacing: 0.2em;
+      text-align: center;
+    }
+  }
+  > main {
+    --page-main-container: calc(100vh - 60px);
+  }
+}
+</style>

+ 95 - 0
src/pages/login.vue

@@ -0,0 +1,95 @@
+<script setup lang="ts">
+import type { AccountFormState }      from '@/model';
+import { accountMethod, loginMethod } from '@/request/api/account.api';
+import router                         from '@/router';
+import { useAccountStore }            from '@/stores';
+import { LockOutlined, UserOutlined } from '@ant-design/icons-vue';
+import { useSerialRequest }           from 'alova/client';
+import { Form }                       from 'ant-design-vue';
+import { h }                          from 'vue';
+
+
+const formState = reactive<AccountFormState>({
+  username: '',
+  password: '',
+  remember: false,
+});
+const rulesRef = reactive({
+  username: [ { required: true, message: '请输入账号' } ],
+  password: [ { required: true, message: '请输入密码' } ],
+});
+
+const { validate, validateInfos } = Form.useForm(formState, rulesRef);
+
+const formWrapper = ref<HTMLElement>();
+
+const { loading, error, send } = useSerialRequest([
+  () => loginMethod(formState),
+  token => accountMethod(token),
+], { immediate: false }).onSuccess(
+  ({ data }) => {
+    useAccountStore().$init(data);
+    router.replace({ path: '/' });
+  },
+);
+
+async function handle() {
+  try {
+    await validate();
+    await send();
+  } catch ( error: any ) {
+    const field = error?.errorFields?.[ 0 ];
+    if ( field ) formWrapper.value?.querySelector<HTMLInputElement>(`#${ field.name }`)?.focus();
+  }
+}
+</script>
+<template>
+  <div class="page-container flex flex-col h-vh w-vw">
+    <header class="flex-none h-60px text-center">
+      <img src="@/assets/images/name.png" alt="中医健康管家" class="h-full">
+    </header>
+    <main class="flex-auto flex justify-center items-center">
+      <div ref="formWrapper" class="form-wrapper flex flex-col justify-center">
+        <a-form layout="vertical" @submit="handle">
+          <a-form-item label="账号" html-for="username" v-bind="validateInfos.username">
+            <a-input id="username" size="large" :prefix="h(UserOutlined)" :disabled="loading"
+                     v-model:value="formState.username"
+            />
+          </a-form-item>
+          <a-form-item label="密码" html-for="password" v-bind="validateInfos.password">
+            <a-input-password id="password" size="large" :prefix="h(LockOutlined)" :disabled="loading"
+                              v-model:value="formState.password"
+            />
+          </a-form-item>
+          <a-form-item style="margin-top: 38px;" :help="error?.message" validate-status="error">
+            <a-button type="primary" block html-type="submit" :loading="loading">登录</a-button>
+          </a-form-item>
+        </a-form>
+      </div>
+    </main>
+  </div>
+</template>
+<style scoped lang="scss">
+.page-container {
+  background: #000 url("@/assets/images/page-login.bg.jpg") no-repeat center center / cover;
+}
+
+.form-wrapper {
+  width: calc(100vmin - 60px * 2);
+  height: calc(100vmin - 60px * 2);
+  max-width: 800px;
+  max-height: 800px;
+  padding: 10%;
+  background: url("@/assets/images/page-login-form.bg.png") no-repeat center / contain;
+
+  :deep(.ant-form-item-label) {
+    color: #fff;
+    font-size: 18px;
+
+    label {
+      color: inherit;
+      font-size: inherit;
+    }
+  }
+}
+</style>

+ 55 - 0
src/request/alova.ts

@@ -0,0 +1,55 @@
+import { createAlova }  from 'alova';
+import fetchAdapter     from 'alova/fetch';
+import VueHook          from 'alova/vue';
+import { notification } from 'ant-design-vue';
+import mockAdapter      from './mock';
+
+
+const request = createAlova({
+  baseURL: import.meta.env.BASE_URL,
+  statesHook: VueHook,
+  requestAdapter: import.meta.env.DEV ? mockAdapter() : fetchAdapter(),
+
+  responded: {
+    async onSuccess(response, method) {
+      let ignoreException = false;
+      try {
+        if ( response.status >= 400 ) throw new Error(`${ response.statusText }(${ response.status })`);
+
+        const result = await response.json();
+        /* 接口修正 code */
+        if ( result.success !== false && result.code === 200 ) result.code = 0;
+        const {
+          error = false, warn = false, code = error || warn ? -1 : 0,
+          msg: message = '未知错误',
+          data, total, rows, ..._data
+        } = result;
+        if ( code === 0 ) {
+          return method.meta?.unconvert
+                 ? { ..._data, data }
+                 : total != null && Array.isArray(rows)
+                   ? { total, data: rows }
+                   : data;
+        } else if ( warn ) {
+          ignoreException = true;
+          notification.warning({
+            message: method.url,
+            description: `${ message }(${ code })`,
+            key: method.url,
+          });
+        }
+        throw new Error(`${ message }(${ code })`);
+      } catch ( e: any ) {
+        if ( !ignoreException && !method.meta?.ignoreException ) {
+          notification.error({
+            message: method.url,
+            description: e?.message,
+            key: method.url,
+          });
+        }
+        throw e;
+      }
+    },
+  },
+});
+export default request;

+ 25 - 0
src/request/api/account.api.ts

@@ -0,0 +1,25 @@
+import { type AccountFormState, transformAccount } from '@/model';
+import request                                     from '@/request/alova';
+
+
+export function loginMethod(data: AccountFormState) {
+  return request.Post<string, { access_token: string }>(`/prod-api/auth/login`, data, {
+    transform(data, headers) {
+      return `Bearer ${ data.access_token }`;
+    },
+  });
+}
+
+export function accountMethod(token: string) {
+  return request.Get(`/prod-api/system/user/getInfo`, {
+    headers: { Authorization: token },
+    transform(data: any, headers) {
+      console.log('accountMethod', data);
+      return {
+        token,
+        local: transformAccount(data.user),
+      };
+    },
+    meta: { unconvert: true },
+  });
+}

+ 15 - 0
src/request/mock/index.ts

@@ -0,0 +1,15 @@
+import { createAlovaMockAdapter } from '@alova/mock';
+import adapterFetch               from 'alova/fetch';
+
+
+export default function mockAdapter() {
+  return createAlovaMockAdapter(
+    [],
+    {
+      enable: true,
+      httpAdapter: adapterFetch(),
+      delay: 1000,
+      mockRequestLogger: true,
+    },
+  );
+}

+ 20 - 0
src/stores/account.store.ts

@@ -0,0 +1,20 @@
+import type { PeopleModel } from '@/model';
+import { defineStore }      from 'pinia';
+
+
+export const useAccountStore = defineStore('account', () => {
+  const token = ref<string>();
+  const local = ref<PeopleModel | null>();
+
+  const $init = (data: { token: string; local: PeopleModel | null }) => {
+    token.value = data.token;
+    local.value = data.local;
+  };
+
+  const $reset = () => {
+    token.value = '';
+    local.value = null;
+  };
+
+  return { token, local, $init, $reset };
+});

+ 8 - 0
src/stores/index.ts

@@ -1,6 +1,14 @@
 import { createPinia } from 'pinia';
+import { createPersistedState } from 'pinia-plugin-persistedstate';
 
+const persistedState = createPersistedState({
+  key: (storeKey) => `${import.meta.env.SIX_APP_NAME ?? '@six/unknown'}:${storeKey}`,
+  auto: true,
+  debug: import.meta.env.DEV,
+});
 
 const pinia = createPinia();
+pinia.use(persistedState);
 
 export default pinia;
+export { useAccountStore } from './account.store';

+ 3 - 0
src/themes/index.scss

@@ -0,0 +1,3 @@
+@import url("@unocss/reset/normalize.css");
+@import url("ant-design-vue/dist/reset.css");
+@import url("vue-virtual-scroller/dist/vue-virtual-scroller.css");

+ 16 - 0
src/themes/index.ts

@@ -0,0 +1,16 @@
+import { camelToKebabCase } from '@/tools/string';
+
+
+const theme = {
+  token: {
+    colorPrimary: '#42b7fd',
+  },
+};
+
+const tokenCSS = Object.entries(theme.token).map(([ key, value ]) => `--${ camelToKebabCase(key) }: ${ value }`).join('\n');
+const el = document.createElement('style');
+el.innerText = `:root {${ tokenCSS }}`;
+document.head.appendChild(el);
+
+export default theme;
+

+ 3 - 0
src/tools/string.ts

@@ -0,0 +1,3 @@
+export function camelToKebabCase(string: string): string {
+  return string.replace(/([a-z0-9])([A-Z])/g, '$1-$2').toLowerCase();
+}

+ 12 - 3
tsconfig.app.json

@@ -1,13 +1,22 @@
 {
   "extends": "@vue/tsconfig/tsconfig.dom.json",
-  "include": ["@types/**/*.d.ts", "src/**/*", "src/**/*.vue"],
-  "exclude": ["src/**/__tests__/*"],
+  "include": [
+    "@types/**/*.d.ts",
+    "src/**/*.d.ts",
+    "src/**/*",
+    "src/**/*.vue"
+  ],
+  "exclude": [
+    "src/**/__tests__/*"
+  ],
   "compilerOptions": {
     "composite": true,
     "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
     "baseUrl": ".",
     "paths": {
-      "@/*": ["./src/*"]
+      "@/*": [
+        "./src/*"
+      ]
     }
   }
 }

+ 1 - 0
tsconfig.node.json

@@ -11,6 +11,7 @@
     "composite": true,
     "noEmit": true,
     "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
+
     "module": "ESNext",
     "moduleResolution": "Bundler",
     "types": ["node"]

+ 19 - 2
vite.config.ts

@@ -12,15 +12,20 @@ import Components               from 'unplugin-vue-components/vite';
 import { VueRouterAutoImports } from 'unplugin-vue-router';
 import VueRouter                from 'unplugin-vue-router/vite';
 
-import { defineConfig } from 'vite';
+import { defineConfig, loadEnv } from 'vite';
 import vueDevTools      from 'vite-plugin-vue-devtools';
 
 
 const dts = `./@types/` as const;
 
 // https://vitejs.dev/config/
-export default defineConfig((env) => {
+export default defineConfig((configEnv) => {
+  const envDir = './.env';
+  const env = loadEnv(configEnv.mode, envDir, 'REQUEST_');
+
   return {
+    envDir,
+    envPrefix: 'SIX_',
     plugins: [
       VueRouter({
         routesFolder: [
@@ -53,5 +58,17 @@ export default defineConfig((env) => {
         '@': fileURLToPath(new URL('./src', import.meta.url)),
       },
     },
+    server:{
+      host: true,
+      open: true,
+      proxy: {
+        '/prod-api': {
+          target: env.REQUEST_API_PROXY_URL,
+          secure: false,
+          changeOrigin: false,
+          logLevel: 'debug',
+        },
+      },
+    }
   };
 });