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:
xiongxiao
2026-03-19 01:15:03 +08:00
committed by cnb
parent fa527192c9
commit 64eabb7aa8
15 changed files with 349 additions and 64 deletions

View File

@@ -25,19 +25,36 @@ src/
``` ```
pages/page-app/ pages/page-app/
├── components/ # 模块专属组件 ├── components/ # 模块专属组件
├── store/ # 模块状态管理 ├── hooks/ # 模块 React Query hooksAPI 查询封装)
── 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 工具函数

View File

@@ -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
View 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"
}
}

View File

@@ -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);
}

View File

@@ -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());

View File

@@ -0,0 +1 @@
export * from './use-api-query';

View 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,
});
};

View File

@@ -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,

View File

@@ -83,7 +83,6 @@ export const BaseHeader = (props: { main?: React.ComponentType | null }) => {
{meInfo} {meInfo}
</div> </div>
</div> </div>
<hr />
</> </>
) )
} }

View File

@@ -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
View 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;

View 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 || '请求失败');
}
}
}
})

View File

@@ -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
View 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 />
}

View File

@@ -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: {