ess-main-template 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/.env.example +7 -0
  2. package/.prettierrc +8 -0
  3. package/README.md +73 -0
  4. package/commitlint.config.js +6 -0
  5. package/eslint.config.js +32 -0
  6. package/index.html +22 -0
  7. package/mock/auth.mock.ts +39 -0
  8. package/mock/user.mock.ts +83 -0
  9. package/package.json +64 -0
  10. package/public/favicon.svg +1 -0
  11. package/public/icons.svg +24 -0
  12. package/src/App.css +182 -0
  13. package/src/App.tsx +29 -0
  14. package/src/assets/fonts/.gitkeep +0 -0
  15. package/src/assets/hero.png +0 -0
  16. package/src/assets/react.svg +1 -0
  17. package/src/assets/vite.svg +1 -0
  18. package/src/components/AuthButton.tsx +23 -0
  19. package/src/components/LangSwitch.tsx +31 -0
  20. package/src/components/RouteProgress.tsx +25 -0
  21. package/src/components/StationPicker.tsx +24 -0
  22. package/src/components/ThemeSwitch.tsx +18 -0
  23. package/src/constants/bus-events.ts +15 -0
  24. package/src/constants/index.ts +25 -0
  25. package/src/hooks/useAuth.ts +17 -0
  26. package/src/i18n/index.ts +32 -0
  27. package/src/i18n/locales/en/auth.json +15 -0
  28. package/src/i18n/locales/en/common.json +33 -0
  29. package/src/i18n/locales/en/menu.json +33 -0
  30. package/src/i18n/locales/zh/auth.json +15 -0
  31. package/src/i18n/locales/zh/common.json +33 -0
  32. package/src/i18n/locales/zh/menu.json +33 -0
  33. package/src/index.css +109 -0
  34. package/src/layouts/BasicLayout.tsx +73 -0
  35. package/src/layouts/BlankLayout.tsx +7 -0
  36. package/src/layouts/components/RightContent.tsx +43 -0
  37. package/src/main.tsx +13 -0
  38. package/src/pages/403.tsx +23 -0
  39. package/src/pages/404.tsx +23 -0
  40. package/src/pages/Dashboard/index.tsx +15 -0
  41. package/src/pages/Login/index.module.less +23 -0
  42. package/src/pages/Login/index.tsx +58 -0
  43. package/src/router/AppEntry.tsx +93 -0
  44. package/src/router/AuthGuard.tsx +19 -0
  45. package/src/router/componentMap.ts +14 -0
  46. package/src/router/generateRoutes.tsx +55 -0
  47. package/src/router/index.tsx +18 -0
  48. package/src/services/auth.ts +42 -0
  49. package/src/store/appStore.ts +51 -0
  50. package/src/store/userStore.ts +81 -0
  51. package/src/styles/global.less +40 -0
  52. package/src/styles/mixins.less +56 -0
  53. package/src/styles/reset.less +49 -0
  54. package/src/styles/variables.less +50 -0
  55. package/src/types/i18next.d.ts +16 -0
  56. package/src/types/less.d.ts +6 -0
  57. package/src/types/wujie-react.d.ts +37 -0
  58. package/src/utils/auth.ts +28 -0
  59. package/src/utils/request.ts +118 -0
  60. package/src/wujie/SubApp.tsx +72 -0
  61. package/src/wujie/bus.ts +38 -0
  62. package/src/wujie/config.ts +33 -0
  63. package/src/wujie/props.ts +42 -0
  64. package/src/wujie/useBusSync.ts +67 -0
  65. package/tsconfig.app.json +34 -0
  66. package/tsconfig.json +4 -0
  67. package/tsconfig.node.json +24 -0
  68. package/vite.config.ts +26 -0
@@ -0,0 +1,50 @@
1
+ // 品牌色(储能行业 - 绿色系)
2
+ @color-primary: #00b96b;
3
+ @color-info: #1890ff;
4
+ @color-error: #ff4d4f;
5
+ @color-warning: #faad14;
6
+ @color-success: #52c41a;
7
+
8
+ // 文字色
9
+ @text-color: rgba(0, 0, 0, 0.88);
10
+ @text-color-secondary: rgba(0, 0, 0, 0.65);
11
+ @text-color-disabled: rgba(0, 0, 0, 0.25);
12
+
13
+ // 背景色
14
+ @bg-layout: #f0f2f5;
15
+ @bg-container: #ffffff;
16
+
17
+ // 间距
18
+ @spacing-xs: 4px;
19
+ @spacing-sm: 8px;
20
+ @spacing-md: 16px;
21
+ @spacing-lg: 24px;
22
+ @spacing-xl: 32px;
23
+
24
+ // 圆角
25
+ @border-radius-sm: 4px;
26
+ @border-radius-base: 6px;
27
+ @border-radius-lg: 8px;
28
+
29
+ // 字体
30
+ @font-family:
31
+ 'CustomFont',
32
+ -apple-system,
33
+ BlinkMacSystemFont,
34
+ 'Segoe UI',
35
+ Roboto,
36
+ sans-serif;
37
+ @font-size-sm: 12px;
38
+ @font-size-base: 14px;
39
+ @font-size-lg: 16px;
40
+ @font-size-xl: 20px;
41
+
42
+ // 阴影
43
+ @box-shadow-base: 0 2px 8px rgba(0, 0, 0, 0.08);
44
+ @box-shadow-card:
45
+ 0 1px 2px rgba(0, 0, 0, 0.03),
46
+ 0 1px 6px -1px rgba(0, 0, 0, 0.02),
47
+ 0 2px 4px rgba(0, 0, 0, 0.02);
48
+
49
+ // 过渡
50
+ @transition-duration: 0.3s;
@@ -0,0 +1,16 @@
1
+ import 'i18next'
2
+
3
+ import type zhCommon from '../i18n/locales/zh/common.json'
4
+ import type zhMenu from '../i18n/locales/zh/menu.json'
5
+ import type zhAuth from '../i18n/locales/zh/auth.json'
6
+
7
+ declare module 'i18next' {
8
+ interface CustomTypeOptions {
9
+ defaultNS: 'common'
10
+ resources: {
11
+ common: typeof zhCommon
12
+ menu: typeof zhMenu
13
+ auth: typeof zhAuth
14
+ }
15
+ }
16
+ }
@@ -0,0 +1,6 @@
1
+ declare module '*.module.less' {
2
+ const classes: { readonly [key: string]: string }
3
+ export default classes
4
+ }
5
+
6
+ declare module '*.less'
@@ -0,0 +1,37 @@
1
+ declare module 'wujie-react' {
2
+ import type { Component } from 'react'
3
+
4
+ interface WujieReactProps {
5
+ name: string
6
+ url: string
7
+ alive?: boolean
8
+ exec?: boolean
9
+ replace?: (code: string) => string
10
+ fetch?: typeof window.fetch
11
+ props?: Record<string, unknown>
12
+ attrs?: Record<string, unknown>
13
+ beforeLoad?: () => void
14
+ beforeMount?: () => void
15
+ afterMount?: () => void
16
+ beforeUnmount?: () => void
17
+ afterUnmount?: () => void
18
+ loadError?: (url: string, e: Error) => void
19
+ width?: string
20
+ height?: string
21
+ }
22
+
23
+ interface EventBus {
24
+ $on: (event: string, callback: (...args: unknown[]) => void) => EventBus
25
+ $off: (event: string, callback: (...args: unknown[]) => void) => EventBus
26
+ $emit: (event: string, ...args: unknown[]) => EventBus
27
+ }
28
+
29
+ class WujieReact extends Component<WujieReactProps> {
30
+ static bus: EventBus
31
+ static setupApp: (options: Record<string, unknown>) => void
32
+ static preloadApp: (options: Record<string, unknown>) => void
33
+ static destroyApp: (name: string) => void
34
+ }
35
+
36
+ export default WujieReact
37
+ }
@@ -0,0 +1,28 @@
1
+ import { STORAGE_KEYS } from '@/constants'
2
+
3
+ export function getAccessToken(): string | null {
4
+ return localStorage.getItem(STORAGE_KEYS.ACCESS_TOKEN)
5
+ }
6
+
7
+ export function setAccessToken(token: string): void {
8
+ localStorage.setItem(STORAGE_KEYS.ACCESS_TOKEN, token)
9
+ }
10
+
11
+ export function getRefreshToken(): string | null {
12
+ return localStorage.getItem(STORAGE_KEYS.REFRESH_TOKEN)
13
+ }
14
+
15
+ export function setRefreshToken(token: string): void {
16
+ localStorage.setItem(STORAGE_KEYS.REFRESH_TOKEN, token)
17
+ }
18
+
19
+ export function setTokens(accessToken: string, refreshToken: string): void {
20
+ setAccessToken(accessToken)
21
+ setRefreshToken(refreshToken)
22
+ }
23
+
24
+ export function clearTokens(): void {
25
+ localStorage.removeItem(STORAGE_KEYS.ACCESS_TOKEN)
26
+ localStorage.removeItem(STORAGE_KEYS.REFRESH_TOKEN)
27
+ localStorage.removeItem(STORAGE_KEYS.USER_INFO)
28
+ }
@@ -0,0 +1,118 @@
1
+ import axios, { type AxiosError, type AxiosResponse, type InternalAxiosRequestConfig } from 'axios'
2
+ import { message } from 'antd'
3
+ import { API_BASE_URL, TOKEN_CONFIG } from '@/constants'
4
+ import { getAccessToken, getRefreshToken, setTokens, clearTokens } from '@/utils/auth'
5
+
6
+ const request = axios.create({
7
+ baseURL: API_BASE_URL,
8
+ timeout: 15000,
9
+ headers: {
10
+ 'Content-Type': 'application/json',
11
+ },
12
+ })
13
+
14
+ // ---------- 请求拦截器 ----------
15
+ request.interceptors.request.use(
16
+ (config: InternalAxiosRequestConfig) => {
17
+ const token = getAccessToken()
18
+ if (token) {
19
+ config.headers.Authorization = `Bearer ${token}`
20
+ }
21
+ return config
22
+ },
23
+ (error: AxiosError) => Promise.reject(error),
24
+ )
25
+
26
+ // ---------- Token 刷新锁 ----------
27
+ let isRefreshing = false
28
+ let pendingRequests: Array<(token: string) => void> = []
29
+
30
+ function onTokenRefreshed(newToken: string) {
31
+ pendingRequests.forEach((cb) => cb(newToken))
32
+ pendingRequests = []
33
+ }
34
+
35
+ async function refreshToken(): Promise<string> {
36
+ const refresh = getRefreshToken()
37
+ const res = await axios.post<{ data: { token: string; refreshToken: string } }>(
38
+ `${API_BASE_URL}/auth/refresh`,
39
+ { refreshToken: refresh },
40
+ )
41
+ const { token, refreshToken: newRefresh } = res.data.data
42
+ setTokens(token, newRefresh)
43
+ return token
44
+ }
45
+
46
+ // ---------- 响应拦截器 ----------
47
+ request.interceptors.response.use(
48
+ (response: AxiosResponse) => response,
49
+ async (error: AxiosError<{ message?: string }>) => {
50
+ // 网络断开检测
51
+ if (!navigator.onLine) {
52
+ message.error('网络已断开,请检查网络连接')
53
+ return Promise.reject(error)
54
+ }
55
+
56
+ const originalRequest = error.config as InternalAxiosRequestConfig & { _retry?: boolean }
57
+ const status = error.response?.status
58
+
59
+ // 401: 自动刷新 token + 重放
60
+ if (status === 401 && !originalRequest._retry) {
61
+ if (isRefreshing) {
62
+ return new Promise((resolve) => {
63
+ pendingRequests.push((newToken: string) => {
64
+ originalRequest.headers.Authorization = `Bearer ${newToken}`
65
+ resolve(request(originalRequest))
66
+ })
67
+ })
68
+ }
69
+
70
+ originalRequest._retry = true
71
+ isRefreshing = true
72
+
73
+ try {
74
+ const newToken = await refreshToken()
75
+ isRefreshing = false
76
+ onTokenRefreshed(newToken)
77
+ originalRequest.headers.Authorization = `Bearer ${newToken}`
78
+ return request(originalRequest)
79
+ } catch {
80
+ isRefreshing = false
81
+ pendingRequests = []
82
+ clearTokens()
83
+ window.location.href = '/login'
84
+ return Promise.reject(error)
85
+ }
86
+ }
87
+
88
+ // 403: 无权限
89
+ if (status === 403) {
90
+ message.error('没有权限执行此操作')
91
+ }
92
+
93
+ // 500/502/503: 服务异常
94
+ if (status && status >= 500) {
95
+ message.error('服务异常,请稍后重试')
96
+ }
97
+
98
+ return Promise.reject(error)
99
+ },
100
+ )
101
+
102
+ // ---------- 带重试的请求方法(用于关键接口) ----------
103
+ export async function requestWithRetry<T>(
104
+ config: Parameters<typeof request>[0],
105
+ retries = TOKEN_CONFIG.MAX_RETRY,
106
+ ): Promise<T> {
107
+ for (let i = 0; i <= retries; i++) {
108
+ try {
109
+ return await request(config)
110
+ } catch (err) {
111
+ if (i === retries) throw err
112
+ await new Promise((r) => setTimeout(r, TOKEN_CONFIG.RETRY_DELAY * 2 ** i))
113
+ }
114
+ }
115
+ throw new Error('Max retries exceeded')
116
+ }
117
+
118
+ export default request
@@ -0,0 +1,72 @@
1
+ import { useEffect, useState } from 'react'
2
+ import WujieReact from 'wujie-react'
3
+ import { Spin, Result, Button } from 'antd'
4
+ import { useTranslation } from 'react-i18next'
5
+ import { getSubAppConfig } from './config'
6
+ import { useSubAppProps } from './props'
7
+
8
+ interface SubAppProps {
9
+ /** 子应用名称,必须与 config 中注册的 name 一致 */
10
+ name: string
11
+ }
12
+
13
+ /** 子应用加载超时时间 (ms) */
14
+ const LOAD_TIMEOUT = 15000
15
+
16
+ export default function SubApp({ name }: SubAppProps) {
17
+ const { t } = useTranslation('common')
18
+ const [loading, setLoading] = useState(true)
19
+ const [error, setError] = useState<string | null>(null)
20
+ const config = getSubAppConfig(name)
21
+ const subAppProps = useSubAppProps()
22
+
23
+ useEffect(() => {
24
+ const timer = setTimeout(() => {
25
+ setLoading(false)
26
+ setError(t('subAppTimeout', { name }))
27
+ }, LOAD_TIMEOUT)
28
+
29
+ return () => clearTimeout(timer)
30
+ }, [name, t])
31
+
32
+ if (!config) {
33
+ return <Result status="error" title={t('subAppNotFound')} subTitle={`${name}`} />
34
+ }
35
+
36
+ const handleLoadError = () => {
37
+ setLoading(false)
38
+ setError(t('subAppLoadError', { name }))
39
+ }
40
+
41
+ const handleLoading = () => {
42
+ setLoading(false)
43
+ }
44
+
45
+ if (error) {
46
+ return (
47
+ <Result
48
+ status="error"
49
+ title={t('subAppError')}
50
+ subTitle={error}
51
+ extra={
52
+ <Button type="primary" onClick={() => window.location.reload()}>
53
+ {t('retry')}
54
+ </Button>
55
+ }
56
+ />
57
+ )
58
+ }
59
+
60
+ return (
61
+ <Spin spinning={loading} tip={t('subAppLoading')} style={{ minHeight: 300 }}>
62
+ <WujieReact
63
+ name={config.name}
64
+ url={config.url}
65
+ alive={config.alive}
66
+ props={subAppProps as unknown as Record<string, unknown>}
67
+ loadError={handleLoadError}
68
+ beforeLoad={() => handleLoading()}
69
+ />
70
+ </Spin>
71
+ )
72
+ }
@@ -0,0 +1,38 @@
1
+ import WujieReact from 'wujie-react'
2
+ import { BUS_EVENTS } from '@/constants/bus-events'
3
+ import type { Theme, Locale } from '@/store/appStore'
4
+
5
+ const { bus } = WujieReact
6
+
7
+ /** 向子应用广播语言切换 */
8
+ export function emitLocaleChange(locale: Locale) {
9
+ bus.$emit(BUS_EVENTS.LOCALE_CHANGE, locale)
10
+ }
11
+
12
+ /** 向子应用广播主题切换 */
13
+ export function emitThemeChange(theme: Theme) {
14
+ bus.$emit(BUS_EVENTS.THEME_CHANGE, theme)
15
+ }
16
+
17
+ /** 向子应用广播站点切换 */
18
+ export function emitStationChange(stationId: string | null) {
19
+ bus.$emit(BUS_EVENTS.STATION_CHANGE, stationId)
20
+ }
21
+
22
+ /** 向子应用广播 Token 已刷新 */
23
+ export function emitTokenRefresh(token: string) {
24
+ bus.$emit(BUS_EVENTS.TOKEN_REFRESH, token)
25
+ }
26
+
27
+ /** 监听子应用 token-expired 事件 */
28
+ export function onTokenExpired(callback: () => void) {
29
+ bus.$on(BUS_EVENTS.TOKEN_EXPIRED, callback)
30
+ return () => bus.$off(BUS_EVENTS.TOKEN_EXPIRED, callback)
31
+ }
32
+
33
+ /** 监听子应用路由跳转事件 */
34
+ export function onNavigate(callback: (path: string) => void) {
35
+ const handler = (...args: unknown[]) => callback(args[0] as string)
36
+ bus.$on(BUS_EVENTS.NAVIGATE, handler)
37
+ return () => bus.$off(BUS_EVENTS.NAVIGATE, handler)
38
+ }
@@ -0,0 +1,33 @@
1
+ import { SUB_APP_URLS } from '@/constants'
2
+
3
+ export interface SubAppConfig {
4
+ /** 子应用唯一名称 */
5
+ name: string
6
+ /** 子应用入口 URL */
7
+ url: string
8
+ /** 是否使用 alive 模式(保活) */
9
+ alive: boolean
10
+ /** 执行模式 */
11
+ exec?: boolean
12
+ /** 自定义 fetch */
13
+ fetch?: typeof fetch
14
+ }
15
+
16
+ /** 子应用注册配置 */
17
+ export const subAppConfigs: SubAppConfig[] = [
18
+ {
19
+ name: 'sub-operation',
20
+ url: SUB_APP_URLS.OPERATION,
21
+ alive: true,
22
+ },
23
+ {
24
+ name: 'sub-analysis',
25
+ url: SUB_APP_URLS.ANALYSIS,
26
+ alive: true,
27
+ },
28
+ ]
29
+
30
+ /** 根据名称获取子应用配置 */
31
+ export function getSubAppConfig(name: string): SubAppConfig | undefined {
32
+ return subAppConfigs.find((app) => app.name === name)
33
+ }
@@ -0,0 +1,42 @@
1
+ import { useMemo } from 'react'
2
+ import { useUserStore } from '@/store/userStore'
3
+ import { useAppStore } from '@/store/appStore'
4
+
5
+ export interface SubAppPropsData {
6
+ token: string | null
7
+ userInfo: ReturnType<typeof useUserStore.getState>['userInfo']
8
+ permissions: string[]
9
+ theme: string
10
+ locale: string
11
+ currentStation: string | null
12
+ }
13
+
14
+ /** 获取传递给子应用的 props(非响应式,用于一次性读取) */
15
+ export function getSubAppProps(): SubAppPropsData {
16
+ const userState = useUserStore.getState()
17
+ const appState = useAppStore.getState()
18
+
19
+ return {
20
+ token: userState.token,
21
+ userInfo: userState.userInfo,
22
+ permissions: userState.permissions,
23
+ theme: appState.theme,
24
+ locale: appState.locale,
25
+ currentStation: appState.currentStation,
26
+ }
27
+ }
28
+
29
+ /** 响应式 hook:store 变更时自动返回最新 props,驱动 SubApp 重新传递 props 给子应用 */
30
+ export function useSubAppProps(): SubAppPropsData {
31
+ const token = useUserStore((s) => s.token)
32
+ const userInfo = useUserStore((s) => s.userInfo)
33
+ const permissions = useUserStore((s) => s.permissions)
34
+ const theme = useAppStore((s) => s.theme)
35
+ const locale = useAppStore((s) => s.locale)
36
+ const currentStation = useAppStore((s) => s.currentStation)
37
+
38
+ return useMemo(
39
+ () => ({ token, userInfo, permissions, theme, locale, currentStation }),
40
+ [token, userInfo, permissions, theme, locale, currentStation],
41
+ )
42
+ }
@@ -0,0 +1,67 @@
1
+ import { useEffect } from 'react'
2
+ import { useNavigate } from 'react-router-dom'
3
+ import { useAppStore } from '@/store/appStore'
4
+ import { useUserStore } from '@/store/userStore'
5
+ import {
6
+ emitLocaleChange,
7
+ emitThemeChange,
8
+ emitStationChange,
9
+ onTokenExpired,
10
+ onNavigate,
11
+ } from './bus'
12
+ import { refreshToken as refreshTokenApi } from '@/services/auth'
13
+
14
+ /**
15
+ * 主应用 bus 同步 hook
16
+ * - 监听 appStore 变更 → 广播给子应用
17
+ * - 监听子应用 bus 事件 → 响应处理
18
+ */
19
+ export function useBusSync() {
20
+ const navigate = useNavigate()
21
+ const theme = useAppStore((s) => s.theme)
22
+ const locale = useAppStore((s) => s.locale)
23
+ const currentStation = useAppStore((s) => s.currentStation)
24
+ const setAuth = useUserStore((s) => s.setAuth)
25
+ const logout = useUserStore((s) => s.logout)
26
+
27
+ // 主题变更 → 广播给子应用
28
+ useEffect(() => {
29
+ emitThemeChange(theme)
30
+ }, [theme])
31
+
32
+ // 语言变更 → 广播给子应用
33
+ useEffect(() => {
34
+ emitLocaleChange(locale)
35
+ }, [locale])
36
+
37
+ // 站点变更 → 广播给子应用
38
+ useEffect(() => {
39
+ emitStationChange(currentStation)
40
+ }, [currentStation])
41
+
42
+ // 监听子应用 token-expired → 刷新 token 或登出
43
+ useEffect(() => {
44
+ const unsubscribe = onTokenExpired(async () => {
45
+ try {
46
+ const res = await refreshTokenApi()
47
+ setAuth(res.data.data.accessToken, res.data.data.refreshToken)
48
+ } catch {
49
+ logout()
50
+ navigate('/login')
51
+ }
52
+ })
53
+ return () => {
54
+ unsubscribe()
55
+ }
56
+ }, [navigate, setAuth, logout])
57
+
58
+ // 监听子应用路由跳转
59
+ useEffect(() => {
60
+ const unsubscribe = onNavigate((path: string) => {
61
+ navigate(path)
62
+ })
63
+ return () => {
64
+ unsubscribe()
65
+ }
66
+ }, [navigate])
67
+ }
@@ -0,0 +1,34 @@
1
+ {
2
+ "compilerOptions": {
3
+ "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
4
+ "target": "es2023",
5
+ "lib": ["ES2023", "DOM", "DOM.Iterable"],
6
+ "module": "esnext",
7
+ "types": ["vite/client"],
8
+ "skipLibCheck": true,
9
+
10
+ /* Bundler mode */
11
+ "moduleResolution": "bundler",
12
+ "allowImportingTsExtensions": true,
13
+ "verbatimModuleSyntax": true,
14
+ "moduleDetection": "force",
15
+ "noEmit": true,
16
+ "jsx": "react-jsx",
17
+
18
+ /* Strict */
19
+ "strict": true,
20
+ "noImplicitAny": true,
21
+
22
+ /* Linting */
23
+ "noUnusedLocals": true,
24
+ "noUnusedParameters": true,
25
+ "erasableSyntaxOnly": true,
26
+ "noFallthroughCasesInSwitch": true,
27
+
28
+ /* Path alias */
29
+ "paths": {
30
+ "@/*": ["./src/*"]
31
+ }
32
+ },
33
+ "include": ["src"]
34
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,4 @@
1
+ {
2
+ "files": [],
3
+ "references": [{ "path": "./tsconfig.app.json" }, { "path": "./tsconfig.node.json" }]
4
+ }
@@ -0,0 +1,24 @@
1
+ {
2
+ "compilerOptions": {
3
+ "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
4
+ "target": "es2023",
5
+ "lib": ["ES2023"],
6
+ "module": "esnext",
7
+ "types": ["node"],
8
+ "skipLibCheck": true,
9
+
10
+ /* Bundler mode */
11
+ "moduleResolution": "bundler",
12
+ "allowImportingTsExtensions": true,
13
+ "verbatimModuleSyntax": true,
14
+ "moduleDetection": "force",
15
+ "noEmit": true,
16
+
17
+ /* Linting */
18
+ "noUnusedLocals": true,
19
+ "noUnusedParameters": true,
20
+ "erasableSyntaxOnly": true,
21
+ "noFallthroughCasesInSwitch": true
22
+ },
23
+ "include": ["vite.config.ts", "plugins"]
24
+ }
package/vite.config.ts ADDED
@@ -0,0 +1,26 @@
1
+ import { defineConfig } from 'vite'
2
+ import react from '@vitejs/plugin-react'
3
+ import { mockDevServerPlugin } from 'vite-plugin-mock-dev-server'
4
+ import path from 'node:path'
5
+
6
+ // https://vite.dev/config/
7
+ export default defineConfig({
8
+ plugins: [react(), mockDevServerPlugin()], //mockDevServerPlugin({ enabled: false })
9
+ resolve: {
10
+ alias: {
11
+ '@': path.resolve(__dirname, 'src'),
12
+ },
13
+ },
14
+ server: {
15
+ proxy: {
16
+ '^/api': { target: 'http://localhost:8080' },
17
+ },
18
+ },
19
+ css: {
20
+ preprocessorOptions: {
21
+ less: {
22
+ javascriptEnabled: true,
23
+ },
24
+ },
25
+ },
26
+ })