feat: update dependencies and add new features
- Update package.json dependencies (@tanstack/react-query, @kevisual/*, etc.) - Add openLink utility function in basename.ts - Add stackQueryClient for React Query in query.ts - Refactor auth store with serverData and query hooks - Add demo route and auth hooks - Add VitePWA plugin and env config - Update AGENTS.md documentation Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
23
AGENTS.md
23
AGENTS.md
@@ -25,19 +25,36 @@ src/
|
|||||||
```
|
```
|
||||||
pages/page-app/
|
pages/page-app/
|
||||||
├── components/ # 模块专属组件
|
├── components/ # 模块专属组件
|
||||||
├── store/ # 模块状态管理
|
├── hooks/ # 模块 React Query hooks(API 查询封装)
|
||||||
└── module/ # 模块功能函数
|
├── modules/ # 模块功能函数(UI 组件、工具函数等)
|
||||||
|
└── store/ # 模块状态管理(Zustand)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### hooks/ 文件夹说明
|
||||||
|
|
||||||
|
每个模块的 `hooks/` 文件夹用于封装与该模块相关的 React Query hooks:
|
||||||
|
|
||||||
|
- **use-api-query.ts**: 使用 `@tanstack/react-query` 的 `useQuery` 封装 API 调用
|
||||||
|
- 定义 `queryKeys` 常量用于缓存标识
|
||||||
|
- 封装 `useQuery` hooks 用于数据获取(GET 请求)
|
||||||
|
- 封装 `useMutation` hooks 用于数据修改(POST/PUT/DELETE 请求)
|
||||||
|
- 支持预取(prefetch)和无限滚动(infinite query)
|
||||||
|
- **index.ts**: 导出模块所有 hooks,便于统一导入使用
|
||||||
|
|
||||||
### 状态和数据获取
|
### 状态和数据获取
|
||||||
|
|
||||||
|
- **@tanstack/react-query** 用于数据获取、缓存和状态管理
|
||||||
|
- 在模块的 `hooks/` 文件夹中封装 API 调用
|
||||||
|
- QueryClient 实例位于 `src/modules/query.ts`
|
||||||
|
- 在 `src/routes/__root.tsx` 中通过 `QueryClientProvider` 提供
|
||||||
- **Zustand** 用于全局状态管理
|
- **Zustand** 用于全局状态管理
|
||||||
- **@kevisual/query** 用于数据获取(QueryClient 实例位于 `src/modules/query.ts`)
|
- **@kevisual/query** 用于底层 API 请求封装
|
||||||
- **React Hook Form** 用于表单管理
|
- **React Hook Form** 用于表单管理
|
||||||
|
|
||||||
## 核心依赖
|
## 核心依赖
|
||||||
|
|
||||||
- **@base-ui/react**: Headless UI 基础组件
|
- **@base-ui/react**: Headless UI 基础组件
|
||||||
|
- **@tanstack/react-query**: 数据获取、缓存和状态管理(配合 hooks/ 使用)
|
||||||
- **@tanstack/react-router**: 基于 TanStack Router 插件的文件路由
|
- **@tanstack/react-router**: 基于 TanStack Router 插件的文件路由
|
||||||
- **class-variance-authority**: 基于变体的样式系统
|
- **class-variance-authority**: 基于变体的样式系统
|
||||||
- **clsx + tailwind-merge**: 通过 `cn()` 提供 className 工具函数
|
- **clsx + tailwind-merge**: 通过 `cn()` 提供 className 工具函数
|
||||||
|
|||||||
38
package.json
38
package.json
@@ -13,53 +13,55 @@
|
|||||||
"dist"
|
"dist"
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@base-ui/react": "^1.2.0",
|
"@base-ui/react": "^1.3.0",
|
||||||
"@kevisual/router": "0.1.1",
|
"@kevisual/router": "0.1.6",
|
||||||
"@tanstack/react-router": "^1.166.7",
|
"@tanstack/react-query": "^5.91.0",
|
||||||
|
"@tanstack/react-router": "^1.167.4",
|
||||||
"@tanstack/react-table": "^8.21.3",
|
"@tanstack/react-table": "^8.21.3",
|
||||||
"@uiw/react-codemirror": "^4.25.8",
|
"@uiw/react-codemirror": "^4.25.8",
|
||||||
"@uiw/react-md-editor": "^4.0.11",
|
"@uiw/react-md-editor": "^4.0.11",
|
||||||
"antd": "^6.3.2",
|
"antd": "^6.3.3",
|
||||||
"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",
|
||||||
"dayjs": "^1.11.19",
|
"dayjs": "^1.11.20",
|
||||||
"eruda": "^3.4.3",
|
"eruda": "^3.4.3",
|
||||||
"es-toolkit": "^1.45.1",
|
"es-toolkit": "^1.45.1",
|
||||||
"fuse.js": "^7.1.0",
|
"fuse.js": "^7.1.0",
|
||||||
"idb-keyval": "^6.2.2",
|
"idb-keyval": "^6.2.2",
|
||||||
"lucide-react": "^0.577.0",
|
"lucide-react": "^0.577.0",
|
||||||
"nanoid": "^5.1.6",
|
"nanoid": "^5.1.7",
|
||||||
"next-themes": "^0.4.6",
|
"next-themes": "^0.4.6",
|
||||||
"react": "19.2.4",
|
"react": "19.2.4",
|
||||||
"react-dom": "19.2.4",
|
"react-dom": "19.2.4",
|
||||||
"react-hook-form": "^7.71.2",
|
"react-hook-form": "^7.71.2",
|
||||||
"react-resizable-panels": "^4.7.2",
|
"react-resizable-panels": "^4.7.3",
|
||||||
"sonner": "^2.0.7",
|
"sonner": "^2.0.7",
|
||||||
"valtio": "^2.3.1",
|
"valtio": "^2.3.1",
|
||||||
"zod": "^4.3.6",
|
"zod": "^4.3.6",
|
||||||
"zustand": "^5.0.11"
|
"zustand": "^5.0.12"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@kevisual/ai": "^0.0.28",
|
"@kevisual/ai": "^0.0.28",
|
||||||
"@kevisual/api": "^0.0.62",
|
"@kevisual/api": "^0.0.64",
|
||||||
"@kevisual/context": "^0.0.8",
|
"@kevisual/context": "^0.0.8",
|
||||||
"@kevisual/js-filter": "^0.0.6",
|
"@kevisual/js-filter": "^0.0.6",
|
||||||
"@kevisual/kv-login": "^0.1.17",
|
"@kevisual/kv-login": "^0.1.18",
|
||||||
"@kevisual/query": "^0.0.53",
|
"@kevisual/query": "^0.0.54",
|
||||||
"@kevisual/types": "^0.0.12",
|
"@kevisual/types": "^0.0.12",
|
||||||
"@tailwindcss/vite": "^4.2.1",
|
"@tailwindcss/vite": "^4.2.2",
|
||||||
"@tanstack/react-router-devtools": "^1.166.7",
|
"@tanstack/react-router-devtools": "^1.166.9",
|
||||||
"@tanstack/router-plugin": "^1.166.7",
|
"@tanstack/router-plugin": "^1.166.13",
|
||||||
"@types/node": "^25.4.0",
|
"@types/node": "^25.5.0",
|
||||||
"@types/react": "^19.2.14",
|
"@types/react": "^19.2.14",
|
||||||
"@types/react-dom": "^19.2.3",
|
"@types/react-dom": "^19.2.3",
|
||||||
"@vitejs/plugin-react": "^5.1.4",
|
"@vitejs/plugin-react": "^6.0.1",
|
||||||
"dotenv": "^17.3.1",
|
"dotenv": "^17.3.1",
|
||||||
"tailwind-merge": "^3.5.0",
|
"tailwind-merge": "^3.5.0",
|
||||||
"tailwindcss": "^4.2.1",
|
"tailwindcss": "^4.2.2",
|
||||||
"tw-animate-css": "^1.4.0",
|
"tw-animate-css": "^1.4.0",
|
||||||
"typescript": "^5.9.3",
|
"typescript": "^5.9.3",
|
||||||
"vite": "v8.0.0-beta.16"
|
"vite": "v8.0.0",
|
||||||
|
"vite-plugin-pwa": "^1.2.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
44
public/auth.json
Normal file
44
public/auth.json
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
{
|
||||||
|
"metadata": {
|
||||||
|
"name": "kevisual",
|
||||||
|
"share": "public"
|
||||||
|
},
|
||||||
|
"registry": "https://kevisual.cn/root/ai/kevisual/frontend/vite-react-template",
|
||||||
|
"clone": {
|
||||||
|
".": {
|
||||||
|
"enabled": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"syncd": [
|
||||||
|
{
|
||||||
|
"files": [
|
||||||
|
"**/*"
|
||||||
|
],
|
||||||
|
"registry": ""
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"scripts": {
|
||||||
|
"auth": "ev sync clone -l -i https://kevisual.cn/root/ai/kevisual/frontend/vite-react-template/public/auth.json"
|
||||||
|
},
|
||||||
|
"sync": {
|
||||||
|
"AGENTS.md": "https://kevisual.cn/root/ai/kevisual/frontend/vite-react-template/AGENTS.md",
|
||||||
|
"vite.config.ts": "https://kevisual.cn/root/ai/kevisual/frontend/vite-react-template/vite.config.ts",
|
||||||
|
"src/main.tsx": "https://kevisual.cn/root/ai/kevisual/frontend/vite-react-template/src/main.tsx",
|
||||||
|
"public/auth.json": "https://kevisual.cn/root/ai/kevisual/frontend/vite-react-template/public/auth.json",
|
||||||
|
"src/agents/index.ts": "https://kevisual.cn/root/ai/kevisual/frontend/vite-react-template/src/agents/index.ts",
|
||||||
|
"src/modules/basename.ts": "https://kevisual.cn/root/ai/kevisual/frontend/vite-react-template/src/modules/basename.ts",
|
||||||
|
"src/modules/query.ts": "https://kevisual.cn/root/ai/kevisual/frontend/vite-react-template/src/modules/query.ts",
|
||||||
|
"src/routes/demo.tsx": "https://kevisual.cn/root/ai/kevisual/frontend/vite-react-template/src/routes/demo.tsx",
|
||||||
|
"src/routes/index.tsx": "https://kevisual.cn/root/ai/kevisual/frontend/vite-react-template/src/routes/index.tsx",
|
||||||
|
"src/routes/login.tsx": "https://kevisual.cn/root/ai/kevisual/frontend/vite-react-template/src/routes/login.tsx",
|
||||||
|
"src/styles/theme.css": "https://kevisual.cn/root/ai/kevisual/frontend/vite-react-template/src/styles/theme.css",
|
||||||
|
"src/pages/auth/index.tsx": "https://kevisual.cn/root/ai/kevisual/frontend/vite-react-template/src/pages/auth/index.tsx",
|
||||||
|
"src/pages/auth/page.tsx": "https://kevisual.cn/root/ai/kevisual/frontend/vite-react-template/src/pages/auth/page.tsx",
|
||||||
|
"src/pages/auth/store.ts": "https://kevisual.cn/root/ai/kevisual/frontend/vite-react-template/src/pages/auth/store.ts",
|
||||||
|
"src/pages/demo/page.tsx": "https://kevisual.cn/root/ai/kevisual/frontend/vite-react-template/src/pages/demo/page.tsx",
|
||||||
|
"src/pages/auth/hooks/index.ts": "https://kevisual.cn/root/ai/kevisual/frontend/vite-react-template/src/pages/auth/hooks/index.ts",
|
||||||
|
"src/pages/auth/hooks/use-api-query.ts": "https://kevisual.cn/root/ai/kevisual/frontend/vite-react-template/src/pages/auth/hooks/use-api-query.ts",
|
||||||
|
"src/pages/auth/modules/BaseHeader.tsx": "https://kevisual.cn/root/ai/kevisual/frontend/vite-react-template/src/pages/auth/modules/BaseHeader.tsx",
|
||||||
|
"src/pages/demo/store/index.ts": "https://kevisual.cn/root/ai/kevisual/frontend/vite-react-template/src/pages/demo/store/index.ts"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -20,3 +20,13 @@ export const getDynamicBasename = (): string => {
|
|||||||
// 默认使用构建时的 basename
|
// 默认使用构建时的 basename
|
||||||
return basename
|
return basename
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const openLink = (path: string, target: string = '_self') => {
|
||||||
|
if (path.startsWith('http://') || path.startsWith('https://')) {
|
||||||
|
window.open(path, target);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const url = new URL(path, window.location.origin);
|
||||||
|
url.pathname = wrapBasename(url.pathname);
|
||||||
|
window.open(url.toString(), target);
|
||||||
|
}
|
||||||
@@ -1,20 +1,18 @@
|
|||||||
import { Query } from '@kevisual/query';
|
import { Query, DataOpts } from '@kevisual/query';
|
||||||
import { QueryLoginBrowser } from '@kevisual/api/query-login'
|
import { QueryLoginBrowser } from '@kevisual/api/query-login'
|
||||||
import { useContextKey } from '@kevisual/context';
|
import { useContextKey } from '@kevisual/context';
|
||||||
export const query = useContextKey('query', () => {
|
import { QueryClient } from '@tanstack/react-query';
|
||||||
return new Query({
|
|
||||||
url: '/api/router',
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
export const queryClient = useContextKey('queryClient', () => {
|
export const query = useContextKey('query', new Query({
|
||||||
return new Query({
|
url: '/api/router',
|
||||||
url: '/client/router',
|
}));
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
export const queryLogin = useContextKey('queryLogin', () => {
|
export const queryClient = useContextKey('queryClient', new Query({
|
||||||
return new QueryLoginBrowser({
|
url: '/client/router',
|
||||||
query: query
|
}));
|
||||||
});
|
|
||||||
});
|
export const queryLogin = useContextKey('queryLogin', new QueryLoginBrowser({
|
||||||
|
query: query
|
||||||
|
}));
|
||||||
|
|
||||||
|
export const stackQueryClient = useContextKey('stackQueryClient', new QueryClient());
|
||||||
1
src/pages/auth/hooks/index.ts
Normal file
1
src/pages/auth/hooks/index.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export * from './use-api-query';
|
||||||
55
src/pages/auth/hooks/use-api-query.ts
Normal file
55
src/pages/auth/hooks/use-api-query.ts
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||||
|
import { queryLogin } from '@/modules/query';
|
||||||
|
import { toast } from 'sonner';
|
||||||
|
import type { UserInfo } from '../store';
|
||||||
|
|
||||||
|
export const authQueryKeys = {
|
||||||
|
me: ['auth', 'me'] as const,
|
||||||
|
token: ['auth', 'token'] as const,
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export const useMe = () => {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: authQueryKeys.me,
|
||||||
|
queryFn: async () => {
|
||||||
|
const res = await queryLogin.getMe();
|
||||||
|
if (res.code === 200) {
|
||||||
|
return res.data;
|
||||||
|
}
|
||||||
|
throw new Error(res.message || 'Failed to fetch user info');
|
||||||
|
},
|
||||||
|
staleTime: 1000 * 60 * 5, // 5 minutes
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useSwitchOrg = () => {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
|
return useMutation({
|
||||||
|
mutationFn: async (username?: string) => {
|
||||||
|
const res = await queryLogin.switchUser(username || '');
|
||||||
|
if (res.code === 200) {
|
||||||
|
return res.data;
|
||||||
|
}
|
||||||
|
throw new Error(res.message || 'Switch failed');
|
||||||
|
},
|
||||||
|
onSuccess: () => {
|
||||||
|
toast.success('切换成功');
|
||||||
|
queryClient.invalidateQueries({ queryKey: authQueryKeys.me });
|
||||||
|
setTimeout(() => {
|
||||||
|
window.location.reload();
|
||||||
|
}, 1000);
|
||||||
|
},
|
||||||
|
onError: (error) => {
|
||||||
|
toast.error(error.message || '请求失败');
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useGetToken = () => {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: authQueryKeys.token,
|
||||||
|
queryFn: () => queryLogin.getToken(),
|
||||||
|
staleTime: Infinity,
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -6,7 +6,6 @@ export { BaseHeader } from './modules/BaseHeader'
|
|||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import { useLocation, useNavigate } from '@tanstack/react-router';
|
import { useLocation, useNavigate } from '@tanstack/react-router';
|
||||||
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
children?: React.ReactNode,
|
children?: React.ReactNode,
|
||||||
mustLogin?: boolean,
|
mustLogin?: boolean,
|
||||||
|
|||||||
@@ -83,7 +83,6 @@ export const BaseHeader = (props: { main?: React.ComponentType | null }) => {
|
|||||||
{meInfo}
|
{meInfo}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<hr />
|
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
|
|
||||||
import { queryLogin } from '@/modules/query';
|
import { queryLogin, stackQueryClient } from '@/modules/query';
|
||||||
import { create } from 'zustand';
|
import { create } from 'zustand';
|
||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
type UserInfo = {
|
import { authQueryKeys } from './hooks';
|
||||||
|
export type UserInfo = {
|
||||||
id?: string;
|
id?: string;
|
||||||
username?: string;
|
username?: string;
|
||||||
nickname?: string | null;
|
nickname?: string | null;
|
||||||
@@ -37,6 +38,9 @@ export type LayoutStore = {
|
|||||||
setLinks: (links: HeaderLink[]) => void;
|
setLinks: (links: HeaderLink[]) => void;
|
||||||
showBaseHeader: boolean;
|
showBaseHeader: boolean;
|
||||||
setShowBaseHeader: (showBaseHeader: boolean) => void;
|
setShowBaseHeader: (showBaseHeader: boolean) => void;
|
||||||
|
serverData: Record<string, any> | null;
|
||||||
|
setServerData: (data: Record<string, any>) => void;
|
||||||
|
initConvex: () => Promise<void>;
|
||||||
};
|
};
|
||||||
type HeaderLink = {
|
type HeaderLink = {
|
||||||
title?: string;
|
title?: string;
|
||||||
@@ -56,19 +60,25 @@ export const useLayoutStore = create<LayoutStore>((set, get) => ({
|
|||||||
setMe: (me) => set({ me }),
|
setMe: (me) => set({ me }),
|
||||||
clearMe: () => {
|
clearMe: () => {
|
||||||
set({ me: undefined, isAdmin: false });
|
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 data = await stackQueryClient.fetchQuery({
|
||||||
if (res.code === 200) {
|
queryKey: authQueryKeys.me,
|
||||||
set({ me: res.data });
|
queryFn: async () => {
|
||||||
set({ isAdmin: res.data.orgs?.includes?.('admin') || false });
|
const res = await queryLogin.getMe();
|
||||||
}
|
if (res.code === 200) {
|
||||||
|
return res.data;
|
||||||
|
}
|
||||||
|
throw new Error(res.message || 'Failed to fetch user info');
|
||||||
|
},
|
||||||
|
});
|
||||||
|
set({ me: data, isAdmin: data?.orgs?.includes?.('admin') || false });
|
||||||
},
|
},
|
||||||
switchOrg: async (username?: string) => {
|
switchOrg: async (username?: string) => {
|
||||||
const res = await queryLogin.switchUser(username || '');
|
const res = await queryLogin.switchUser(username || '');
|
||||||
if (res.code === 200) {
|
if (res.code === 200) {
|
||||||
toast.success('切换成功');
|
toast.success('切换成功');
|
||||||
|
stackQueryClient.invalidateQueries({ queryKey: authQueryKeys.me });
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
window.location.reload();
|
window.location.reload();
|
||||||
}, 1000);
|
}, 1000);
|
||||||
@@ -79,20 +89,32 @@ 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.getToken();
|
await queryLogin.init();
|
||||||
|
const token = await queryLogin.checkLocalToken();
|
||||||
if (token) {
|
if (token) {
|
||||||
set({ me: {} })
|
set({ me: {} });
|
||||||
const me = await queryLogin.getMe();
|
try {
|
||||||
// const user = await queryLogin.checkLocalUser() as UserInfo;
|
// const data = await stackQueryClient.fetchQuery({
|
||||||
const user = me.code === 200 ? me.data : undefined;
|
// queryKey: authQueryKeys.me,
|
||||||
if (user) {
|
// }) as UserInfo;
|
||||||
set({ me: user });
|
const userInfo = await queryLogin.checkLocalUser();
|
||||||
set({ isAdmin: user.orgs?.includes?.('admin') || false });
|
if (userInfo) {
|
||||||
} else {
|
set({ me: userInfo as UserInfo, isAdmin: userInfo.orgs?.includes?.('admin') || false });
|
||||||
|
} else {
|
||||||
|
set({ me: undefined, isAdmin: false });
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
set({ me: undefined, isAdmin: false });
|
set({ me: undefined, isAdmin: false });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// 获取服务端数据
|
||||||
|
// @ts-ignore
|
||||||
|
const sererData = window.__SERVER_DATA__;
|
||||||
|
if (sererData) {
|
||||||
|
set({ serverData: sererData });
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
initConvex: async () => { },
|
||||||
openLinkList: ['/login'],
|
openLinkList: ['/login'],
|
||||||
setOpenLinkList: (openLinkList) => set({ openLinkList }),
|
setOpenLinkList: (openLinkList) => set({ openLinkList }),
|
||||||
loginPageConfig: {
|
loginPageConfig: {
|
||||||
@@ -107,4 +129,6 @@ export const useLayoutStore = create<LayoutStore>((set, get) => ({
|
|||||||
setLinks: (links) => set({ links }),
|
setLinks: (links) => set({ links }),
|
||||||
showBaseHeader: true,
|
showBaseHeader: true,
|
||||||
setShowBaseHeader: (showBaseHeader) => set({ showBaseHeader }),
|
setShowBaseHeader: (showBaseHeader) => set({ showBaseHeader }),
|
||||||
|
serverData: null,
|
||||||
|
setServerData: (data) => set({ serverData: data }),
|
||||||
}));
|
}));
|
||||||
|
|||||||
8
src/pages/demo/page.tsx
Normal file
8
src/pages/demo/page.tsx
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
import { useDemoStore } from './store/index'
|
||||||
|
export const App = () => {
|
||||||
|
const demoStore = useDemoStore()
|
||||||
|
console.log('demo', demoStore.formData)
|
||||||
|
return <div>App</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
export default App;
|
||||||
95
src/pages/demo/store/index.ts
Normal file
95
src/pages/demo/store/index.ts
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
import { create } from 'zustand';
|
||||||
|
import { query } from '@/modules/query';
|
||||||
|
import { toast } from 'sonner';
|
||||||
|
|
||||||
|
interface Data {
|
||||||
|
id: string;
|
||||||
|
[key: string]: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
type State = {
|
||||||
|
formData: Record<string, any>;
|
||||||
|
setFormData: (data: Record<string, any>) => void;
|
||||||
|
showEdit: boolean;
|
||||||
|
setShowEdit: (showEdit: boolean) => void;
|
||||||
|
loading: boolean;
|
||||||
|
setLoading: (loading: boolean) => void;
|
||||||
|
list: Data[];
|
||||||
|
getItem: (id: string) => Promise<any>;
|
||||||
|
getList: () => Promise<any>;
|
||||||
|
updateData: (data: Data) => Promise<void>;
|
||||||
|
deleteData: (id: string) => Promise<void>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useDemoStore = create<State>((set, get) => {
|
||||||
|
return {
|
||||||
|
formData: {},
|
||||||
|
setFormData: (data) => set({ formData: data }),
|
||||||
|
showEdit: false,
|
||||||
|
setShowEdit: (showEdit) => set({ showEdit }),
|
||||||
|
loading: false,
|
||||||
|
setLoading: (loading) => set({ loading }),
|
||||||
|
list: [],
|
||||||
|
getItem: async (id) => {
|
||||||
|
const { setLoading } = get();
|
||||||
|
setLoading(true);
|
||||||
|
try {
|
||||||
|
const res = await query.post({
|
||||||
|
path: 'demo',
|
||||||
|
key: 'item',
|
||||||
|
data: { id }
|
||||||
|
})
|
||||||
|
if (res.code === 200) {
|
||||||
|
return res;
|
||||||
|
} else {
|
||||||
|
toast.error(res.message || '请求失败');
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
getList: async () => {
|
||||||
|
const { setLoading } = get();
|
||||||
|
setLoading(true);
|
||||||
|
try {
|
||||||
|
const res = await query.post({
|
||||||
|
path: 'demo',
|
||||||
|
key: 'list'
|
||||||
|
});
|
||||||
|
if (res.code === 200) {
|
||||||
|
const list = res.data?.list || []
|
||||||
|
set({ list });
|
||||||
|
} else {
|
||||||
|
toast.error(res.message || '请求失败');
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
updateData: async (data) => {
|
||||||
|
const res = await query.post({
|
||||||
|
path: 'demo',
|
||||||
|
key: 'update',
|
||||||
|
data
|
||||||
|
})
|
||||||
|
if (res.code === 200) {
|
||||||
|
get().getList()
|
||||||
|
} else {
|
||||||
|
toast.error(res.message || '请求失败');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
deleteData: async (id) => {
|
||||||
|
const res = await query.post({
|
||||||
|
path: 'demo',
|
||||||
|
key: 'delete',
|
||||||
|
data: { id }
|
||||||
|
})
|
||||||
|
if (res.code === 200) {
|
||||||
|
get().getList()
|
||||||
|
} else {
|
||||||
|
toast.error(res.message || '请求失败');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
@@ -11,6 +11,7 @@
|
|||||||
import { Route as rootRouteImport } from './routes/__root'
|
import { Route as rootRouteImport } from './routes/__root'
|
||||||
import { Route as ViewRouteImport } from './routes/view'
|
import { Route as ViewRouteImport } from './routes/view'
|
||||||
import { Route as LoginRouteImport } from './routes/login'
|
import { Route as LoginRouteImport } from './routes/login'
|
||||||
|
import { Route as DemoRouteImport } from './routes/demo'
|
||||||
import { Route as ConsoleRouteImport } from './routes/console'
|
import { Route as ConsoleRouteImport } from './routes/console'
|
||||||
import { Route as IdRouteImport } from './routes/$id'
|
import { Route as IdRouteImport } from './routes/$id'
|
||||||
import { Route as IndexRouteImport } from './routes/index'
|
import { Route as IndexRouteImport } from './routes/index'
|
||||||
@@ -25,6 +26,11 @@ const LoginRoute = LoginRouteImport.update({
|
|||||||
path: '/login',
|
path: '/login',
|
||||||
getParentRoute: () => rootRouteImport,
|
getParentRoute: () => rootRouteImport,
|
||||||
} as any)
|
} as any)
|
||||||
|
const DemoRoute = DemoRouteImport.update({
|
||||||
|
id: '/demo',
|
||||||
|
path: '/demo',
|
||||||
|
getParentRoute: () => rootRouteImport,
|
||||||
|
} as any)
|
||||||
const ConsoleRoute = ConsoleRouteImport.update({
|
const ConsoleRoute = ConsoleRouteImport.update({
|
||||||
id: '/console',
|
id: '/console',
|
||||||
path: '/console',
|
path: '/console',
|
||||||
@@ -45,6 +51,7 @@ export interface FileRoutesByFullPath {
|
|||||||
'/': typeof IndexRoute
|
'/': typeof IndexRoute
|
||||||
'/$id': typeof IdRoute
|
'/$id': typeof IdRoute
|
||||||
'/console': typeof ConsoleRoute
|
'/console': typeof ConsoleRoute
|
||||||
|
'/demo': typeof DemoRoute
|
||||||
'/login': typeof LoginRoute
|
'/login': typeof LoginRoute
|
||||||
'/view': typeof ViewRoute
|
'/view': typeof ViewRoute
|
||||||
}
|
}
|
||||||
@@ -52,6 +59,7 @@ export interface FileRoutesByTo {
|
|||||||
'/': typeof IndexRoute
|
'/': typeof IndexRoute
|
||||||
'/$id': typeof IdRoute
|
'/$id': typeof IdRoute
|
||||||
'/console': typeof ConsoleRoute
|
'/console': typeof ConsoleRoute
|
||||||
|
'/demo': typeof DemoRoute
|
||||||
'/login': typeof LoginRoute
|
'/login': typeof LoginRoute
|
||||||
'/view': typeof ViewRoute
|
'/view': typeof ViewRoute
|
||||||
}
|
}
|
||||||
@@ -60,21 +68,23 @@ export interface FileRoutesById {
|
|||||||
'/': typeof IndexRoute
|
'/': typeof IndexRoute
|
||||||
'/$id': typeof IdRoute
|
'/$id': typeof IdRoute
|
||||||
'/console': typeof ConsoleRoute
|
'/console': typeof ConsoleRoute
|
||||||
|
'/demo': typeof DemoRoute
|
||||||
'/login': typeof LoginRoute
|
'/login': typeof LoginRoute
|
||||||
'/view': typeof ViewRoute
|
'/view': typeof ViewRoute
|
||||||
}
|
}
|
||||||
export interface FileRouteTypes {
|
export interface FileRouteTypes {
|
||||||
fileRoutesByFullPath: FileRoutesByFullPath
|
fileRoutesByFullPath: FileRoutesByFullPath
|
||||||
fullPaths: '/' | '/$id' | '/console' | '/login' | '/view'
|
fullPaths: '/' | '/$id' | '/console' | '/demo' | '/login' | '/view'
|
||||||
fileRoutesByTo: FileRoutesByTo
|
fileRoutesByTo: FileRoutesByTo
|
||||||
to: '/' | '/$id' | '/console' | '/login' | '/view'
|
to: '/' | '/$id' | '/console' | '/demo' | '/login' | '/view'
|
||||||
id: '__root__' | '/' | '/$id' | '/console' | '/login' | '/view'
|
id: '__root__' | '/' | '/$id' | '/console' | '/demo' | '/login' | '/view'
|
||||||
fileRoutesById: FileRoutesById
|
fileRoutesById: FileRoutesById
|
||||||
}
|
}
|
||||||
export interface RootRouteChildren {
|
export interface RootRouteChildren {
|
||||||
IndexRoute: typeof IndexRoute
|
IndexRoute: typeof IndexRoute
|
||||||
IdRoute: typeof IdRoute
|
IdRoute: typeof IdRoute
|
||||||
ConsoleRoute: typeof ConsoleRoute
|
ConsoleRoute: typeof ConsoleRoute
|
||||||
|
DemoRoute: typeof DemoRoute
|
||||||
LoginRoute: typeof LoginRoute
|
LoginRoute: typeof LoginRoute
|
||||||
ViewRoute: typeof ViewRoute
|
ViewRoute: typeof ViewRoute
|
||||||
}
|
}
|
||||||
@@ -95,6 +105,13 @@ declare module '@tanstack/react-router' {
|
|||||||
preLoaderRoute: typeof LoginRouteImport
|
preLoaderRoute: typeof LoginRouteImport
|
||||||
parentRoute: typeof rootRouteImport
|
parentRoute: typeof rootRouteImport
|
||||||
}
|
}
|
||||||
|
'/demo': {
|
||||||
|
id: '/demo'
|
||||||
|
path: '/demo'
|
||||||
|
fullPath: '/demo'
|
||||||
|
preLoaderRoute: typeof DemoRouteImport
|
||||||
|
parentRoute: typeof rootRouteImport
|
||||||
|
}
|
||||||
'/console': {
|
'/console': {
|
||||||
id: '/console'
|
id: '/console'
|
||||||
path: '/console'
|
path: '/console'
|
||||||
@@ -123,6 +140,7 @@ const rootRouteChildren: RootRouteChildren = {
|
|||||||
IndexRoute: IndexRoute,
|
IndexRoute: IndexRoute,
|
||||||
IdRoute: IdRoute,
|
IdRoute: IdRoute,
|
||||||
ConsoleRoute: ConsoleRoute,
|
ConsoleRoute: ConsoleRoute,
|
||||||
|
DemoRoute: DemoRoute,
|
||||||
LoginRoute: LoginRoute,
|
LoginRoute: LoginRoute,
|
||||||
ViewRoute: ViewRoute,
|
ViewRoute: ViewRoute,
|
||||||
}
|
}
|
||||||
|
|||||||
9
src/routes/demo.tsx
Normal file
9
src/routes/demo.tsx
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import { createFileRoute } from '@tanstack/react-router'
|
||||||
|
import App from '@/pages/demo/page'
|
||||||
|
export const Route = createFileRoute('/demo')({
|
||||||
|
component: RouteComponent,
|
||||||
|
})
|
||||||
|
|
||||||
|
function RouteComponent() {
|
||||||
|
return <App />
|
||||||
|
}
|
||||||
@@ -4,11 +4,14 @@ import path from 'path';
|
|||||||
import pkgs from './package.json';
|
import pkgs from './package.json';
|
||||||
import tailwindcss from '@tailwindcss/vite';
|
import tailwindcss from '@tailwindcss/vite';
|
||||||
import { tanstackRouter } from '@tanstack/router-plugin/vite'
|
import { tanstackRouter } from '@tanstack/router-plugin/vite'
|
||||||
import 'dotenv/config'
|
import dotenv from 'dotenv';
|
||||||
const isDev = process.env.NODE_ENV === 'development';
|
import { VitePWA } from 'vite-plugin-pwa';
|
||||||
const basename = isDev ? '/' : pkgs?.basename || '/';
|
|
||||||
let target = process.env.VITE_API_URL || 'http://localhost:51515';
|
|
||||||
|
|
||||||
|
const env = dotenv.config().parsed || {};
|
||||||
|
const isDev = env.NODE_ENV === 'development' || process.env.NODE_ENV === 'development';
|
||||||
|
const basename = isDev ? '/' : pkgs?.basename || '/';
|
||||||
|
|
||||||
|
let target = env.VITE_API_URL || process.env.API_URL || 'http://localhost:51515';
|
||||||
const apiProxy = { target: target, changeOrigin: true, ws: true, rewriteWsOrigin: true, secure: false, cookieDomainRewrite: 'localhost' };
|
const apiProxy = { target: target, changeOrigin: true, ws: true, rewriteWsOrigin: true, secure: false, cookieDomainRewrite: 'localhost' };
|
||||||
let proxy = {
|
let proxy = {
|
||||||
'/root/': apiProxy,
|
'/root/': apiProxy,
|
||||||
@@ -26,7 +29,10 @@ export default defineConfig({
|
|||||||
autoCodeSplitting: true,
|
autoCodeSplitting: true,
|
||||||
}),
|
}),
|
||||||
react(),
|
react(),
|
||||||
tailwindcss()
|
tailwindcss(),
|
||||||
|
VitePWA({
|
||||||
|
injectRegister: 'auto',
|
||||||
|
}),
|
||||||
],
|
],
|
||||||
resolve: {
|
resolve: {
|
||||||
alias: {
|
alias: {
|
||||||
|
|||||||
Reference in New Issue
Block a user