@lvetechs/create-app 1.0.4 → 1.0.6

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 (58) hide show
  1. package/package.json +1 -1
  2. package/templates/react/.env +3 -3
  3. package/templates/react/.env.development +3 -3
  4. package/templates/react/.env.production +3 -3
  5. package/templates/react/package.json +1 -1
  6. package/templates/react/pnpm-lock.yaml +5 -5
  7. package/templates/react/src/App.tsx +7 -1
  8. package/templates/react/src/api/notification.ts +43 -0
  9. package/templates/react/src/components/NotificationButton/index.tsx +219 -0
  10. package/templates/react/src/components/Toast/index.tsx +150 -0
  11. package/templates/react/src/hooks/useForm.ts +77 -0
  12. package/templates/react/src/layouts/DefaultLayout.tsx +2 -0
  13. package/templates/react/src/layouts/menuConfig.ts +3 -33
  14. package/templates/react/src/router/index.tsx +0 -39
  15. package/templates/react/src/stores/app.ts +141 -3
  16. package/templates/react/src/stores/notification.ts +146 -0
  17. package/templates/react/src/stores/permission.ts +173 -0
  18. package/templates/react/src/stores/user.ts +151 -4
  19. package/templates/react/src/views/home/index.tsx +167 -6
  20. package/templates/react/src/views/system/user/index.tsx +171 -5
  21. package/templates/vue/.env +2 -2
  22. package/templates/vue/.env.development +2 -2
  23. package/templates/vue/.env.production +2 -2
  24. package/templates/vue/pnpm-lock.yaml +3307 -3307
  25. package/templates/vue/src/App.vue +2 -0
  26. package/templates/vue/src/api/notification.ts +43 -0
  27. package/templates/vue/src/auto-imports.d.ts +5 -0
  28. package/templates/vue/src/components/NotificationButton/index.vue +242 -0
  29. package/templates/vue/src/components/Toast/index.vue +126 -0
  30. package/templates/vue/src/components.d.ts +2 -0
  31. package/templates/vue/src/layouts/DefaultLayout.vue +2 -0
  32. package/templates/vue/src/layouts/menuConfig.ts +3 -33
  33. package/templates/vue/src/router/index.ts +3 -88
  34. package/templates/vue/src/stores/app.ts +133 -2
  35. package/templates/vue/src/stores/notification.ts +189 -0
  36. package/templates/vue/src/stores/permission.ts +184 -0
  37. package/templates/vue/src/stores/user.ts +109 -2
  38. package/templates/vue/src/views/home/index.vue +7 -7
  39. package/templates/react/src/views/about/index.tsx +0 -40
  40. package/templates/react/src/views/login/index.tsx +0 -138
  41. package/templates/react/src/views/register/index.tsx +0 -143
  42. package/templates/react/src/views/result/fail.tsx +0 -39
  43. package/templates/react/src/views/result/success.tsx +0 -35
  44. package/templates/react/src/views/screen/index.tsx +0 -120
  45. package/templates/react/src/views/system/log/login.tsx +0 -51
  46. package/templates/react/src/views/system/log/operation.tsx +0 -47
  47. package/templates/react/src/views/system/menu/index.tsx +0 -62
  48. package/templates/react/src/views/system/role/index.tsx +0 -63
  49. package/templates/vue/src/views/about/index.vue +0 -67
  50. package/templates/vue/src/views/login/index.vue +0 -153
  51. package/templates/vue/src/views/register/index.vue +0 -169
  52. package/templates/vue/src/views/result/fail.vue +0 -92
  53. package/templates/vue/src/views/result/success.vue +0 -92
  54. package/templates/vue/src/views/screen/index.vue +0 -150
  55. package/templates/vue/src/views/system/log/login.vue +0 -51
  56. package/templates/vue/src/views/system/log/operation.vue +0 -47
  57. package/templates/vue/src/views/system/menu/index.vue +0 -58
  58. package/templates/vue/src/views/system/role/index.vue +0 -59
@@ -7,33 +7,180 @@ export interface UserInfo {
7
7
  nickname: string
8
8
  avatar: string
9
9
  roles: string[]
10
+ email?: string
11
+ phone?: string
12
+ department?: string
13
+ lastLoginTime?: number
14
+ }
15
+
16
+ export interface LoginHistoryItem {
17
+ time: number
18
+ ip: string
19
+ location?: string
10
20
  }
11
21
 
12
22
  interface UserState {
13
23
  token: string
14
24
  userInfo: UserInfo | null
25
+ /** 是否已登录(由 token 推导,也会被显式维护一份,方便订阅) */
15
26
  isLoggedIn: boolean
27
+ /** 登录历史(最近若干次登录) */
28
+ loginHistory: LoginHistoryItem[]
29
+
30
+ // ========== 计算相关(通过方法计算,而不是状态字段) ==========
31
+ /** 用户展示名称 */
32
+ getDisplayName: () => string
33
+ /** 用户头像地址 */
34
+ getAvatar: () => string
35
+ /** 是否管理员 */
36
+ getIsAdmin: () => boolean
37
+
38
+ // ========== 基础方法 ==========
16
39
  setToken: (val: string) => void
17
40
  setUserInfo: (info: UserInfo) => void
41
+ updateUserInfo: (updates: Partial<UserInfo>) => void
42
+
43
+ addLoginHistory: (ip: string, location?: string) => void
44
+
45
+ hasRole: (role: string) => boolean
46
+ addRole: (role: string) => void
47
+ removeRole: (role: string) => void
48
+
18
49
  logout: () => void
50
+
51
+ /** 模拟登录流程(实际项目中可替换为真实接口) */
52
+ login: (username: string, password: string) => Promise<UserInfo>
19
53
  }
20
54
 
21
55
  export const useUserStore = create<UserState>()(
22
56
  persist(
23
- (set) => ({
57
+ (set, get) => ({
24
58
  token: '',
25
59
  userInfo: null,
26
60
  isLoggedIn: false,
61
+ loginHistory: [],
62
+
63
+ // 计算相关
64
+ getDisplayName: () => {
65
+ const user = get().userInfo
66
+ return user?.nickname || user?.username || '未登录'
67
+ },
68
+
69
+ getAvatar: () => {
70
+ const user = get().userInfo
71
+ return user?.avatar || '/default-avatar.png'
72
+ },
73
+
74
+ getIsAdmin: () => {
75
+ const user = get().userInfo
76
+ return user?.roles?.includes('admin') ?? false
77
+ },
27
78
 
79
+ // 基础方法
28
80
  setToken: (val) => set({ token: val, isLoggedIn: !!val }),
29
81
 
30
- setUserInfo: (info) => set({ userInfo: info }),
82
+ setUserInfo: (info) =>
83
+ set(() => ({
84
+ userInfo: {
85
+ ...info,
86
+ lastLoginTime: info.lastLoginTime ?? Date.now()
87
+ }
88
+ })),
89
+
90
+ updateUserInfo: (updates) =>
91
+ set((state) =>
92
+ state.userInfo
93
+ ? {
94
+ userInfo: {
95
+ ...state.userInfo,
96
+ ...updates
97
+ }
98
+ }
99
+ : state
100
+ ),
101
+
102
+ addLoginHistory: (ip, location) =>
103
+ set((state) => {
104
+ const next: LoginHistoryItem[] = [
105
+ {
106
+ time: Date.now(),
107
+ ip,
108
+ location
109
+ },
110
+ ...state.loginHistory
111
+ ]
112
+ // 只保留最近 10 条
113
+ if (next.length > 10) {
114
+ next.length = 10
115
+ }
116
+ return { loginHistory: next }
117
+ }),
118
+
119
+ hasRole: (role) => {
120
+ const user = get().userInfo
121
+ return user?.roles?.includes(role) ?? false
122
+ },
123
+
124
+ addRole: (role) =>
125
+ set((state) => {
126
+ const user = state.userInfo
127
+ if (!user) return state
128
+ if (user.roles.includes(role)) return state
129
+ return {
130
+ userInfo: {
131
+ ...user,
132
+ roles: [...user.roles, role]
133
+ }
134
+ }
135
+ }),
136
+
137
+ removeRole: (role) =>
138
+ set((state) => {
139
+ const user = state.userInfo
140
+ if (!user) return state
141
+ return {
142
+ userInfo: {
143
+ ...user,
144
+ roles: user.roles.filter((r) => r !== role)
145
+ }
146
+ }
147
+ }),
148
+
149
+ logout: () => set({ token: '', userInfo: null, isLoggedIn: false }),
150
+
151
+ // 模拟登录(可在实际项目中替换成真正的 API 调用)
152
+ login: async (username, _password) => {
153
+ // 模拟异步请求
154
+ await new Promise((resolve) => setTimeout(resolve, 1000))
155
+
156
+ const mockUserInfo: UserInfo = {
157
+ id: 1,
158
+ username,
159
+ nickname: username === 'admin' ? '管理员' : '普通用户',
160
+ avatar: '/avatar.png',
161
+ roles: username === 'admin' ? ['admin'] : ['viewer'],
162
+ email: `${username}@example.com`,
163
+ lastLoginTime: Date.now()
164
+ }
165
+
166
+ set({
167
+ token: `mock-token-${Date.now()}`,
168
+ userInfo: mockUserInfo,
169
+ isLoggedIn: true
170
+ })
171
+
172
+ get().addLoginHistory('192.168.1.100', '本地')
31
173
 
32
- logout: () => set({ token: '', userInfo: null, isLoggedIn: false })
174
+ return mockUserInfo
175
+ }
33
176
  }),
34
177
  {
35
178
  name: 'user-store',
36
- partialize: (state) => ({ token: state.token })
179
+ partialize: (state) => ({
180
+ token: state.token,
181
+ userInfo: state.userInfo,
182
+ loginHistory: state.loginHistory
183
+ })
37
184
  }
38
185
  )
39
186
  )
@@ -1,13 +1,159 @@
1
- import { useState } from 'react'
1
+ import { createContext, useContext, useReducer } from 'react'
2
2
  import '@/styles/page-common.scss'
3
- import { Button, Container, Input, Tabs } from '@lvetechs/ui-lib'
4
- export default function Home() {
5
- const [count, setCount] = useState(0)
3
+ import { Button, Card, Container, Input, Tabs } from '@lvetechs/ui-lib'
4
+ import { useForm } from '@/hooks/useForm'
5
+
6
+ // ================ useContext + useReducer 示例 ================
7
+
8
+ interface CounterState {
9
+ count: number
10
+ }
11
+
12
+ type CounterAction = { type: 'increment' } | { type: 'decrement' } | { type: 'reset' }
13
+
14
+ const CounterContext = createContext<
15
+ | {
16
+ state: CounterState
17
+ dispatch: React.Dispatch<CounterAction>
18
+ }
19
+ | undefined
20
+ >(undefined)
21
+
22
+ function counterReducer(state: CounterState, action: CounterAction): CounterState {
23
+ switch (action.type) {
24
+ case 'increment':
25
+ return { count: state.count + 1 }
26
+ case 'decrement':
27
+ return { count: state.count - 1 }
28
+ case 'reset':
29
+ return { count: 0 }
30
+ default:
31
+ return state
32
+ }
33
+ }
34
+
35
+ function useCounter() {
36
+ const ctx = useContext(CounterContext)
37
+ if (!ctx) {
38
+ throw new Error('useCounter 必须在 <CounterProvider> 内使用')
39
+ }
40
+ return ctx
41
+ }
42
+
43
+ function CounterProvider({ children }: { children: React.ReactNode }) {
44
+ const [state, dispatch] = useReducer(counterReducer, { count: 0 })
45
+
46
+ return <CounterContext.Provider value={{ state, dispatch }}>{children}</CounterContext.Provider>
47
+ }
48
+
49
+ function CounterSection() {
50
+ const {
51
+ state: { count },
52
+ dispatch
53
+ } = useCounter()
54
+
55
+ return (
56
+ <Card padding="md">
57
+ <h3 className="mb-2 text-lg font-semibold">useContext + useReducer 计数器示例</h3>
58
+ <p className="mb-4 text-3xl font-bold text-indigo-600">{count}</p>
59
+ <Container direction="row" gap="sm">
60
+ <Button onClick={() => dispatch({ type: 'increment' })}>+1</Button>
61
+ <Button onClick={() => dispatch({ type: 'decrement' })}>-1</Button>
62
+ <Button onClick={() => dispatch({ type: 'reset' })}>重置</Button>
63
+ </Container>
64
+ </Card>
65
+ )
66
+ }
67
+
68
+ // ================ 表单 Hook 示例(useForm) ================
69
+
70
+ interface ProfileFormValues {
71
+ name: string
72
+ email: string
73
+ }
74
+
75
+ function validateProfile(values: ProfileFormValues) {
76
+ const errors: Partial<Record<keyof ProfileFormValues, string>> = {}
77
+ if (!values.name.trim()) {
78
+ errors.name = '请输入姓名'
79
+ }
80
+ if (!values.email.trim()) {
81
+ errors.email = '请输入邮箱'
82
+ } else if (!/^[\w-.]+@([\w-]+\.)+[\w-]{2,4}$/.test(values.email)) {
83
+ errors.email = '邮箱格式不正确'
84
+ }
85
+ return errors
86
+ }
87
+
88
+ function ProfileFormSection() {
89
+ const { values, errors, submitting, handleChange, handleSubmit, reset } = useForm<ProfileFormValues>(
90
+ {
91
+ initialValues: {
92
+ name: '',
93
+ email: ''
94
+ },
95
+ validate: validateProfile,
96
+ onSubmit: async (formValues) => {
97
+ // eslint-disable-next-line no-console
98
+ console.log('提交的表单数据', formValues)
99
+ await new Promise((resolve) => setTimeout(resolve, 800))
100
+ alert('提交成功(请在控制台查看提交数据)')
101
+ }
102
+ }
103
+ )
104
+
105
+ return (
106
+ <Card padding="md">
107
+ <h3 className="mb-2 text-lg font-semibold">useForm 表单示例</h3>
108
+ <p className="mb-4 text-sm text-slate-500">
109
+ 演示如何用自定义 Hook 管理表单值、校验和提交状态。
110
+ </p>
111
+ <form className="space-y-4" onSubmit={handleSubmit}>
112
+ <div>
113
+ <label className="mb-1 block text-sm font-medium text-slate-700" htmlFor="name">
114
+ 姓名
115
+ </label>
116
+ <Input
117
+ id="name"
118
+ name="name"
119
+ placeholder="请输入姓名"
120
+ value={values.name}
121
+ onChange={handleChange}
122
+ />
123
+ {errors.name && <p className="mt-1 text-xs text-rose-500">{errors.name}</p>}
124
+ </div>
125
+ <div>
126
+ <label className="mb-1 block text-sm font-medium text-slate-700" htmlFor="email">
127
+ 邮箱
128
+ </label>
129
+ <Input
130
+ id="email"
131
+ name="email"
132
+ type="email"
133
+ placeholder="请输入邮箱"
134
+ value={values.email}
135
+ onChange={handleChange}
136
+ />
137
+ {errors.email && <p className="mt-1 text-xs text-rose-500">{errors.email}</p>}
138
+ </div>
139
+ <Container direction="row" gap="sm">
140
+ <Button type="submit" disabled={submitting}>
141
+ {submitting ? '提交中...' : '提交'}
142
+ </Button>
143
+ <Button type="button" onClick={reset}>
144
+ 重置
145
+ </Button>
146
+ </Container>
147
+ </form>
148
+ </Card>
149
+ )
150
+ }
6
151
 
152
+ export default function Home() {
7
153
  return (
8
154
  <div className="home-page" style={{ maxWidth: 900, margin: '0 auto' }}>
9
155
  <div className="page-card">
10
- <h1 style={{ fontSize: 28, marginBottom: 12 }}>👋 欢迎使用 {import.meta.env.VITE_APP_TITLE}</h1>
156
+ <h1 style={{ fontSize: 28, marginBottom: 12 }}>欢迎使用 {import.meta.env.VITE_APP_TITLE}</h1>
11
157
  <p style={{ color: 'var(--text-color-secondary)', fontSize: 15, marginBottom: 24 }}>
12
158
  该模板创建自 @lvetechs/create-app react模板
13
159
  </p>
@@ -48,8 +194,23 @@ export default function Home() {
48
194
  },
49
195
  {
50
196
  label: 'TailwindCSS', key: 'TailwindCSS', children: <div className="feature-card">
51
- 实用优先的
197
+ 实用优先的 CSS 框架
52
198
  </div>
199
+ },
200
+ {
201
+ label: 'React Hooks', key: 'React Hooks', children: (
202
+ <CounterProvider>
203
+ <div className="space-y-4">
204
+ <p className="text-sm text-slate-500 mb-4">
205
+ 演示 useContext、useReducer 和自定义 useForm Hook 的实际应用
206
+ </p>
207
+ <div className="grid gap-4 md:grid-cols-2">
208
+ <CounterSection />
209
+ <ProfileFormSection />
210
+ </div>
211
+ </div>
212
+ </CounterProvider>
213
+ )
53
214
  }
54
215
  ]} defaultValue="@lvetechs/ui-lib" />
55
216
  </div>
@@ -1,5 +1,39 @@
1
+ import { useState } from 'react'
1
2
  import { useNavigate } from 'react-router-dom'
2
3
  import '@/styles/page-common.scss'
4
+ import { Button, Input, Dialog } from '@lvetechs/ui-lib'
5
+ import { useForm } from '@/hooks/useForm'
6
+
7
+ // 如果 @lvetechs/ui-lib 没有 Select,自己实现一个简单的 Select
8
+ interface SelectProps {
9
+ name?: string
10
+ value: string
11
+ onChange: (value: string) => void
12
+ options: Array<{ label: string; value: string }>
13
+ placeholder?: string
14
+ }
15
+
16
+ function Select({ name, value, onChange, options, placeholder }: SelectProps) {
17
+ return (
18
+ <select
19
+ name={name}
20
+ className="w-full rounded border border-slate-200 px-3 py-2 text-sm outline-none focus:border-indigo-500 focus:ring-1 focus:ring-indigo-500"
21
+ value={value}
22
+ onChange={(e) => onChange(e.target.value)}
23
+ >
24
+ {placeholder && (
25
+ <option value="" disabled>
26
+ {placeholder}
27
+ </option>
28
+ )}
29
+ {options.map((option) => (
30
+ <option key={option.value} value={option.value}>
31
+ {option.label}
32
+ </option>
33
+ ))}
34
+ </select>
35
+ )
36
+ }
3
37
 
4
38
  const users = [
5
39
  { id: 1, username: 'admin', nickname: '管理员', role: '超级管理员', status: '启用' },
@@ -7,15 +41,147 @@ const users = [
7
41
  { id: 3, username: 'viewer', nickname: '访客', role: '只读', status: '禁用' }
8
42
  ]
9
43
 
44
+ interface UserFormValues {
45
+ username: string
46
+ nickname: string
47
+ email: string
48
+ role: string
49
+ }
50
+
51
+ function validateUserForm(values: UserFormValues) {
52
+ const errors: Partial<Record<keyof UserFormValues, string>> = {}
53
+ if (!values.username.trim()) {
54
+ errors.username = '请输入用户名'
55
+ }
56
+ if (!values.nickname.trim()) {
57
+ errors.nickname = '请输入昵称'
58
+ }
59
+ if (!values.email.trim()) {
60
+ errors.email = '请输入邮箱'
61
+ } else if (!/^[\w-.]+@([\w-]+\.)+[\w-]{2,4}$/.test(values.email)) {
62
+ errors.email = '邮箱格式不正确'
63
+ }
64
+ if (!values.role.trim()) {
65
+ errors.role = '请选择角色'
66
+ }
67
+ return errors
68
+ }
69
+
10
70
  export default function SystemUser() {
11
71
  const navigate = useNavigate()
72
+ const [showForm, setShowForm] = useState(false)
73
+
74
+ const { values, errors, submitting, handleChange, handleSubmit, reset } = useForm<UserFormValues>({
75
+ initialValues: {
76
+ username: '',
77
+ nickname: '',
78
+ email: '',
79
+ role: ''
80
+ },
81
+ validate: validateUserForm,
82
+ onSubmit: async (formValues) => {
83
+ // eslint-disable-next-line no-console
84
+ console.log('新增用户', formValues)
85
+ await new Promise((resolve) => setTimeout(resolve, 800))
86
+ alert(`用户 ${formValues.username} 创建成功!`)
87
+ setShowForm(false)
88
+ reset()
89
+ }
90
+ })
91
+
92
+ const handleOpenForm = () => {
93
+ setShowForm(true)
94
+ reset()
95
+ }
96
+
97
+ const handleCloseForm = () => {
98
+ setShowForm(false)
99
+ reset()
100
+ }
12
101
 
13
102
  return (
14
103
  <div className="page-container">
15
104
  <div className="page-header">
16
105
  <h2>用户管理</h2>
17
- <button className="btn-primary">+ 新增用户</button>
106
+ <Button onClick={handleOpenForm}>+ 新增用户</Button>
18
107
  </div>
108
+
109
+ {/* 新增用户表单弹窗(使用 @lvetechs/ui-lib 的 Dialog 作为容器) */}
110
+ {showForm && (
111
+ <Dialog
112
+ visible={showForm}
113
+ maskClosable={false}
114
+ onClose={handleCloseForm}
115
+ onOk={handleSubmit}
116
+ okText={submitting ? '提交中...' : '提交'}
117
+ cancelText='取消'
118
+ onCancel={handleCloseForm}
119
+ >
120
+ <div className="space-y-4">
121
+ <label className="mb-1 block text-sm font-medium text-slate-700" htmlFor="username">
122
+ 用户名 <span className="text-rose-500">*</span>
123
+ </label>
124
+ <Input
125
+ id="username"
126
+ name="username"
127
+ placeholder="请输入用户名"
128
+ value={values.username}
129
+ onChange={handleChange}
130
+ />
131
+ {errors.username && (
132
+ <p className="mt-1 text-xs text-rose-500">{errors.username}</p>
133
+ )}
134
+ </div>
135
+ <div>
136
+ <label className="mb-1 block text-sm font-medium text-slate-700" htmlFor="nickname">
137
+ 昵称 <span className="text-rose-500">*</span>
138
+ </label>
139
+ <Input
140
+ id="nickname"
141
+ name="nickname"
142
+ placeholder="请输入昵称"
143
+ value={values.nickname}
144
+ onChange={handleChange}
145
+ />
146
+ {errors.nickname && (
147
+ <p className="mt-1 text-xs text-rose-500">{errors.nickname}</p>
148
+ )}
149
+ </div>
150
+ <div>
151
+ <label className="mb-1 block text-sm font-medium text-slate-700" htmlFor="email">
152
+ 邮箱 <span className="text-rose-500">*</span>
153
+ </label>
154
+ <Input
155
+ id="email"
156
+ name="email"
157
+ type="email"
158
+ placeholder="请输入邮箱"
159
+ value={values.email}
160
+ onChange={handleChange}
161
+ />
162
+ {errors.email && <p className="mt-1 text-xs text-rose-500">{errors.email}</p>}
163
+ </div>
164
+ <div>
165
+ <label className="mb-1 block text-sm font-medium text-slate-700" htmlFor="role">
166
+ 角色 <span className="text-rose-500">*</span>
167
+ </label>
168
+ <Select
169
+ name="role"
170
+ value={values.role}
171
+ onChange={(value) => {
172
+ handleChange({ target: { name: 'role', value } } as any)
173
+ }}
174
+ placeholder="请选择角色"
175
+ options={[
176
+ { label: '超级管理员', value: '超级管理员' },
177
+ { label: '编辑', value: '编辑' },
178
+ { label: '只读', value: '只读' }
179
+ ]}
180
+ />
181
+ {errors.role && <p className="mt-1 text-xs text-rose-500">{errors.role}</p>}
182
+ </div>
183
+ </Dialog>
184
+ )}
19
185
  <table className="data-table">
20
186
  <thead>
21
187
  <tr>
@@ -40,11 +206,11 @@ export default function SystemUser() {
40
206
  </span>
41
207
  </td>
42
208
  <td>
43
- <button className="btn-link" onClick={() => navigate(`/user/detail/${user.id}`)}>
209
+ <Button style={{ margin: '0 5px' }} type="button" onClick={() => navigate(`/user/detail/${user.id}`)}>
44
210
  详情
45
- </button>
46
- <button className="btn-link">编辑</button>
47
- <button className="btn-link danger">删除</button>
211
+ </Button>
212
+ <Button style={{ margin: '0 5px' }} type="button">编辑</Button>
213
+ <Button style={{ margin: '0 5px' }} type="button">删除</Button>
48
214
  </td>
49
215
  </tr>
50
216
  ))}
@@ -1,3 +1,3 @@
1
- # 基础环境变量
2
- VITE_APP_TITLE=My Vue App
1
+ # 基础环境变量
2
+ VITE_APP_TITLE=My Vue App
3
3
  VITE_APP_BASE_API=/api
@@ -1,3 +1,3 @@
1
- # 开发环境
2
- VITE_APP_TITLE=My Vue App (Dev)
1
+ # 开发环境
2
+ VITE_APP_TITLE=My Vue App (Dev)
3
3
  VITE_APP_BASE_API=/api
@@ -1,3 +1,3 @@
1
- # 生产环境
2
- VITE_APP_TITLE=My Vue App
1
+ # 生产环境
2
+ VITE_APP_TITLE=My Vue App
3
3
  VITE_APP_BASE_API=https://api.example.com