Commit a47d42d0 authored by tyyin lan's avatar tyyin lan

feat: 登录页面

parent 6934461d
......@@ -3,7 +3,7 @@ VITE_APP_ENV = 'DEV'
VITE_APP_NAME = 'DIGITAL_PERSON_FE'
VITE_APP_THEME_COLOR = '#2468f2'
VITE_PORT = 8848
VITE_PUBLIC_PATH = /
VITE_ROUTER_MODE = 'hash'
VITE_PUBLIC_PATH = /fe
VITE_ROUTER_MODE = 'h5'
VITE_VITEST = true
VITE_HIDE_HOME = false
......@@ -68,6 +68,7 @@ export default [
'@typescript-eslint/array-type': 'error',
'@unocss/order': 'off',
'unocss/order': 'off',
},
},
......
......@@ -16,13 +16,16 @@
"preinstall": "npx only-allow pnpm"
},
"dependencies": {
"@icon-park/vue-next": "^1.4.2",
"@iconify/vue": "^4.1.2",
"@unocss/reset": "^0.61.9",
"@vueuse/core": "^10.11.1",
"axios": "^1.7.7",
"nanoid": "^5.0.7",
"pinia": "^2.2.2",
"vue": "^3.5.3",
"spark-md5": "^3.0.2",
"validator": "^13.12.0",
"vue": "^3.5.5",
"vue-router": "^4.4.3"
},
"devDependencies": {
......@@ -30,6 +33,8 @@
"@commitlint/config-conventional": "^19.4.1",
"@commitlint/types": "^19.0.3",
"@types/node": "^20.16.5",
"@types/spark-md5": "^3.0.4",
"@types/validator": "^13.12.1",
"@typescript-eslint/parser": "^7.18.0",
"@unocss/eslint-config": "^0.61.9",
"@vitejs/plugin-vue": "^4.6.2",
......
......@@ -8,15 +8,18 @@ importers:
.:
dependencies:
'@icon-park/vue-next':
specifier: ^1.4.2
version: 1.4.2(vue@3.5.5(typescript@5.5.4))
'@iconify/vue':
specifier: ^4.1.2
version: 4.1.2(vue@3.5.3(typescript@5.5.4))
version: 4.1.2(vue@3.5.5(typescript@5.5.4))
'@unocss/reset':
specifier: ^0.61.9
version: 0.61.9
'@vueuse/core':
specifier: ^10.11.1
version: 10.11.1(vue@3.5.3(typescript@5.5.4))
version: 10.11.1(vue@3.5.5(typescript@5.5.4))
axios:
specifier: ^1.7.7
version: 1.7.7
......@@ -25,13 +28,19 @@ importers:
version: 5.0.7
pinia:
specifier: ^2.2.2
version: 2.2.2(typescript@5.5.4)(vue@3.5.3(typescript@5.5.4))
version: 2.2.2(typescript@5.5.4)(vue@3.5.5(typescript@5.5.4))
spark-md5:
specifier: ^3.0.2
version: 3.0.2
validator:
specifier: ^13.12.0
version: 13.12.0
vue:
specifier: ^3.5.3
version: 3.5.3(typescript@5.5.4)
specifier: ^3.5.5
version: 3.5.5(typescript@5.5.4)
vue-router:
specifier: ^4.4.3
version: 4.4.3(vue@3.5.3(typescript@5.5.4))
version: 4.4.3(vue@3.5.5(typescript@5.5.4))
devDependencies:
'@commitlint/cli':
specifier: ^19.4.1
......@@ -45,6 +54,12 @@ importers:
'@types/node':
specifier: ^20.16.5
version: 20.16.5
'@types/spark-md5':
specifier: ^3.0.4
version: 3.0.4
'@types/validator':
specifier: ^13.12.1
version: 13.12.1
'@typescript-eslint/parser':
specifier: ^7.18.0
version: 7.18.0(eslint@9.9.1(jiti@1.21.6))(typescript@5.5.4)
......@@ -53,7 +68,7 @@ importers:
version: 0.61.9(eslint@9.9.1(jiti@1.21.6))(typescript@5.5.4)
'@vitejs/plugin-vue':
specifier: ^4.6.2
version: 4.6.2(vite@5.4.3(@types/node@20.16.5)(sass@1.78.0))(vue@3.5.3(typescript@5.5.4))
version: 4.6.2(vite@5.4.3(@types/node@20.16.5)(sass@1.78.0))(vue@3.5.5(typescript@5.5.4))
autoprefixer:
specifier: ^10.4.20
version: 10.4.20(postcss@8.4.45)
......@@ -80,7 +95,7 @@ importers:
version: 15.2.10
naive-ui:
specifier: ^2.39.0
version: 2.39.0(vue@3.5.3(typescript@5.5.4))
version: 2.39.0(vue@3.5.5(typescript@5.5.4))
postcss:
specifier: ^8.4.45
version: 8.4.45
......@@ -131,10 +146,10 @@ importers:
version: 0.61.9(postcss@8.4.45)(rollup@4.21.2)(vite@5.4.3(@types/node@20.16.5)(sass@1.78.0))
unplugin-auto-import:
specifier: ^0.17.8
version: 0.17.8(@vueuse/core@10.11.1(vue@3.5.3(typescript@5.5.4)))(rollup@4.21.2)(webpack-sources@3.2.3)
version: 0.17.8(@vueuse/core@10.11.1(vue@3.5.5(typescript@5.5.4)))(rollup@4.21.2)(webpack-sources@3.2.3)
unplugin-vue-components:
specifier: ^0.26.0
version: 0.26.0(@babel/parser@7.25.6)(rollup@4.21.2)(vue@3.5.3(typescript@5.5.4))(webpack-sources@3.2.3)
version: 0.26.0(@babel/parser@7.25.6)(rollup@4.21.2)(vue@3.5.5(typescript@5.5.4))(webpack-sources@3.2.3)
vite:
specifier: ^5.4.3
version: 5.4.3(@types/node@20.16.5)(sass@1.78.0)
......@@ -721,6 +736,12 @@ packages:
resolution: {integrity: sha512-d2CGZR2o7fS6sWB7DG/3a95bGKQyHMACZ5aW8qGkkqQpUoZV6C0X7Pc7l4ZNMZkfNBf4VWNe9E1jRsf0G146Ew==}
engines: {node: '>=18.18'}
'@icon-park/vue-next@1.4.2':
resolution: {integrity: sha512-+QklF255wkfBOabY+xw6FAI0Bwln/RhdwCunNy/9sKdKuChtaU67QZqU67KGAvZUTeeBgsL+yaHHxqfQeGZXEQ==}
engines: {node: '>= 8.0.0', npm: '>= 5.0.0'}
peerDependencies:
vue: 3.x
'@iconify/types@2.0.0':
resolution: {integrity: sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==}
......@@ -888,6 +909,12 @@ packages:
'@types/node@20.16.5':
resolution: {integrity: sha512-VwYCweNo3ERajwy0IUlqqcyZ8/A7Zwa9ZP3MnENWcB11AejO+tLy3pu850goUW2FC/IJMdZUfKpX/yxL1gymCA==}
'@types/spark-md5@3.0.4':
resolution: {integrity: sha512-qtOaDz+IXiNndPgYb6t1YoutnGvFRtWSNzpVjkAPCfB2UzTyybuD4Tjgs7VgRawum3JnJNRwNQd4N//SvrHg1Q==}
'@types/validator@13.12.1':
resolution: {integrity: sha512-w0URwf7BQb0rD/EuiG12KP0bailHKHP5YVviJG9zw3ykAokL0TuxU2TUqMB7EwZ59bDHYdeTIvjI5m0S7qHfOA==}
'@types/web-bluetooth@0.0.20':
resolution: {integrity: sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow==}
......@@ -1059,17 +1086,17 @@ packages:
'@volar/typescript@2.4.2':
resolution: {integrity: sha512-m2uZduhaHO1SZuagi30OsjI/X1gwkaEAC+9wT/nCNAtJ5FqXEkKvUncHmffG7ESDZPlFFUBK4vJ0D9Hfr+f2EA==}
'@vue/compiler-core@3.5.3':
resolution: {integrity: sha512-adAfy9boPkP233NTyvLbGEqVuIfK/R0ZsBsIOW4BZNfb4BRpRW41Do1u+ozJpsb+mdoy80O20IzAsHaihRb5qA==}
'@vue/compiler-core@3.5.5':
resolution: {integrity: sha512-ZrxcY8JMoV+kgDrmRwlDufz0SjDZ7jfoNZiIBluAACMBmgr55o/jTbxnyrccH6VSJXnFaDI4Ik1UFCiq9r8i7w==}
'@vue/compiler-dom@3.5.3':
resolution: {integrity: sha512-wnzFArg9zpvk/811CDOZOadJRugf1Bgl/TQ3RfV4nKfSPok4hi0w10ziYUQR6LnnBAUlEXYLUfZ71Oj9ds/+QA==}
'@vue/compiler-dom@3.5.5':
resolution: {integrity: sha512-HSvK5q1gmBbxRse3S0Wt34RcKuOyjDJKDDMuF3i7NC+QkDFrbAqw8NnrEm/z7zFDxWZa4/5eUwsBOMQzm1RHBA==}
'@vue/compiler-sfc@3.5.3':
resolution: {integrity: sha512-P3uATLny2tfyvMB04OQFe7Sczteno7SLFxwrOA/dw01pBWQHB5HL15a8PosoNX2aG/EAMGqnXTu+1LnmzFhpTQ==}
'@vue/compiler-sfc@3.5.5':
resolution: {integrity: sha512-MzBHDxwZhgQPHrwJ5tj92gdTYRCuPDSZr8PY3+JFv8cv2UD5/WayH5yo0kKCkKfrtJhc39jNSMityHrkMSbfnA==}
'@vue/compiler-ssr@3.5.3':
resolution: {integrity: sha512-F/5f+r2WzL/2YAPl7UlKcJWHrvoZN8XwEBLnT7S4BXwncH25iDOabhO2M2DWioyTguJAGavDOawejkFXj8EM1w==}
'@vue/compiler-ssr@3.5.5':
resolution: {integrity: sha512-oFasHnpv/upubjJEmqiTKQYb4qS3ziJddf4UVWuFw6ebk/QTrTUc+AUoTJdo39x9g+AOQBzhOU0ICCRuUjvkmw==}
'@vue/compiler-vue2@2.7.16':
resolution: {integrity: sha512-qYC3Psj9S/mfu9uVi5WvNZIzq+xnXMhOwbTFKKDD7b1lhpnn71jXSFdTQ+WsIEk0ONCd7VV2IMm7ONl6tbQ86A==}
......@@ -1085,22 +1112,22 @@ packages:
typescript:
optional: true
'@vue/reactivity@3.5.3':
resolution: {integrity: sha512-2w61UnRWTP7+rj1H/j6FH706gRBHdFVpIqEkSDAyIpafBXYH8xt4gttstbbCWdU3OlcSWO8/3mbKl/93/HSMpw==}
'@vue/reactivity@3.5.5':
resolution: {integrity: sha512-V4tTWElZQhT73PSK3Wnax9R9m4qvMX+LeKHnfylZc6SLh4Jc5/BPakp6e3zEhKWi5AN8TDzRkGnLkp8OqycYng==}
'@vue/runtime-core@3.5.3':
resolution: {integrity: sha512-5b2AQw5OZlmCzSsSBWYoZOsy75N4UdMWenTfDdI5bAzXnuVR7iR8Q4AOzQm2OGoA41xjk53VQKrqQhOz2ktWaw==}
'@vue/runtime-core@3.5.5':
resolution: {integrity: sha512-2/CFaRN17jgsXy4MpigWFBCAMmLkXPb4CjaHrndglwYSra7ajvkH2cat21dscuXaH91G8fXAeg5gCyxWJ+wCRA==}
'@vue/runtime-dom@3.5.3':
resolution: {integrity: sha512-wPR1DEGc3XnQ7yHbmkTt3GoY0cEnVGQnARRdAkDzZ8MbUKEs26gogCQo6AOvvgahfjIcnvWJzkZArQ1fmWjcSg==}
'@vue/runtime-dom@3.5.5':
resolution: {integrity: sha512-0bQGgCuL+4Muz5PsCLgF4Ata9BTdhHi5VjsxtTDyI0Wy4MgoSvBGaA6bDc7W7CGgZOyirf9LNeetMYHQ05pgpw==}
'@vue/server-renderer@3.5.3':
resolution: {integrity: sha512-28volmaZVG2PGO3V3+gBPKoSHvLlE8FGfG/GKXKkjjfxLuj/50B/0OQGakM/g6ehQeqCrZYM4eHC4Ks48eig1Q==}
'@vue/server-renderer@3.5.5':
resolution: {integrity: sha512-XjRamLIq5f47cxgy+hiX7zUIY+4RHdPDVrPvvMDAUTdW5RJWX/S0ji/rCbm3LWTT/9Co9bvQME8ZI15ahL4/Qw==}
peerDependencies:
vue: 3.5.3
vue: 3.5.5
'@vue/shared@3.5.3':
resolution: {integrity: sha512-Jp2v8nylKBT+PlOUjun2Wp/f++TfJVFjshLzNtJDdmFJabJa7noGMncqXRM1vXGX+Yo2V7WykQFNxusSim8SCA==}
'@vue/shared@3.5.5':
resolution: {integrity: sha512-0KyMXyEgnmFAs6rNUL+6eUHtUCqCaNrVd+AW3MX3LyA0Yry5SA0Km03CDKiOua1x1WWnIr+W9+S0GMFoSDWERQ==}
'@vueuse/core@10.11.1':
resolution: {integrity: sha512-guoy26JQktXPcz+0n3GukWIy/JDNKti9v6VEMu6kV2sYBsWuGiTU8OWdg+ADfUbHg3/3DlqySDe7JmdHrktiww==}
......@@ -2471,10 +2498,17 @@ packages:
resolution: {integrity: sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==}
engines: {node: '>=0.10.0'}
source-map-js@1.2.1:
resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
engines: {node: '>=0.10.0'}
source-map@0.7.4:
resolution: {integrity: sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==}
engines: {node: '>= 8'}
spark-md5@3.0.2:
resolution: {integrity: sha512-wcFzz9cDfbuqe0FZzfi2or1sgyIrsDwmPwfZC4hiNidPdPINjeUwNfv5kldczoEAcjl9Y1L3SM7Uz2PUEQzxQw==}
split2@4.2.0:
resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==}
engines: {node: '>= 10.x'}
......@@ -2754,6 +2788,10 @@ packages:
util-deprecate@1.0.2:
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
validator@13.12.0:
resolution: {integrity: sha512-c1Q0mCiPlgdTVVVIJIrBuxNicYE+t/7oKeI9MWLj3fh/uq2Pxh/3eeWbVZ4OcGW1TUf53At0njHw5SMdA3tmMg==}
engines: {node: '>= 0.10'}
vdirs@0.1.8:
resolution: {integrity: sha512-H9V1zGRLQZg9b+GdMk8MXDN2Lva0zx72MPahDKc30v+DtwKjfyOSXWRIX4t2mhDubM1H09gPhWeth/BJWPHGUw==}
peerDependencies:
......@@ -2881,8 +2919,8 @@ packages:
peerDependencies:
typescript: '>=5.0.0'
vue@3.5.3:
resolution: {integrity: sha512-xvRbd0HpuLovYbOHXRHlSBsSvmUJbo0pzbkKTApWnQGf3/cu5Z39mQeA5cZdLRVIoNf3zI6MSoOgHUT5i2jO+Q==}
vue@3.5.5:
resolution: {integrity: sha512-ybC+xn67K4+df1yVeov4UjBGyVcXM0a1g7JVZr+pWVUX3xF6ntXU0wIjkTkduZBUIpxTlsftJSxz2kwhsT7dgA==}
peerDependencies:
typescript: '*'
peerDependenciesMeta:
......@@ -3290,9 +3328,9 @@ snapshots:
dependencies:
css-render: 0.15.14
'@css-render/vue3-ssr@0.15.14(vue@3.5.3(typescript@5.5.4))':
'@css-render/vue3-ssr@0.15.14(vue@3.5.5(typescript@5.5.4))':
dependencies:
vue: 3.5.3(typescript@5.5.4)
vue: 3.5.5(typescript@5.5.4)
'@csstools/css-parser-algorithms@3.0.1(@csstools/css-tokenizer@3.0.1)':
dependencies:
......@@ -3491,6 +3529,10 @@ snapshots:
'@humanwhocodes/retry@0.3.0': {}
'@icon-park/vue-next@1.4.2(vue@3.5.5(typescript@5.5.4))':
dependencies:
vue: 3.5.5(typescript@5.5.4)
'@iconify/types@2.0.0': {}
'@iconify/utils@2.1.32':
......@@ -3505,10 +3547,10 @@ snapshots:
transitivePeerDependencies:
- supports-color
'@iconify/vue@4.1.2(vue@3.5.3(typescript@5.5.4))':
'@iconify/vue@4.1.2(vue@3.5.5(typescript@5.5.4))':
dependencies:
'@iconify/types': 2.0.0
vue: 3.5.3(typescript@5.5.4)
vue: 3.5.5(typescript@5.5.4)
'@jridgewell/gen-mapping@0.3.5':
dependencies:
......@@ -3619,6 +3661,10 @@ snapshots:
dependencies:
undici-types: 6.19.8
'@types/spark-md5@3.0.4': {}
'@types/validator@13.12.1': {}
'@types/web-bluetooth@0.0.20': {}
'@typescript-eslint/eslint-plugin@7.18.0(@typescript-eslint/parser@7.18.0(eslint@9.9.1(jiti@1.21.6))(typescript@5.5.4))(eslint@9.9.1(jiti@1.21.6))(typescript@5.5.4)':
......@@ -3882,10 +3928,10 @@ snapshots:
- rollup
- supports-color
'@vitejs/plugin-vue@4.6.2(vite@5.4.3(@types/node@20.16.5)(sass@1.78.0))(vue@3.5.3(typescript@5.5.4))':
'@vitejs/plugin-vue@4.6.2(vite@5.4.3(@types/node@20.16.5)(sass@1.78.0))(vue@3.5.5(typescript@5.5.4))':
dependencies:
vite: 5.4.3(@types/node@20.16.5)(sass@1.78.0)
vue: 3.5.3(typescript@5.5.4)
vue: 3.5.5(typescript@5.5.4)
'@volar/language-core@2.4.2':
dependencies:
......@@ -3899,35 +3945,35 @@ snapshots:
path-browserify: 1.0.1
vscode-uri: 3.0.8
'@vue/compiler-core@3.5.3':
'@vue/compiler-core@3.5.5':
dependencies:
'@babel/parser': 7.25.6
'@vue/shared': 3.5.3
'@vue/shared': 3.5.5
entities: 4.5.0
estree-walker: 2.0.2
source-map-js: 1.2.0
source-map-js: 1.2.1
'@vue/compiler-dom@3.5.3':
'@vue/compiler-dom@3.5.5':
dependencies:
'@vue/compiler-core': 3.5.3
'@vue/shared': 3.5.3
'@vue/compiler-core': 3.5.5
'@vue/shared': 3.5.5
'@vue/compiler-sfc@3.5.3':
'@vue/compiler-sfc@3.5.5':
dependencies:
'@babel/parser': 7.25.6
'@vue/compiler-core': 3.5.3
'@vue/compiler-dom': 3.5.3
'@vue/compiler-ssr': 3.5.3
'@vue/shared': 3.5.3
'@vue/compiler-core': 3.5.5
'@vue/compiler-dom': 3.5.5
'@vue/compiler-ssr': 3.5.5
'@vue/shared': 3.5.5
estree-walker: 2.0.2
magic-string: 0.30.11
postcss: 8.4.45
source-map-js: 1.2.0
source-map-js: 1.2.1
'@vue/compiler-ssr@3.5.3':
'@vue/compiler-ssr@3.5.5':
dependencies:
'@vue/compiler-dom': 3.5.3
'@vue/shared': 3.5.3
'@vue/compiler-dom': 3.5.5
'@vue/shared': 3.5.5
'@vue/compiler-vue2@2.7.16':
dependencies:
......@@ -3939,9 +3985,9 @@ snapshots:
'@vue/language-core@2.0.29(typescript@5.5.4)':
dependencies:
'@volar/language-core': 2.4.2
'@vue/compiler-dom': 3.5.3
'@vue/compiler-dom': 3.5.5
'@vue/compiler-vue2': 2.7.16
'@vue/shared': 3.5.3
'@vue/shared': 3.5.5
computeds: 0.0.1
minimatch: 9.0.5
muggle-string: 0.4.1
......@@ -3949,45 +3995,45 @@ snapshots:
optionalDependencies:
typescript: 5.5.4
'@vue/reactivity@3.5.3':
'@vue/reactivity@3.5.5':
dependencies:
'@vue/shared': 3.5.3
'@vue/shared': 3.5.5
'@vue/runtime-core@3.5.3':
'@vue/runtime-core@3.5.5':
dependencies:
'@vue/reactivity': 3.5.3
'@vue/shared': 3.5.3
'@vue/reactivity': 3.5.5
'@vue/shared': 3.5.5
'@vue/runtime-dom@3.5.3':
'@vue/runtime-dom@3.5.5':
dependencies:
'@vue/reactivity': 3.5.3
'@vue/runtime-core': 3.5.3
'@vue/shared': 3.5.3
'@vue/reactivity': 3.5.5
'@vue/runtime-core': 3.5.5
'@vue/shared': 3.5.5
csstype: 3.1.3
'@vue/server-renderer@3.5.3(vue@3.5.3(typescript@5.5.4))':
'@vue/server-renderer@3.5.5(vue@3.5.5(typescript@5.5.4))':
dependencies:
'@vue/compiler-ssr': 3.5.3
'@vue/shared': 3.5.3
vue: 3.5.3(typescript@5.5.4)
'@vue/compiler-ssr': 3.5.5
'@vue/shared': 3.5.5
vue: 3.5.5(typescript@5.5.4)
'@vue/shared@3.5.3': {}
'@vue/shared@3.5.5': {}
'@vueuse/core@10.11.1(vue@3.5.3(typescript@5.5.4))':
'@vueuse/core@10.11.1(vue@3.5.5(typescript@5.5.4))':
dependencies:
'@types/web-bluetooth': 0.0.20
'@vueuse/metadata': 10.11.1
'@vueuse/shared': 10.11.1(vue@3.5.3(typescript@5.5.4))
vue-demi: 0.14.10(vue@3.5.3(typescript@5.5.4))
'@vueuse/shared': 10.11.1(vue@3.5.5(typescript@5.5.4))
vue-demi: 0.14.10(vue@3.5.5(typescript@5.5.4))
transitivePeerDependencies:
- '@vue/composition-api'
- vue
'@vueuse/metadata@10.11.1': {}
'@vueuse/shared@10.11.1(vue@3.5.3(typescript@5.5.4))':
'@vueuse/shared@10.11.1(vue@3.5.5(typescript@5.5.4))':
dependencies:
vue-demi: 0.14.10(vue@3.5.3(typescript@5.5.4))
vue-demi: 0.14.10(vue@3.5.5(typescript@5.5.4))
transitivePeerDependencies:
- '@vue/composition-api'
- vue
......@@ -4939,10 +4985,10 @@ snapshots:
muggle-string@0.4.1: {}
naive-ui@2.39.0(vue@3.5.3(typescript@5.5.4)):
naive-ui@2.39.0(vue@3.5.5(typescript@5.5.4)):
dependencies:
'@css-render/plugin-bem': 0.15.14(css-render@0.15.14)
'@css-render/vue3-ssr': 0.15.14(vue@3.5.3(typescript@5.5.4))
'@css-render/vue3-ssr': 0.15.14(vue@3.5.5(typescript@5.5.4))
'@types/katex': 0.16.7
'@types/lodash': 4.17.7
'@types/lodash-es': 4.17.12
......@@ -4957,10 +5003,10 @@ snapshots:
lodash-es: 4.17.21
seemly: 0.3.8
treemate: 0.3.11
vdirs: 0.1.8(vue@3.5.3(typescript@5.5.4))
vooks: 0.2.12(vue@3.5.3(typescript@5.5.4))
vue: 3.5.3(typescript@5.5.4)
vueuc: 0.4.58(vue@3.5.3(typescript@5.5.4))
vdirs: 0.1.8(vue@3.5.5(typescript@5.5.4))
vooks: 0.2.12(vue@3.5.5(typescript@5.5.4))
vue: 3.5.5(typescript@5.5.4)
vueuc: 0.4.58(vue@3.5.5(typescript@5.5.4))
nanoid@3.3.7: {}
......@@ -5070,11 +5116,11 @@ snapshots:
pidtree@0.6.0: {}
pinia@2.2.2(typescript@5.5.4)(vue@3.5.3(typescript@5.5.4)):
pinia@2.2.2(typescript@5.5.4)(vue@3.5.5(typescript@5.5.4)):
dependencies:
'@vue/devtools-api': 6.6.3
vue: 3.5.3(typescript@5.5.4)
vue-demi: 0.14.10(vue@3.5.3(typescript@5.5.4))
vue: 3.5.5(typescript@5.5.4)
vue-demi: 0.14.10(vue@3.5.5(typescript@5.5.4))
optionalDependencies:
typescript: 5.5.4
......@@ -5256,8 +5302,12 @@ snapshots:
source-map-js@1.2.0: {}
source-map-js@1.2.1: {}
source-map@0.7.4: {}
spark-md5@3.0.2: {}
split2@4.2.0: {}
string-argv@0.3.2: {}
......@@ -5544,7 +5594,7 @@ snapshots:
- rollup
- supports-color
unplugin-auto-import@0.17.8(@vueuse/core@10.11.1(vue@3.5.3(typescript@5.5.4)))(rollup@4.21.2)(webpack-sources@3.2.3):
unplugin-auto-import@0.17.8(@vueuse/core@10.11.1(vue@3.5.5(typescript@5.5.4)))(rollup@4.21.2)(webpack-sources@3.2.3):
dependencies:
'@antfu/utils': 0.7.10
'@rollup/pluginutils': 5.1.0(rollup@4.21.2)
......@@ -5555,12 +5605,12 @@ snapshots:
unimport: 3.11.1(rollup@4.21.2)(webpack-sources@3.2.3)
unplugin: 1.13.1(webpack-sources@3.2.3)
optionalDependencies:
'@vueuse/core': 10.11.1(vue@3.5.3(typescript@5.5.4))
'@vueuse/core': 10.11.1(vue@3.5.5(typescript@5.5.4))
transitivePeerDependencies:
- rollup
- webpack-sources
unplugin-vue-components@0.26.0(@babel/parser@7.25.6)(rollup@4.21.2)(vue@3.5.3(typescript@5.5.4))(webpack-sources@3.2.3):
unplugin-vue-components@0.26.0(@babel/parser@7.25.6)(rollup@4.21.2)(vue@3.5.5(typescript@5.5.4))(webpack-sources@3.2.3):
dependencies:
'@antfu/utils': 0.7.10
'@rollup/pluginutils': 5.1.0(rollup@4.21.2)
......@@ -5572,7 +5622,7 @@ snapshots:
minimatch: 9.0.5
resolve: 1.22.8
unplugin: 1.13.1(webpack-sources@3.2.3)
vue: 3.5.3(typescript@5.5.4)
vue: 3.5.5(typescript@5.5.4)
optionalDependencies:
'@babel/parser': 7.25.6
transitivePeerDependencies:
......@@ -5599,10 +5649,12 @@ snapshots:
util-deprecate@1.0.2: {}
vdirs@0.1.8(vue@3.5.3(typescript@5.5.4)):
validator@13.12.0: {}
vdirs@0.1.8(vue@3.5.5(typescript@5.5.4)):
dependencies:
evtd: 0.2.4
vue: 3.5.3(typescript@5.5.4)
vue: 3.5.5(typescript@5.5.4)
vite-plugin-checker@0.7.2(eslint@9.9.1(jiti@1.21.6))(optionator@0.9.4)(stylelint@16.9.0(typescript@5.5.4))(typescript@5.5.4)(vite@5.4.3(@types/node@20.16.5)(sass@1.78.0))(vue-tsc@2.0.29(typescript@5.5.4)):
dependencies:
......@@ -5638,10 +5690,10 @@ snapshots:
fsevents: 2.3.3
sass: 1.78.0
vooks@0.2.12(vue@3.5.3(typescript@5.5.4)):
vooks@0.2.12(vue@3.5.5(typescript@5.5.4)):
dependencies:
evtd: 0.2.4
vue: 3.5.3(typescript@5.5.4)
vue: 3.5.5(typescript@5.5.4)
vscode-jsonrpc@6.0.0: {}
......@@ -5666,9 +5718,9 @@ snapshots:
vscode-uri@3.0.8: {}
vue-demi@0.14.10(vue@3.5.3(typescript@5.5.4)):
vue-demi@0.14.10(vue@3.5.5(typescript@5.5.4)):
dependencies:
vue: 3.5.3(typescript@5.5.4)
vue: 3.5.5(typescript@5.5.4)
vue-eslint-parser@9.4.3(eslint@9.9.1(jiti@1.21.6)):
dependencies:
......@@ -5683,10 +5735,10 @@ snapshots:
transitivePeerDependencies:
- supports-color
vue-router@4.4.3(vue@3.5.3(typescript@5.5.4)):
vue-router@4.4.3(vue@3.5.5(typescript@5.5.4)):
dependencies:
'@vue/devtools-api': 6.6.3
vue: 3.5.3(typescript@5.5.4)
vue: 3.5.5(typescript@5.5.4)
vue-tsc@2.0.29(typescript@5.5.4):
dependencies:
......@@ -5695,26 +5747,26 @@ snapshots:
semver: 7.6.3
typescript: 5.5.4
vue@3.5.3(typescript@5.5.4):
vue@3.5.5(typescript@5.5.4):
dependencies:
'@vue/compiler-dom': 3.5.3
'@vue/compiler-sfc': 3.5.3
'@vue/runtime-dom': 3.5.3
'@vue/server-renderer': 3.5.3(vue@3.5.3(typescript@5.5.4))
'@vue/shared': 3.5.3
'@vue/compiler-dom': 3.5.5
'@vue/compiler-sfc': 3.5.5
'@vue/runtime-dom': 3.5.5
'@vue/server-renderer': 3.5.5(vue@3.5.5(typescript@5.5.4))
'@vue/shared': 3.5.5
optionalDependencies:
typescript: 5.5.4
vueuc@0.4.58(vue@3.5.3(typescript@5.5.4)):
vueuc@0.4.58(vue@3.5.5(typescript@5.5.4)):
dependencies:
'@css-render/vue3-ssr': 0.15.14(vue@3.5.3(typescript@5.5.4))
'@css-render/vue3-ssr': 0.15.14(vue@3.5.5(typescript@5.5.4))
'@juggle/resize-observer': 3.4.0
css-render: 0.15.14
evtd: 0.2.4
seemly: 0.3.8
vdirs: 0.1.8(vue@3.5.3(typescript@5.5.4))
vooks: 0.2.12(vue@3.5.3(typescript@5.5.4))
vue: 3.5.3(typescript@5.5.4)
vdirs: 0.1.8(vue@3.5.5(typescript@5.5.4))
vooks: 0.2.12(vue@3.5.5(typescript@5.5.4))
vue: 3.5.5(typescript@5.5.4)
webpack-sources@3.2.3:
optional: true
......
import { request } from '@/utils/request'
export function fetchLogin<T>(payload: { username: string; password: string }) {
return request.post<T>(`/oauth/auth?username=${payload.username}&password=${payload.password}`, null, {
ignoreErrorCheck: true,
})
export function fetchLogin<T>(payload: {
loginChannel: 'MEMBER_PLATFOMR_SMS' | 'MEMBER_PLATFOMR_EMAIL' | 'MEMBER_PLATFOMR_PW'
account: string
password?: string
authCode?: string
}) {
return request.post<T>('/bizMemberInfoRest/doLogin.json', payload)
}
export function fetchSMSCode<T>(phoneNumber: string) {
return request.post<T>(`/smsRest/smsDelivered.json?phone=${phoneNumber}`)
}
export function fetchEmailCode<T>(emailAddress: string) {
return request.post<T>(`/sendEmailRest/sendEmailCode.json?emailAddress=${emailAddress}`)
}
export const BASE_URLS: Record<'DEV' | 'PROD', string> = {
DEV: '',
DEV: 'https://poc-sit.gsstcloud.com',
PROD: '',
}
......@@ -3,7 +3,7 @@ import { GlobalThemeOverrides } from 'naive-ui'
export const themeOverrides: GlobalThemeOverrides = {
common: {
primaryColor: '#2468f2',
primaryColorHover: '#45a9bb',
primaryColorHover: '#528eff',
primaryColorPressed: '#398c9b',
primaryColorSuppl: '#45a9bb',
},
......
......@@ -5,25 +5,28 @@ import { useUserStore } from '@/store/modules/user'
const whitePathList = ['/login']
export function createRouterGuards(router: Router) {
router.beforeEach((to) => {
router.beforeEach((to, _from, next) => {
window.$loadingBar.start()
const userStore = useUserStore()
if (userStore.isLogin && to.fullPath === '/login') {
return false
if (userStore.isLogin && to.name === 'Login') {
next({ name: 'Root' })
return
}
// 白名单直接跳过
if (whitePathList.includes(to.path)) {
return true
next()
return
}
if (!userStore.isLogin && !whitePathList.includes(to.fullPath)) {
return { path: '/login', query: { redirect: encodeURIComponent(to.fullPath) } }
next({ path: '/login', query: { redirect: encodeURIComponent(to.fullPath) } })
return
}
return true
next()
})
router.afterEach((to) => {
......
import { defineStore } from 'pinia'
import { ss } from '@/utils/storage'
import { TOKEN, IS_LOGIN, USER_INFO } from '@/utils/storage-key'
import type { UserState, UserInfo } from '../types/user'
import { type UserState, type UserInfo, UserStoreStorageKeyEnum } from '../types/user'
function getDefaultUserInfo(): UserInfo {
function createDefaultUserInfoFactory(): UserInfo {
return {
account: '',
userAccount: '',
userId: null,
userName: '用户',
createdTime: '',
avatar: '',
memberId: null,
avatarUrl: '',
nickName: '',
mobilePhone: '',
}
}
export const useUserStore = defineStore('user-store', {
state: (): UserState => ({
isLogin: ss.get(IS_LOGIN),
token: ss.get(TOKEN) || '',
userInfo: ss.get(USER_INFO) || getDefaultUserInfo(),
isLogin: ss.get(UserStoreStorageKeyEnum.isLogin),
token: ss.get(UserStoreStorageKeyEnum.token) || '',
userInfo: ss.get(UserStoreStorageKeyEnum.userInfo) || createDefaultUserInfoFactory(),
}),
actions: {
async logout() {
this.isLogin = false
this.token = ''
this.userInfo = getDefaultUserInfo()
this.userInfo = createDefaultUserInfoFactory()
ss.remove(IS_LOGIN)
ss.remove(TOKEN)
ss.remove(USER_INFO)
},
updateIsLogin(status: boolean) {
this.isLogin = status
ss.set(IS_LOGIN, status)
ss.remove(UserStoreStorageKeyEnum.isLogin)
ss.remove(UserStoreStorageKeyEnum.token)
ss.remove(UserStoreStorageKeyEnum.userInfo)
},
updateToken(token: string) {
this.token = token
ss.set(TOKEN, token)
ss.set(UserStoreStorageKeyEnum.token, token)
if (token) {
this.isLogin = true
ss.set(UserStoreStorageKeyEnum.isLogin, true)
} else {
this.isLogin = false
ss.set(UserStoreStorageKeyEnum.isLogin, false)
}
},
updateUserInfo(userInfo: UserInfo) {
this.userInfo = userInfo
ss.set(USER_INFO, userInfo)
ss.set(UserStoreStorageKeyEnum.userInfo, userInfo)
},
},
})
export interface UserInfo {
account: string
userAccount: string
userId: null | number
userName: string
createdTime: string
avatar: string
memberId: number | null
mobilePhone: string
nickName: string
avatarUrl: string
}
export interface UserState {
......@@ -12,3 +10,9 @@ export interface UserState {
token: string
userInfo: UserInfo
}
export enum UserStoreStorageKeyEnum {
userInfo = 'USER_INFO',
token = 'TOKEN',
isLogin = 'IS_LOGIN',
}
......@@ -26,7 +26,7 @@ function handleLogout() {
}
const service = axios.create({
baseURL: `${BASE_URLS[ENV]}/api`,
baseURL: `${BASE_URLS[ENV]}/api/rest`,
timeout: 7000,
headers: {
'Content-Type': 'application/json',
......
export const TOKEN = 'TOKEN'
export const IS_LOGIN = 'IS_LOGIN'
export const USER_INFO = 'USER_INFO'
<script setup lang="ts">
import { ref } from 'vue'
import { appConfig } from '@/config/app-config'
import CustomIcon from '@/components/custom-icon/custom-icon.vue'
import { FormInst } from 'naive-ui'
// import { fetchLogin } from '@/apis/user'
import { ref, shallowReadonly, useTemplateRef, watchEffect } from 'vue'
import type { FormInst, FormRules, FormItemRule, CountdownInst } from 'naive-ui'
import { Mail, Lock, Iphone, Down, User } from '@icon-park/vue-next'
import isMobilePhone from 'validator/es/lib/isMobilePhone'
import isEmail from 'validator/es/lib/isEmail'
import { ss } from '@/utils/storage'
import { fetchEmailCode, fetchLogin, fetchSMSCode } from '@/apis/user'
import SparkMD5 from 'spark-md5'
import { useUserStore } from '@/store/modules/user'
// import type { UserInfo } from '@/store/types/user'
import { useRouter } from 'vue-router'
import type { UserInfo } from '@/store/types/user'
import { useRouter, useRoute } from 'vue-router'
enum StorageKeyEnum {
smsCountdownTime = 'SMS_COUNTDOWN_TIME',
emailCountdownTime = 'MAIL_COUNTDOWN_TIME',
}
type LoginMethod = 'password' | 'sms' | 'email'
interface LoginPayload {
loginChannel: 'MEMBER_PLATFOMR_SMS' | 'MEMBER_PLATFOMR_EMAIL' | 'MEMBER_PLATFOMR_PW'
account: string
password?: string
authCode?: string
}
const userStore = useUserStore()
const router = useRouter()
const route = useRoute()
const passwordLoginFormRef = useTemplateRef<FormInst>('passwordLoginFormRef')
const smsLoginFormRef = useTemplateRef<FormInst>('smsLoginFormRef')
const emailLoginFormRef = useTemplateRef<FormInst>('emailLoginFormRef')
const countdownRef = useTemplateRef<CountdownInst>('countdownRef')
const currentLoginMethod = ref<LoginMethod>('password')
const showCardReserveAnimation = ref(false)
const loginForm = ref({
username: '',
const loginBtnLoading = ref(false)
const passwordLoginForm = ref({
account: '',
password: '',
})
const loginBtnLoading = ref(false)
const loginFormRef = ref<FormInst | null>(null)
const smsLoginForm = ref({
phoneNumber: '',
code: '',
})
const emailLoginForm = ref({
email: '',
code: '',
})
const passwordLoginFormRules = shallowReadonly<FormRules>({
account: { required: true, message: '請輸入用戶名', trigger: 'blur' },
password: { required: true, message: '請輸入密碼', trigger: 'blur' },
})
const smsLoginFormRules = shallowReadonly<FormRules>({
phoneNumber: {
key: 'phoneNumber',
required: true,
validator: (_rule: FormItemRule, value: string) => {
if (!value) {
return new Error('請輸入手機號')
} else if (!isMobilePhone(value, ['zh-CN', 'zh-HK'])) {
return new Error('請輸入正確手機號')
}
return
},
},
code: { required: true, message: '請輸入驗証碼' },
})
const emailLoginFormRules = shallowReadonly<FormRules>({
email: {
key: 'email',
required: true,
validator: (_rule: FormItemRule, value: string) => {
if (!value) {
return new Error('請輸入郵箱地址')
} else if (!isEmail(value)) {
return new Error('請輸入正確郵箱地址')
}
return
},
},
code: { required: true, message: '請輸入驗証碼' },
})
const phoneNumberAreaOptions = shallowReadonly([
{
label: '+86 中國大陸',
value: '+86',
},
{
label: '+852 中國香港',
value: '+852',
},
])
const currentPhoneNumberArea = ref<'+86' | '+852'>('+852')
const countdownActive = ref(true)
const isShowCountdown = ref(false)
const countdownDuration = ref<number>(60000)
watchEffect(() => {
let timeStringDraft = ''
switch (currentLoginMethod.value) {
case 'sms':
{
timeStringDraft = ss.get(StorageKeyEnum.smsCountdownTime)
}
break
case 'email':
{
timeStringDraft = ss.get(StorageKeyEnum.emailCountdownTime)
}
break
}
if (timeStringDraft) {
const time = Math.floor(Date.now() - parseInt(timeStringDraft))
if (time < 60000) {
countdownDuration.value = 60000 - time
countdownRef.value?.reset()
isShowCountdown.value = true
}
}
})
function onlyAllowNumber(value: string) {
return !value || /^\d+$/.test(value)
}
function noSideSpace(value: string) {
return !value.startsWith(' ') && !value.endsWith(' ')
}
function countdownRender({ seconds, minutes }: { seconds: number; minutes: number }) {
if (minutes && minutes === 1) {
return '60 s'
}
const loginFormRules = {
username: { required: true, message: '请输入用户名', trigger: 'blur' },
password: { required: true, message: '请输入密码', trigger: 'blur' },
return `${seconds} s`
}
function handleLogin(e: MouseEvent) {
e.preventDefault()
function onCardReserveAnimationEnd() {
showCardReserveAnimation.value = false
}
loginFormRef.value?.validate((errors) => {
if (!errors) {
// loginBtnLoading.value = true
function onCountdownFinish() {
isShowCountdown.value = false
}
function getInputPhoneNumber() {
return currentPhoneNumberArea.value !== '+86'
? `${currentPhoneNumberArea.value}${smsLoginForm.value.phoneNumber}`
: smsLoginForm.value.phoneNumber
}
userStore.updateIsLogin(true)
userStore.updateToken('res.data.token')
const userInfoRes = {
account: 'account',
userAccount: 'userAccount',
userId: 134,
userName: 'userName',
createdTime: 'createdTime',
function handleLoginSubmit(method: LoginMethod) {
let payload: LoginPayload = {
loginChannel: 'MEMBER_PLATFOMR_PW',
account: '',
password: '',
}
userStore.updateUserInfo({
account: userInfoRes.account,
userAccount: userInfoRes.userAccount,
userId: userInfoRes.userId,
userName: userInfoRes.userName,
createdTime: userInfoRes.createdTime,
avatar: '',
new Promise((resolve) => {
switch (method) {
case 'password':
{
passwordLoginFormRef.value?.validate((errors) => {
if (errors) return ''
payload = {
loginChannel: 'MEMBER_PLATFOMR_PW',
account: passwordLoginForm.value.account,
password: SparkMD5.hash(passwordLoginForm.value.password),
}
resolve(true)
})
}
break
case 'sms':
{
smsLoginFormRef.value?.validate((errors) => {
if (errors) return ''
const currentRoute = router.currentRoute.value
const redirectUrl = decodeURIComponent((currentRoute.query.redirect as string) || '')
payload = {
loginChannel: 'MEMBER_PLATFOMR_SMS',
account: getInputPhoneNumber(),
authCode: smsLoginForm.value.code,
}
if (redirectUrl) {
router.replace({ path: redirectUrl })
return
resolve(true)
})
}
break
case 'email':
{
emailLoginFormRef.value?.validate((errors) => {
if (errors) return ''
payload = {
loginChannel: 'MEMBER_PLATFOMR_EMAIL',
account: emailLoginForm.value.email,
authCode: emailLoginForm.value.code,
}
router.replace({ name: 'Root' })
// fetchLogin<{ token: string; user: UserInfo }>(loginForm.value)
// .then((res) => {
// if (res.code !== null && res.code !== 0) {
// window.$message.error(res.message || '登录失败请重试')
// return
// }
// userStore.updateIsLogin(true)
// userStore.updateToken(res.data.token)
// const userInfoRes = res.data.user || {}
// userStore.updateUserInfo({
// account: userInfoRes.account,
// userAccount: userInfoRes.userAccount,
// userId: userInfoRes.userId,
// userName: userInfoRes.userName,
// createdTime: userInfoRes.createdTime,
// avatar: '',
// })
// const currentRoute = router.currentRoute.value
// const redirectUrl = decodeURIComponent((currentRoute.query.redirect as string) || '')
// if (redirectUrl) {
// router.replace({ path: redirectUrl })
// return
// }
// router.replace({ name: 'Root' })
// })
// .catch((err) => {
// console.log(err)
// })
// .finally(() => {
// loginBtnLoading.value = false
// })
resolve(true)
})
}
break
}
}).then(() => {
loginBtnLoading.value = true
fetchLogin<UserInfo & { token: string }>(payload)
.then((res) => {
if (res.code !== 0) return ''
userStore.updateToken(res.data.token)
userStore.updateUserInfo({
avatarUrl: res.data.avatarUrl,
memberId: res.data.memberId,
mobilePhone: res.data.mobilePhone,
nickName: res.data.nickName,
})
const redirectUrl = decodeURIComponent((route.query.redirect as string) || '')
router.replace({ path: redirectUrl ? redirectUrl : '/' })
window.$message.success('登錄成功')
ss.remove(StorageKeyEnum.smsCountdownTime)
ss.remove(StorageKeyEnum.emailCountdownTime)
})
.finally(() => {
loginBtnLoading.value = false
})
})
}
function handleLoginMethodChange(method: LoginMethod) {
showCardReserveAnimation.value = true
setTimeout(() => {
currentLoginMethod.value = method
}, 500)
}
function handleSMSCodeGain() {
smsLoginFormRef.value?.validate(
(errors) => {
if (errors) return ''
countdownDuration.value = 60000
ss.set(StorageKeyEnum.smsCountdownTime, Date.now())
countdownRef.value?.reset()
isShowCountdown.value = true
fetchSMSCode(getInputPhoneNumber()).then((res) => {
if (res.code !== 0) return ''
window.$message.success('獲取成功')
})
},
(rule) => {
return rule.key === 'phoneNumber'
},
)
}
function handleEmailCodeGain() {
emailLoginFormRef.value?.validate(
(errors) => {
if (errors) return ''
countdownDuration.value = 60000
ss.set(StorageKeyEnum.emailCountdownTime, Date.now())
countdownRef.value?.reset()
isShowCountdown.value = true
fetchEmailCode(encodeURIComponent(emailLoginForm.value.email)).then((res) => {
if (res.code !== 0) return ''
window.$message.success('獲取成功')
})
},
(rule) => {
return rule.key === 'email'
},
)
}
</script>
<template>
<div class="bg-svg-login-bg h-full w-full">
<div class="fixed left-1/2 top-1/3 w-[90%] -translate-x-1/2 -translate-y-1/3 rounded-lg bg-white p-6 sm:w-[410px]">
<div class="mb-6 flex justify-center sm:mb-7">
<div class="bg-px-logo-png h-24 w-36"></div>
</div>
<div class="mb-8 text-center text-2xl font-bold text-[#999] outline-none sm:mb-10 sm:text-2xl">
{{ appConfig.title }}
</div>
<div class="bg-px-login-bg-png relative h-screen min-h-[750px] w-full min-w-[600px] bg-cover bg-center bg-no-repeat">
<div class="absolute right-[14%] top-1/2 h-[458px] w-[390px] -translate-y-1/2">
<div
class="h-full w-full rounded-[10px] bg-[#fff] px-[29px]"
:class="{ 'animate-card-reverse': showCardReserveAnimation }"
style="transform-style: preserve-3d"
@animationend="onCardReserveAnimationEnd"
>
<h1 class="font-600 py-[34px] text-center text-[22px]">歡迎使用萃想智能雲創</h1>
<div>
<NForm ref="loginFormRef" label-placement="left" size="large" :model="loginForm" :rules="loginFormRules">
<NFormItem path="username">
<NInput v-model:value="loginForm.username" placeholder="请输入用户名">
<!-- 密码登录 -->
<n-form
v-if="currentLoginMethod === 'password'"
ref="passwordLoginFormRef"
label-placement="left"
size="large"
:model="passwordLoginForm"
:rules="passwordLoginFormRules"
>
<n-form-item path="account">
<n-input
v-model:value="passwordLoginForm.account"
:allow-input="noSideSpace"
:maxlength="11"
placeholder="請輸入用戶名"
>
<template #prefix>
<CustomIcon class="h-5 w-5 text-[#868686]" icon="material-symbols:person-outline" />
<div class="mr-[6px]">
<User theme="outline" size="16" fill="#868686" :stroke-width="3" />
</div>
</template>
</NInput>
</NFormItem>
<NFormItem path="password">
<NInput
v-model:value="loginForm.password"
</n-input>
</n-form-item>
<n-form-item path="password">
<n-input
v-model:value="passwordLoginForm.password"
:allow-input="noSideSpace"
type="password"
show-password-on="click"
placeholder="请输入密码"
placeholder="請輸入密碼"
>
<template #prefix>
<div class="mr-[6px]">
<Lock theme="outline" size="16" fill="#868686" :stroke-width="3" />
</div>
</template>
</n-input>
</n-form-item>
<n-form-item class="mt-4">
<n-button
type="primary"
size="large"
block
:loading="loginBtnLoading"
:disabled="!passwordLoginForm.account || !passwordLoginForm.password"
@click="handleLoginSubmit('password')"
>
登錄
</n-button>
</n-form-item>
</n-form>
<!-- SMS登录 -->
<n-form
v-if="currentLoginMethod === 'sms'"
ref="smsLoginFormRef"
label-placement="left"
size="large"
:model="smsLoginForm"
:rules="smsLoginFormRules"
>
<n-form-item path="phoneNumber">
<n-input
v-model:value.trim="smsLoginForm.phoneNumber"
:allow-input="onlyAllowNumber"
:maxlength="currentPhoneNumberArea === '+852' ? 8 : 11"
placeholder="請輸入手機號"
>
<template #prefix>
<div class="flex items-center">
<n-popselect
v-model:value="currentPhoneNumberArea"
:options="phoneNumberAreaOptions"
trigger="click"
>
<div class="flex w-[62px] cursor-pointer items-center">
<div class="mr-[4px]">{{ currentPhoneNumberArea }}</div>
<Down theme="outline" size="18" fill="#333" :stroke-width="3" />
</div>
</n-popselect>
<div class="mx-[8px] h-[18px] w-[1px] bg-[#868686]"></div>
</div>
</template>
</n-input>
</n-form-item>
<n-form-item path="code">
<n-input
v-model:value="smsLoginForm.code"
:allow-input="onlyAllowNumber"
show-password-on="click"
:maxlength="6"
placeholder="請輸入驗証碼"
>
<template #suffix>
<div class="flex items-center">
<div class="mx-[6px] h-[18px] w-[1px] bg-[#868686]"></div>
<div class="w-[90px] text-end">
<n-button
v-show="!isShowCountdown"
class="!text-[11px]"
type="tertiary"
size="small"
@click="handleSMSCodeGain"
>
獲取驗証碼
</n-button>
<div v-show="isShowCountdown" class="inline-block w-[50px] text-center">
<n-countdown
ref="countdownRef"
:duration="countdownDuration"
:active="countdownActive"
:render="countdownRender"
:on-finish="onCountdownFinish"
/>
</div>
</div>
</div>
</template>
</n-input>
</n-form-item>
<n-form-item class="mt-4">
<n-button
type="primary"
size="large"
block
:loading="loginBtnLoading"
:disabled="!smsLoginForm.phoneNumber || !smsLoginForm.code"
@click="handleLoginSubmit('sms')"
>
登錄
</n-button>
</n-form-item>
</n-form>
<!-- 邮箱登录 -->
<n-form
v-if="currentLoginMethod === 'email'"
ref="emailLoginFormRef"
label-placement="left"
size="large"
:model="emailLoginForm"
:rules="emailLoginFormRules"
>
<n-form-item path="email">
<n-input v-model:value="emailLoginForm.email" :allow-input="noSideSpace" placeholder="請輸入郵箱地址">
<template #prefix>
<CustomIcon class="h-5 w-5 text-[#868686]" icon="mdi:lock-outline" />
<div class="mr-[6px]">
<Mail theme="outline" size="16" fill="#868686" :stroke-width="3" />
</div>
</template>
</n-input>
</n-form-item>
<n-form-item path="code">
<n-input
v-model:value="emailLoginForm.code"
:allow-input="onlyAllowNumber"
show-password-on="click"
:maxlength="6"
placeholder="請輸入驗証碼"
>
<template #suffix>
<div class="flex items-center">
<div class="mx-[6px] h-[18px] w-[1px] bg-[#868686]"></div>
<div class="w-[90px] text-end">
<n-button
v-show="!isShowCountdown"
class="!text-[11px]"
type="tertiary"
size="small"
@click="handleEmailCodeGain"
>
獲取驗証碼
</n-button>
<div v-show="isShowCountdown" class="inline-block w-[50px] text-center">
<n-countdown
ref="countdownRef"
:duration="countdownDuration"
:active="countdownActive"
:render="countdownRender"
:on-finish="onCountdownFinish"
/>
</div>
</div>
</div>
</template>
</NInput>
</NFormItem>
<NFormItem class="mt-4">
<NButton
</n-input>
</n-form-item>
<n-form-item class="mt-4">
<n-button
type="primary"
size="large"
block
:loading="loginBtnLoading"
:disabled="!loginForm.username || !loginForm.password"
@click="handleLogin"
:disabled="!emailLoginForm.email || !emailLoginForm.code"
@click="handleLoginSubmit('email')"
>
登錄
</n-button>
</n-form-item>
</n-form>
</div>
<div class="absolute bottom-[22px] left-0 w-full">
<div class="mb-[32px]">
<div class="mb-[12px] text-center text-[12px] text-[#999999]">其他登錄方式</div>
<div class="flex items-center justify-center">
<button
v-show="currentLoginMethod !== 'email'"
class="mx-[10px] flex h-[34px] w-[34px] items-center justify-center rounded-full bg-[#f1f1f1] transition hover:bg-[#e0e0e0]"
@click="handleLoginMethodChange('email')"
>
<Mail theme="outline" size="18" fill="#666666" :stroke-width="3" />
</button>
<button
v-show="currentLoginMethod !== 'sms'"
class="mx-[10px] flex h-[34px] w-[34px] items-center justify-center rounded-full bg-[#f1f1f1] transition hover:bg-[#e0e0e0]"
@click="handleLoginMethodChange('sms')"
>
<Iphone theme="outline" size="17" fill="#666666" :stroke-width="3" />
</button>
<button
v-show="currentLoginMethod !== 'password'"
class="mx-[10px] flex h-[34px] w-[34px] items-center justify-center rounded-full bg-[#f1f1f1] transition hover:bg-[#e0e0e0]"
@click="handleLoginMethodChange('password')"
>
登录
</NButton>
</NFormItem>
</NForm>
<Lock theme="outline" size="17" fill="#666666" :stroke-width="3" />
</button>
</div>
</div>
<!-- <div class="text-center">
<n-checkbox size="small"><span class="text-[12px]">閱讀並同意協議</span></n-checkbox>
</div> -->
</div>
</div>
</div>
</div>
......
......@@ -15,4 +15,20 @@ export default defineConfig({
}),
],
],
theme: {
animation: {
keyframes: {
'card-reverse': `{ 0% { transform: rotateY(0deg); } 100% { transform: rotateY(1turn); } }`,
},
durations: {
'card-reverse': '1s',
},
timingFns: {
'card-reverse': 'ease-in-out',
},
counts: {
'card-reverse': '1',
},
},
},
})
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment