generated from kevisual/vite-react-template
feat: 更新版本号,添加登录功能和路由支持
This commit is contained in:
13
package.json
13
package.json
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "cli-center",
|
"name": "cli-center",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.0.1",
|
"version": "0.0.2",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"basename": "/root/cli-center",
|
"basename": "/root/cli-center",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
@@ -9,7 +9,7 @@
|
|||||||
"build": "vite build",
|
"build": "vite build",
|
||||||
"preview": "vite preview",
|
"preview": "vite preview",
|
||||||
"ui": "bunx shadcn@latest add ",
|
"ui": "bunx shadcn@latest add ",
|
||||||
"pub": "envision deploy ./dist -k cli-center -v 0.0.1 -y y -u"
|
"pub": "envision deploy ./dist -k cli-center -v 0.0.2 -y y -u"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"dist"
|
"dist"
|
||||||
@@ -18,10 +18,11 @@
|
|||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@base-ui/react": "^1.2.0",
|
"@base-ui/react": "^1.2.0",
|
||||||
"@kevisual/api": "^0.0.59",
|
"@kevisual/api": "^0.0.60",
|
||||||
"@kevisual/context": "^0.0.8",
|
"@kevisual/context": "^0.0.8",
|
||||||
|
"@kevisual/kv-login": "^0.1.15",
|
||||||
"@kevisual/router": "0.0.84",
|
"@kevisual/router": "0.0.84",
|
||||||
"@tanstack/react-router": "^1.162.6",
|
"@tanstack/react-router": "^1.162.8",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"cmdk": "^1.1.1",
|
"cmdk": "^1.1.1",
|
||||||
@@ -45,8 +46,8 @@
|
|||||||
"@kevisual/query": "0.0.49",
|
"@kevisual/query": "0.0.49",
|
||||||
"@kevisual/types": "^0.0.12",
|
"@kevisual/types": "^0.0.12",
|
||||||
"@tailwindcss/vite": "^4.2.1",
|
"@tailwindcss/vite": "^4.2.1",
|
||||||
"@tanstack/react-router-devtools": "^1.162.6",
|
"@tanstack/react-router-devtools": "^1.162.8",
|
||||||
"@tanstack/router-plugin": "^1.162.6",
|
"@tanstack/router-plugin": "^1.162.8",
|
||||||
"@types/node": "^25.3.0",
|
"@types/node": "^25.3.0",
|
||||||
"@types/react": "^19.2.14",
|
"@types/react": "^19.2.14",
|
||||||
"@types/react-dom": "^19.2.3",
|
"@types/react-dom": "^19.2.3",
|
||||||
|
|||||||
56
pnpm-lock.yaml
generated
56
pnpm-lock.yaml
generated
@@ -12,17 +12,20 @@ importers:
|
|||||||
specifier: ^1.2.0
|
specifier: ^1.2.0
|
||||||
version: 1.2.0(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
version: 1.2.0(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||||
'@kevisual/api':
|
'@kevisual/api':
|
||||||
specifier: ^0.0.59
|
specifier: ^0.0.60
|
||||||
version: 0.0.59(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(use-sync-external-store@1.6.0(react@19.2.4))
|
version: 0.0.60(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(use-sync-external-store@1.6.0(react@19.2.4))
|
||||||
'@kevisual/context':
|
'@kevisual/context':
|
||||||
specifier: ^0.0.8
|
specifier: ^0.0.8
|
||||||
version: 0.0.8
|
version: 0.0.8
|
||||||
|
'@kevisual/kv-login':
|
||||||
|
specifier: ^0.1.15
|
||||||
|
version: 0.1.15
|
||||||
'@kevisual/router':
|
'@kevisual/router':
|
||||||
specifier: 0.0.84
|
specifier: 0.0.84
|
||||||
version: 0.0.84
|
version: 0.0.84
|
||||||
'@tanstack/react-router':
|
'@tanstack/react-router':
|
||||||
specifier: ^1.162.6
|
specifier: ^1.162.8
|
||||||
version: 1.162.6(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
version: 1.162.8(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||||
class-variance-authority:
|
class-variance-authority:
|
||||||
specifier: ^0.7.1
|
specifier: ^0.7.1
|
||||||
version: 0.7.1
|
version: 0.7.1
|
||||||
@@ -79,11 +82,11 @@ importers:
|
|||||||
specifier: ^4.2.1
|
specifier: ^4.2.1
|
||||||
version: 4.2.1(vite@8.0.0-beta.14(@types/node@25.3.0)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0))
|
version: 4.2.1(vite@8.0.0-beta.14(@types/node@25.3.0)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0))
|
||||||
'@tanstack/react-router-devtools':
|
'@tanstack/react-router-devtools':
|
||||||
specifier: ^1.162.6
|
specifier: ^1.162.8
|
||||||
version: 1.162.6(@tanstack/react-router@1.162.6(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(@tanstack/router-core@1.162.6)(csstype@3.2.3)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
version: 1.162.8(@tanstack/react-router@1.162.8(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(@tanstack/router-core@1.162.6)(csstype@3.2.3)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||||
'@tanstack/router-plugin':
|
'@tanstack/router-plugin':
|
||||||
specifier: ^1.162.6
|
specifier: ^1.162.8
|
||||||
version: 1.162.6(@tanstack/react-router@1.162.6(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(vite@8.0.0-beta.14(@types/node@25.3.0)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0))
|
version: 1.162.8(@tanstack/react-router@1.162.8(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(vite@8.0.0-beta.14(@types/node@25.3.0)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0))
|
||||||
'@types/node':
|
'@types/node':
|
||||||
specifier: ^25.3.0
|
specifier: ^25.3.0
|
||||||
version: 25.3.0
|
version: 25.3.0
|
||||||
@@ -433,8 +436,8 @@ packages:
|
|||||||
'@jridgewell/trace-mapping@0.3.31':
|
'@jridgewell/trace-mapping@0.3.31':
|
||||||
resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==}
|
resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==}
|
||||||
|
|
||||||
'@kevisual/api@0.0.59':
|
'@kevisual/api@0.0.60':
|
||||||
resolution: {integrity: sha512-2w6GBG2mS92dz8afB0hLfjTw8lBGD5oXU5bw/QglFugHHp24fISZkEW1Hc+jP/jOiYlMzphws2/31DIXdDoGkg==}
|
resolution: {integrity: sha512-NTFDx1ns/iGli2fUJLJZRWu8nf5VkXV+sOQUqGGAJvrvGATvXSuITu6mD4P/aDQakx4hzQUPr9wDTZoNk7+RqQ==}
|
||||||
|
|
||||||
'@kevisual/cache@0.0.5':
|
'@kevisual/cache@0.0.5':
|
||||||
resolution: {integrity: sha512-fgtUYGUUq/DY0KFV4CkWszNqvQUaA8XvMTUjoR9ZXRpau5IIDolD/Wen2TFsZ7G3Rfy+lef5dnaiZVDkZwdVKg==}
|
resolution: {integrity: sha512-fgtUYGUUq/DY0KFV4CkWszNqvQUaA8XvMTUjoR9ZXRpau5IIDolD/Wen2TFsZ7G3Rfy+lef5dnaiZVDkZwdVKg==}
|
||||||
@@ -445,6 +448,9 @@ packages:
|
|||||||
'@kevisual/js-filter@0.0.5':
|
'@kevisual/js-filter@0.0.5':
|
||||||
resolution: {integrity: sha512-+S+Sf3K/aP6XtZI2s7TgKOr35UuvUvtpJ9YDW30a+mY0/N8gRuzyKhieBzQN7Ykayzz70uoMavBXut2rUlLgzw==}
|
resolution: {integrity: sha512-+S+Sf3K/aP6XtZI2s7TgKOr35UuvUvtpJ9YDW30a+mY0/N8gRuzyKhieBzQN7Ykayzz70uoMavBXut2rUlLgzw==}
|
||||||
|
|
||||||
|
'@kevisual/kv-login@0.1.15':
|
||||||
|
resolution: {integrity: sha512-PqFvhi11/zAIqdEePsINzhIX9QYf25nmvsIoT5kD8NB5663ZDsSPdgBiT/Jn3U+jx50a43ndNFwOTNuZkdp0Iw==}
|
||||||
|
|
||||||
'@kevisual/load@0.0.6':
|
'@kevisual/load@0.0.6':
|
||||||
resolution: {integrity: sha512-+3YTFehRcZ1haGel5DKYMUwmi5i6f2psyaPZlfkKU/cOXgkpwoG9/BEqPCnPjicKqqnksEpixVRkyHJ+5bjLVA==}
|
resolution: {integrity: sha512-+3YTFehRcZ1haGel5DKYMUwmi5i6f2psyaPZlfkKU/cOXgkpwoG9/BEqPCnPjicKqqnksEpixVRkyHJ+5bjLVA==}
|
||||||
|
|
||||||
@@ -853,11 +859,11 @@ packages:
|
|||||||
resolution: {integrity: sha512-Kp/WSt411ZWYvgXy6uiv5RmhHrz9cAml05AQPrtdAp7eUqvIDbMGPnML25OKbzR3RJ1q4wgENxDTvlGPa9+Mww==}
|
resolution: {integrity: sha512-Kp/WSt411ZWYvgXy6uiv5RmhHrz9cAml05AQPrtdAp7eUqvIDbMGPnML25OKbzR3RJ1q4wgENxDTvlGPa9+Mww==}
|
||||||
engines: {node: '>=20.19'}
|
engines: {node: '>=20.19'}
|
||||||
|
|
||||||
'@tanstack/react-router-devtools@1.162.6':
|
'@tanstack/react-router-devtools@1.162.8':
|
||||||
resolution: {integrity: sha512-oSUmF5IrBUc67apoQlJ1lvIRD0EalXuAmfY9GIzW0x10BrdV/ecgCudT4Mo0U/mdXQuF4BHg4Et6MMIvuvdtaA==}
|
resolution: {integrity: sha512-dDohOU8eNbCukLQNcuocCTnvwSu8Z1XwbKvPc4U7KDYoUTUlJls48fXl5y/ENThK/nZEsA7i3oCy1BcX42OOlw==}
|
||||||
engines: {node: '>=20.19'}
|
engines: {node: '>=20.19'}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@tanstack/react-router': ^1.162.6
|
'@tanstack/react-router': ^1.162.8
|
||||||
'@tanstack/router-core': ^1.162.6
|
'@tanstack/router-core': ^1.162.6
|
||||||
react: '>=18.0.0 || >=19.0.0'
|
react: '>=18.0.0 || >=19.0.0'
|
||||||
react-dom: '>=18.0.0 || >=19.0.0'
|
react-dom: '>=18.0.0 || >=19.0.0'
|
||||||
@@ -865,8 +871,8 @@ packages:
|
|||||||
'@tanstack/router-core':
|
'@tanstack/router-core':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@tanstack/react-router@1.162.6':
|
'@tanstack/react-router@1.162.8':
|
||||||
resolution: {integrity: sha512-aj/gQ+TrODVjQYG5spXAlJTd4ZGaqUuRG/CJaQn8mMdc7h7NrATCnxDOugz99WPOl0bzMYQum7cTEhjCe2zOgA==}
|
resolution: {integrity: sha512-WunoknGI5ielJ833yl/F7Vq4nv/OWzrJVBsMgyxX16Db1DwVvX/B5zTg8EMjdZUOJ7ONpvur3t4aq7KQiYRagQ==}
|
||||||
engines: {node: '>=20.19'}
|
engines: {node: '>=20.19'}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
react: '>=18.0.0 || >=19.0.0'
|
react: '>=18.0.0 || >=19.0.0'
|
||||||
@@ -896,12 +902,12 @@ packages:
|
|||||||
resolution: {integrity: sha512-mzkD3kfPW50xgX1hI8YrQx76+hshsUmpI9fVvS741L0cRQKH7bCIYTvcNHkz3sftZwmjt/lh+k7arV1AMLaWhA==}
|
resolution: {integrity: sha512-mzkD3kfPW50xgX1hI8YrQx76+hshsUmpI9fVvS741L0cRQKH7bCIYTvcNHkz3sftZwmjt/lh+k7arV1AMLaWhA==}
|
||||||
engines: {node: '>=20.19'}
|
engines: {node: '>=20.19'}
|
||||||
|
|
||||||
'@tanstack/router-plugin@1.162.6':
|
'@tanstack/router-plugin@1.162.8':
|
||||||
resolution: {integrity: sha512-4Q+MtwHqqCawazM6I3NG6wVFDJdBfJ4uJYggUzGY0ir2bgbOULvvAlDD2tBHEOIoNhQwLcnr2AQ0JQJSWl8iZA==}
|
resolution: {integrity: sha512-u6ZqYEjIA8jXge6JSl5UFFYPzVRciee0vwDwtkIF1Sb+G4cDdDaEjYQ4aN1/va8D7n3LptYvSMU8SeGkX+9slA==}
|
||||||
engines: {node: '>=20.19'}
|
engines: {node: '>=20.19'}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@rsbuild/core': '>=1.0.2'
|
'@rsbuild/core': '>=1.0.2'
|
||||||
'@tanstack/react-router': ^1.162.6
|
'@tanstack/react-router': ^1.162.8
|
||||||
vite: '>=5.0.0 || >=6.0.0 || >=7.0.0'
|
vite: '>=5.0.0 || >=6.0.0 || >=7.0.0'
|
||||||
vite-plugin-solid: ^2.11.10
|
vite-plugin-solid: ^2.11.10
|
||||||
webpack: '>=5.92.0'
|
webpack: '>=5.92.0'
|
||||||
@@ -1854,7 +1860,7 @@ snapshots:
|
|||||||
'@jridgewell/resolve-uri': 3.1.2
|
'@jridgewell/resolve-uri': 3.1.2
|
||||||
'@jridgewell/sourcemap-codec': 1.5.5
|
'@jridgewell/sourcemap-codec': 1.5.5
|
||||||
|
|
||||||
'@kevisual/api@0.0.59(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(use-sync-external-store@1.6.0(react@19.2.4))':
|
'@kevisual/api@0.0.60(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(use-sync-external-store@1.6.0(react@19.2.4))':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@kevisual/context': 0.0.8
|
'@kevisual/context': 0.0.8
|
||||||
'@kevisual/js-filter': 0.0.5
|
'@kevisual/js-filter': 0.0.5
|
||||||
@@ -1885,6 +1891,8 @@ snapshots:
|
|||||||
|
|
||||||
'@kevisual/js-filter@0.0.5': {}
|
'@kevisual/js-filter@0.0.5': {}
|
||||||
|
|
||||||
|
'@kevisual/kv-login@0.1.15': {}
|
||||||
|
|
||||||
'@kevisual/load@0.0.6':
|
'@kevisual/load@0.0.6':
|
||||||
dependencies:
|
dependencies:
|
||||||
eventemitter3: 5.0.4
|
eventemitter3: 5.0.4
|
||||||
@@ -2190,9 +2198,9 @@ snapshots:
|
|||||||
|
|
||||||
'@tanstack/history@1.161.4': {}
|
'@tanstack/history@1.161.4': {}
|
||||||
|
|
||||||
'@tanstack/react-router-devtools@1.162.6(@tanstack/react-router@1.162.6(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(@tanstack/router-core@1.162.6)(csstype@3.2.3)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
|
'@tanstack/react-router-devtools@1.162.8(@tanstack/react-router@1.162.8(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(@tanstack/router-core@1.162.6)(csstype@3.2.3)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@tanstack/react-router': 1.162.6(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
'@tanstack/react-router': 1.162.8(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||||
'@tanstack/router-devtools-core': 1.162.6(@tanstack/router-core@1.162.6)(csstype@3.2.3)
|
'@tanstack/router-devtools-core': 1.162.6(@tanstack/router-core@1.162.6)(csstype@3.2.3)
|
||||||
react: 19.2.4
|
react: 19.2.4
|
||||||
react-dom: 19.2.4(react@19.2.4)
|
react-dom: 19.2.4(react@19.2.4)
|
||||||
@@ -2201,7 +2209,7 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- csstype
|
- csstype
|
||||||
|
|
||||||
'@tanstack/react-router@1.162.6(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
|
'@tanstack/react-router@1.162.8(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@tanstack/history': 1.161.4
|
'@tanstack/history': 1.161.4
|
||||||
'@tanstack/react-store': 0.9.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
'@tanstack/react-store': 0.9.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||||
@@ -2251,7 +2259,7 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
'@tanstack/router-plugin@1.162.6(@tanstack/react-router@1.162.6(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(vite@8.0.0-beta.14(@types/node@25.3.0)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0))':
|
'@tanstack/router-plugin@1.162.8(@tanstack/react-router@1.162.8(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(vite@8.0.0-beta.14(@types/node@25.3.0)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0))':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/core': 7.29.0
|
'@babel/core': 7.29.0
|
||||||
'@babel/plugin-syntax-jsx': 7.28.6(@babel/core@7.29.0)
|
'@babel/plugin-syntax-jsx': 7.28.6(@babel/core@7.29.0)
|
||||||
@@ -2267,7 +2275,7 @@ snapshots:
|
|||||||
unplugin: 2.3.11
|
unplugin: 2.3.11
|
||||||
zod: 3.25.76
|
zod: 3.25.76
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@tanstack/react-router': 1.162.6(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
'@tanstack/react-router': 1.162.8(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||||
vite: 8.0.0-beta.14(@types/node@25.3.0)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0)
|
vite: 8.0.0-beta.14(@types/node@25.3.0)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0)
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Query, DataOpts } from '@kevisual/query';
|
import { Query, DataOpts } from '@kevisual/query';
|
||||||
import { QueryLoginBrowser } from '@kevisual/api/login'
|
import { QueryLoginBrowser } from '@kevisual/api/query-login'
|
||||||
import { useContextKey } from '@kevisual/context';
|
import { useContextKey } from '@kevisual/context';
|
||||||
export const query = useContextKey('query', new Query({
|
export const query = useContextKey('query', new Query({
|
||||||
url: '/api/router',
|
url: '/api/router',
|
||||||
|
|||||||
@@ -2,6 +2,10 @@ import { useEffect } from "react"
|
|||||||
import { useLayoutStore } from "./store"
|
import { useLayoutStore } from "./store"
|
||||||
import { useShallow } from "zustand/shallow"
|
import { useShallow } from "zustand/shallow"
|
||||||
import { LogIn, LockKeyhole } from "lucide-react"
|
import { LogIn, LockKeyhole } from "lucide-react"
|
||||||
|
export { BaseHeader } from './modules/BaseHeader'
|
||||||
|
import { useMemo } from 'react';
|
||||||
|
import { useLocation, useNavigate } from '@tanstack/react-router';
|
||||||
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
children?: React.ReactNode,
|
children?: React.ReactNode,
|
||||||
@@ -11,12 +15,18 @@ export const AuthProvider = ({ children, mustLogin }: Props) => {
|
|||||||
const store = useLayoutStore(useShallow(state => ({
|
const store = useLayoutStore(useShallow(state => ({
|
||||||
init: state.init,
|
init: state.init,
|
||||||
me: state.me,
|
me: state.me,
|
||||||
|
openLinkList: state.openLinkList,
|
||||||
})));
|
})));
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
store.init()
|
store.init()
|
||||||
}, [])
|
}, [])
|
||||||
|
const location = useLocation()
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const isOpen = useMemo(() => {
|
||||||
|
return store.openLinkList.some(item => location.pathname.startsWith(item))
|
||||||
|
}, [location.pathname])
|
||||||
const loginUrl = '/root/login/?redirect=' + encodeURIComponent(window.location.href);
|
const loginUrl = '/root/login/?redirect=' + encodeURIComponent(window.location.href);
|
||||||
if (mustLogin && !store.me) {
|
if (mustLogin && !store.me && !isOpen) {
|
||||||
return (
|
return (
|
||||||
<div className="w-full h-full min-h-screen flex items-center justify-center bg-background">
|
<div className="w-full h-full min-h-screen flex items-center justify-center bg-background">
|
||||||
<div className="flex flex-col items-center gap-6 p-10 rounded-2xl border border-border bg-card shadow-lg max-w-sm w-full mx-4">
|
<div className="flex flex-col items-center gap-6 p-10 rounded-2xl border border-border bg-card shadow-lg max-w-sm w-full mx-4">
|
||||||
@@ -30,7 +40,8 @@ export const AuthProvider = ({ children, mustLogin }: Props) => {
|
|||||||
<div
|
<div
|
||||||
className="inline-flex items-center justify-center gap-2 w-full px-6 py-2.5 rounded-lg bg-foreground text-background text-sm font-medium transition-opacity hover:opacity-80 active:opacity-70"
|
className="inline-flex items-center justify-center gap-2 w-full px-6 py-2.5 rounded-lg bg-foreground text-background text-sm font-medium transition-opacity hover:opacity-80 active:opacity-70"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
window.open(loginUrl, '_self')
|
// window.open(loginUrl, '_blank');
|
||||||
|
navigate({ to: '/login' });
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<LogIn className="w-4 h-4" />
|
<LogIn className="w-4 h-4" />
|
||||||
|
|||||||
75
src/pages/auth/modules/BaseHeader.tsx
Normal file
75
src/pages/auth/modules/BaseHeader.tsx
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
import { Home, User, LogIn, LogOut } from 'lucide-react';
|
||||||
|
import { Link } from '@tanstack/react-router'
|
||||||
|
import { useLayoutStore } from '../store';
|
||||||
|
import { useShallow } from 'zustand/shallow';
|
||||||
|
import { useMemo } from 'react';
|
||||||
|
|
||||||
|
export const BaseHeader = (props: { main?: React.ComponentType | null }) => {
|
||||||
|
const store = useLayoutStore(useShallow(state => ({
|
||||||
|
me: state.me,
|
||||||
|
clearMe: state.clearMe,
|
||||||
|
})));
|
||||||
|
|
||||||
|
const meInfo = useMemo(() => {
|
||||||
|
if (!store.me) {
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
onClick={() => window.open('/root/login/?redirect=' + encodeURIComponent(window.location.href), '_self')}
|
||||||
|
className="flex items-center gap-2 px-3 py-1.5 text-sm text-gray-600 hover:text-gray-900 hover:bg-gray-100 rounded-lg transition-colors cursor-pointer"
|
||||||
|
>
|
||||||
|
<LogIn className="w-4 h-4" />
|
||||||
|
<span>去登录</span>
|
||||||
|
</button>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
{store.me.avatar && (
|
||||||
|
<img
|
||||||
|
src={store.me.avatar}
|
||||||
|
alt="Avatar"
|
||||||
|
className="w-8 h-8 rounded-full object-cover"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{!store.me.avatar && (
|
||||||
|
<div className="w-8 h-8 rounded-full bg-gray-200 flex items-center justify-center">
|
||||||
|
<User className="w-4 h-4 text-gray-500" />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<span className="font-medium text-gray-700">{store.me?.username}</span>
|
||||||
|
<button
|
||||||
|
onClick={() => store.clearMe?.()}
|
||||||
|
className="flex items-center gap-1 px-2 py-1 text-sm text-gray-500 hover:text-red-600 hover:bg-red-50 rounded-lg transition-colors cursor-pointer"
|
||||||
|
title="退出登录"
|
||||||
|
>
|
||||||
|
<LogOut className="w-4 h-4" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}, [store.me, store.clearMe])
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="flex gap-2 text-lg w-full h-12 items-center justify-between">
|
||||||
|
<div className='px-2'>
|
||||||
|
<Link
|
||||||
|
to="/"
|
||||||
|
activeProps={{
|
||||||
|
className: 'font-bold',
|
||||||
|
}}
|
||||||
|
activeOptions={{ exact: true }}
|
||||||
|
>
|
||||||
|
<Home className='w-5 h-5' />
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
<div className='mr-4'>
|
||||||
|
{meInfo}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<hr />
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const LayoutMain = () => {
|
||||||
|
return <BaseHeader />
|
||||||
|
}
|
||||||
49
src/pages/auth/page.tsx
Normal file
49
src/pages/auth/page.tsx
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
import { useContextKey } from '@kevisual/context';
|
||||||
|
import '@kevisual/kv-login';
|
||||||
|
import { checkPluginLogin } from '@kevisual/kv-login'
|
||||||
|
import { useEffect } from 'react';
|
||||||
|
import { useLayoutStore } from './store';
|
||||||
|
import { useShallow } from 'zustand/shallow';
|
||||||
|
import { useNavigate } from '@tanstack/react-router';
|
||||||
|
|
||||||
|
export const LoginComponent = ({ onLoginSuccess }: { onLoginSuccess: () => void }) => {
|
||||||
|
useEffect(() => {
|
||||||
|
// 监听登录成功事件
|
||||||
|
const handleLoginSuccess = () => {
|
||||||
|
console.log('监听到登录成功事件,关闭弹窗');
|
||||||
|
onLoginSuccess();
|
||||||
|
};
|
||||||
|
const loginEmitter = useContextKey('login-emitter')
|
||||||
|
console.log('KvLogin Types:', loginEmitter);
|
||||||
|
|
||||||
|
loginEmitter.on('login-success', handleLoginSuccess);
|
||||||
|
|
||||||
|
// 清理监听器
|
||||||
|
return () => {
|
||||||
|
loginEmitter.off('login-success', handleLoginSuccess);
|
||||||
|
};
|
||||||
|
}, [onLoginSuccess]);
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
return (<kv-login></kv-login>)
|
||||||
|
}
|
||||||
|
export const App = () => {
|
||||||
|
const store = useLayoutStore(useShallow((state) => ({
|
||||||
|
init: state.init
|
||||||
|
})));
|
||||||
|
useEffect(() => {
|
||||||
|
checkPluginLogin();
|
||||||
|
}, []);
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const handleLoginSuccess = async () => {
|
||||||
|
await store.init()
|
||||||
|
navigate({ to: '/' })
|
||||||
|
};
|
||||||
|
return <div className='w-full h-full'>
|
||||||
|
<div className='w-md mx-auto flex mt-20'>
|
||||||
|
<LoginComponent onLoginSuccess={handleLoginSuccess} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
export default App;
|
||||||
@@ -19,11 +19,14 @@ export type LayoutStore = {
|
|||||||
setOpenUser: (openUser: boolean) => void;
|
setOpenUser: (openUser: boolean) => void;
|
||||||
me?: UserInfo;
|
me?: UserInfo;
|
||||||
setMe: (me: UserInfo) => void;
|
setMe: (me: UserInfo) => void;
|
||||||
|
clearMe: () => void;
|
||||||
getMe: () => Promise<void>;
|
getMe: () => Promise<void>;
|
||||||
switchOrg: (username?: string) => Promise<void>;
|
switchOrg: (username?: string) => Promise<void>;
|
||||||
isAdmin: boolean;
|
isAdmin: boolean;
|
||||||
setIsAdmin: (isAdmin: boolean) => void
|
setIsAdmin: (isAdmin: boolean) => void
|
||||||
init: () => Promise<void>;
|
init: () => Promise<void>;
|
||||||
|
openLinkList: string[];
|
||||||
|
setOpenLinkList: (openLinkList: string[]) => void;
|
||||||
};
|
};
|
||||||
export const useLayoutStore = create<LayoutStore>((set, get) => ({
|
export const useLayoutStore = create<LayoutStore>((set, get) => ({
|
||||||
open: false,
|
open: false,
|
||||||
@@ -32,6 +35,10 @@ export const useLayoutStore = create<LayoutStore>((set, get) => ({
|
|||||||
setOpenUser: (openUser) => set({ openUser }),
|
setOpenUser: (openUser) => set({ openUser }),
|
||||||
me: undefined,
|
me: undefined,
|
||||||
setMe: (me) => set({ me }),
|
setMe: (me) => set({ me }),
|
||||||
|
clearMe: () => {
|
||||||
|
set({ me: undefined, isAdmin: false });
|
||||||
|
window.location.href = '/root/login/?redirect=' + encodeURIComponent(window.location.href);
|
||||||
|
},
|
||||||
getMe: async () => {
|
getMe: async () => {
|
||||||
const res = await queryLogin.getMe();
|
const res = await queryLogin.getMe();
|
||||||
if (res.code === 200) {
|
if (res.code === 200) {
|
||||||
@@ -53,13 +60,20 @@ export const useLayoutStore = create<LayoutStore>((set, get) => ({
|
|||||||
isAdmin: false,
|
isAdmin: false,
|
||||||
setIsAdmin: (isAdmin) => set({ isAdmin }),
|
setIsAdmin: (isAdmin) => set({ isAdmin }),
|
||||||
init: async () => {
|
init: async () => {
|
||||||
const token = await queryLogin.checkTokenValid()
|
const token = await queryLogin.getToken();
|
||||||
if (token) {
|
if (token) {
|
||||||
const user = await queryLogin.checkLocalUser() as UserInfo;
|
set({ me: {} })
|
||||||
|
const me = await queryLogin.getMe();
|
||||||
|
// const user = await queryLogin.checkLocalUser() as UserInfo;
|
||||||
|
const user = me.code === 200 ? me.data : undefined;
|
||||||
if (user) {
|
if (user) {
|
||||||
set({ me: user });
|
set({ me: user });
|
||||||
set({ isAdmin: user.orgs?.includes?.('admin') || false });
|
set({ isAdmin: user.orgs?.includes?.('admin') || false });
|
||||||
|
} else {
|
||||||
|
set({ me: undefined, isAdmin: false });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
openLinkList: ['/login'],
|
||||||
|
setOpenLinkList: (openLinkList) => set({ openLinkList }),
|
||||||
}));
|
}));
|
||||||
@@ -9,10 +9,16 @@
|
|||||||
// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.
|
// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.
|
||||||
|
|
||||||
import { Route as rootRouteImport } from './routes/__root'
|
import { Route as rootRouteImport } from './routes/__root'
|
||||||
|
import { Route as LoginRouteImport } from './routes/login'
|
||||||
import { Route as DemoRouteImport } from './routes/demo'
|
import { Route as DemoRouteImport } from './routes/demo'
|
||||||
import { Route as CnbBoardRouteImport } from './routes/cnb-board'
|
import { Route as CnbBoardRouteImport } from './routes/cnb-board'
|
||||||
import { Route as IndexRouteImport } from './routes/index'
|
import { Route as IndexRouteImport } from './routes/index'
|
||||||
|
|
||||||
|
const LoginRoute = LoginRouteImport.update({
|
||||||
|
id: '/login',
|
||||||
|
path: '/login',
|
||||||
|
getParentRoute: () => rootRouteImport,
|
||||||
|
} as any)
|
||||||
const DemoRoute = DemoRouteImport.update({
|
const DemoRoute = DemoRouteImport.update({
|
||||||
id: '/demo',
|
id: '/demo',
|
||||||
path: '/demo',
|
path: '/demo',
|
||||||
@@ -33,34 +39,45 @@ export interface FileRoutesByFullPath {
|
|||||||
'/': typeof IndexRoute
|
'/': typeof IndexRoute
|
||||||
'/cnb-board': typeof CnbBoardRoute
|
'/cnb-board': typeof CnbBoardRoute
|
||||||
'/demo': typeof DemoRoute
|
'/demo': typeof DemoRoute
|
||||||
|
'/login': typeof LoginRoute
|
||||||
}
|
}
|
||||||
export interface FileRoutesByTo {
|
export interface FileRoutesByTo {
|
||||||
'/': typeof IndexRoute
|
'/': typeof IndexRoute
|
||||||
'/cnb-board': typeof CnbBoardRoute
|
'/cnb-board': typeof CnbBoardRoute
|
||||||
'/demo': typeof DemoRoute
|
'/demo': typeof DemoRoute
|
||||||
|
'/login': typeof LoginRoute
|
||||||
}
|
}
|
||||||
export interface FileRoutesById {
|
export interface FileRoutesById {
|
||||||
__root__: typeof rootRouteImport
|
__root__: typeof rootRouteImport
|
||||||
'/': typeof IndexRoute
|
'/': typeof IndexRoute
|
||||||
'/cnb-board': typeof CnbBoardRoute
|
'/cnb-board': typeof CnbBoardRoute
|
||||||
'/demo': typeof DemoRoute
|
'/demo': typeof DemoRoute
|
||||||
|
'/login': typeof LoginRoute
|
||||||
}
|
}
|
||||||
export interface FileRouteTypes {
|
export interface FileRouteTypes {
|
||||||
fileRoutesByFullPath: FileRoutesByFullPath
|
fileRoutesByFullPath: FileRoutesByFullPath
|
||||||
fullPaths: '/' | '/cnb-board' | '/demo'
|
fullPaths: '/' | '/cnb-board' | '/demo' | '/login'
|
||||||
fileRoutesByTo: FileRoutesByTo
|
fileRoutesByTo: FileRoutesByTo
|
||||||
to: '/' | '/cnb-board' | '/demo'
|
to: '/' | '/cnb-board' | '/demo' | '/login'
|
||||||
id: '__root__' | '/' | '/cnb-board' | '/demo'
|
id: '__root__' | '/' | '/cnb-board' | '/demo' | '/login'
|
||||||
fileRoutesById: FileRoutesById
|
fileRoutesById: FileRoutesById
|
||||||
}
|
}
|
||||||
export interface RootRouteChildren {
|
export interface RootRouteChildren {
|
||||||
IndexRoute: typeof IndexRoute
|
IndexRoute: typeof IndexRoute
|
||||||
CnbBoardRoute: typeof CnbBoardRoute
|
CnbBoardRoute: typeof CnbBoardRoute
|
||||||
DemoRoute: typeof DemoRoute
|
DemoRoute: typeof DemoRoute
|
||||||
|
LoginRoute: typeof LoginRoute
|
||||||
}
|
}
|
||||||
|
|
||||||
declare module '@tanstack/react-router' {
|
declare module '@tanstack/react-router' {
|
||||||
interface FileRoutesByPath {
|
interface FileRoutesByPath {
|
||||||
|
'/login': {
|
||||||
|
id: '/login'
|
||||||
|
path: '/login'
|
||||||
|
fullPath: '/login'
|
||||||
|
preLoaderRoute: typeof LoginRouteImport
|
||||||
|
parentRoute: typeof rootRouteImport
|
||||||
|
}
|
||||||
'/demo': {
|
'/demo': {
|
||||||
id: '/demo'
|
id: '/demo'
|
||||||
path: '/demo'
|
path: '/demo'
|
||||||
@@ -89,6 +106,7 @@ const rootRouteChildren: RootRouteChildren = {
|
|||||||
IndexRoute: IndexRoute,
|
IndexRoute: IndexRoute,
|
||||||
CnbBoardRoute: CnbBoardRoute,
|
CnbBoardRoute: CnbBoardRoute,
|
||||||
DemoRoute: DemoRoute,
|
DemoRoute: DemoRoute,
|
||||||
|
LoginRoute: LoginRoute,
|
||||||
}
|
}
|
||||||
export const routeTree = rootRouteImport
|
export const routeTree = rootRouteImport
|
||||||
._addFileChildren(rootRouteChildren)
|
._addFileChildren(rootRouteChildren)
|
||||||
|
|||||||
@@ -1,43 +1,18 @@
|
|||||||
// import { LayoutMain } from '@/modules/layout'
|
import { LayoutMain } from '@/pages/auth/modules/BaseHeader';
|
||||||
const LayoutMain = null;
|
import { Outlet, createRootRoute } from '@tanstack/react-router'
|
||||||
import { Link, Outlet, createRootRoute } from '@tanstack/react-router'
|
|
||||||
import { TanStackRouterDevtools } from '@tanstack/react-router-devtools'
|
import { TanStackRouterDevtools } from '@tanstack/react-router-devtools'
|
||||||
import { Toaster } from '@/components/ui/sonner'
|
import { Toaster } from '@/components/ui/sonner'
|
||||||
import { AuthProvider } from '@/pages/auth'
|
import { AuthProvider } from '@/pages/auth'
|
||||||
import { TooltipProvider } from '@/components/ui/tooltip'
|
import { TooltipProvider } from '@/components/ui/tooltip'
|
||||||
import { Home } from 'lucide-react';
|
|
||||||
export const Route = createRootRoute({
|
export const Route = createRootRoute({
|
||||||
component: RootComponent,
|
component: RootComponent,
|
||||||
})
|
})
|
||||||
|
|
||||||
const BaseHeader = (props: { main?: React.ComponentType | null }) => {
|
|
||||||
if (props.main) {
|
|
||||||
const MainComponent = props.main
|
|
||||||
return <MainComponent />
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<div className="flex gap-2 text-lg w-full h-12 items-center">
|
|
||||||
<div className='px-2'>
|
|
||||||
<Link
|
|
||||||
to="/"
|
|
||||||
activeProps={{
|
|
||||||
className: 'font-bold',
|
|
||||||
}}
|
|
||||||
activeOptions={{ exact: true }}
|
|
||||||
>
|
|
||||||
<Home className='w-5 h-5' />
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<hr />
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
function RootComponent() {
|
function RootComponent() {
|
||||||
return (
|
return (
|
||||||
<div className='h-full overflow-hidden'>
|
<div className='h-full overflow-hidden'>
|
||||||
<BaseHeader main={LayoutMain} />
|
<LayoutMain />
|
||||||
<AuthProvider mustLogin={true}>
|
<AuthProvider mustLogin={true}>
|
||||||
<TooltipProvider>
|
<TooltipProvider>
|
||||||
<main className='h-[calc(100%-3rem)] overflow-auto scrollbar'>
|
<main className='h-[calc(100%-3rem)] overflow-auto scrollbar'>
|
||||||
|
|||||||
9
src/routes/login.tsx
Normal file
9
src/routes/login.tsx
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import { createFileRoute } from '@tanstack/react-router'
|
||||||
|
import App from '@/pages/auth/page'
|
||||||
|
export const Route = createFileRoute('/login')({
|
||||||
|
component: RouteComponent,
|
||||||
|
})
|
||||||
|
|
||||||
|
function RouteComponent() {
|
||||||
|
return <App />
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user