cmpt-huitu-cli 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.
- package/README.md +169 -0
- package/bin/huitu.js +30 -0
- package/package.json +38 -0
- package/src/create.js +127 -0
- package/src/versions.js +23 -0
- package/templates/default/README.md +127 -0
- package/templates/default/eslint.config.js +187 -0
- package/templates/default/index.html +38 -0
- package/templates/default/jsconfig.json +29 -0
- package/templates/default/package.json +56 -0
- package/templates/default/public/.DS_Store +0 -0
- package/templates/default/public/config/envCfg.js +68 -0
- package/templates/default/public/images/favicon.ico +0 -0
- package/templates/default/src/.DS_Store +0 -0
- package/templates/default/src/App.vue +49 -0
- package/templates/default/src/assets/.DS_Store +0 -0
- package/templates/default/src/assets/empty.png +0 -0
- package/templates/default/src/assets/images/.DS_Store +0 -0
- package/templates/default/src/assets/images/404/403_1.png +0 -0
- package/templates/default/src/assets/images/404/404.png +0 -0
- package/templates/default/src/assets/images/404/404_2.png +0 -0
- package/templates/default/src/assets/images/404/404_cloud.png +0 -0
- package/templates/default/src/assets/images/login/img.png +0 -0
- package/templates/default/src/assets/images/login/logo.png +0 -0
- package/templates/default/src/assets/images/login/person.png +0 -0
- package/templates/default/src/components/.DS_Store +0 -0
- package/templates/default/src/components/error/Error.vue +259 -0
- package/templates/default/src/components.d.ts +18 -0
- package/templates/default/src/imports.d.ts +90 -0
- package/templates/default/src/main.js +27 -0
- package/templates/default/src/routers/base.js +41 -0
- package/templates/default/src/routers/guard.js +43 -0
- package/templates/default/src/routers/index.js +36 -0
- package/templates/default/src/routers/modules/example.js +17 -0
- package/templates/default/src/services/.DS_Store +0 -0
- package/templates/default/src/services/login.js +32 -0
- package/templates/default/src/stores/README.md +317 -0
- package/templates/default/src/stores/index/global.js +49 -0
- package/templates/default/src/stores/index/template.js +31 -0
- package/templates/default/src/stores/index.js +14 -0
- package/templates/default/src/stores//344/275/277/347/224/250/347/244/272/344/276/213.vue +94 -0
- package/templates/default/src/styles/index.scss +23 -0
- package/templates/default/src/styles/theme/README.md +52 -0
- package/templates/default/src/styles/theme/variables.scss +62 -0
- package/templates/default/src/utils/RequestCache.js +198 -0
- package/templates/default/src/utils/auth.js +51 -0
- package/templates/default/src/utils/errorCode.js +16 -0
- package/templates/default/src/utils/index.js +519 -0
- package/templates/default/src/utils/requestAxios.js +148 -0
- package/templates/default/src/utils/theme.js +20 -0
- package/templates/default/src/views/About.vue +6 -0
- package/templates/default/src/views/Home.vue +111 -0
- package/templates/default/src/views/login/index.vue +285 -0
- package/templates/default/vite/plugins/autoComponents.js +18 -0
- package/templates/default/vite/plugins/autoImport.js +13 -0
- package/templates/default/vite/plugins/compression.js +24 -0
- package/templates/default/vite/plugins/externals.js +8 -0
- package/templates/default/vite/plugins/index.js +18 -0
- package/templates/default/vite/plugins/visualizer.js +11 -0
- package/templates/default/vite.config.js +185 -0
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
// 基础路由(常量路由)
|
|
2
|
+
export const constantRoutes = [
|
|
3
|
+
{
|
|
4
|
+
path: '/401',
|
|
5
|
+
name: '401',
|
|
6
|
+
component: () => import('@components/error/Error.vue'),
|
|
7
|
+
},
|
|
8
|
+
{
|
|
9
|
+
path: '/404',
|
|
10
|
+
name: '404',
|
|
11
|
+
component: () => import('@components/error/Error.vue'),
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
path: '/login',
|
|
15
|
+
name: 'login',
|
|
16
|
+
component: () => import('@views/login/index.vue'),
|
|
17
|
+
meta: {
|
|
18
|
+
title: '登录',
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
path: '/',
|
|
23
|
+
name: 'index',
|
|
24
|
+
component: () => import('@views/Home.vue'),
|
|
25
|
+
meta: {
|
|
26
|
+
title: '首页',
|
|
27
|
+
keepAlive: true,
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
path: '/about',
|
|
32
|
+
name: 'about',
|
|
33
|
+
component: () => import('@views/About.vue'),
|
|
34
|
+
},
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
// 404 兜底路由(应始终放在最后)
|
|
38
|
+
export const notFoundRoute = {
|
|
39
|
+
path: '/:pathMatch(.*)*',
|
|
40
|
+
redirect: '/404',
|
|
41
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import NProgress from 'nprogress'
|
|
2
|
+
import 'nprogress/nprogress.css'
|
|
3
|
+
import { getToken } from '@utils/auth'
|
|
4
|
+
|
|
5
|
+
// NProgress 配置
|
|
6
|
+
NProgress.configure({ showSpinner: false })
|
|
7
|
+
|
|
8
|
+
// 白名单路由
|
|
9
|
+
const whiteList = ['/login', '/404', '/401']
|
|
10
|
+
|
|
11
|
+
export function setupRouterGuard(router) {
|
|
12
|
+
router.beforeEach((to, _from, next) => {
|
|
13
|
+
// 开启进度条
|
|
14
|
+
NProgress.start()
|
|
15
|
+
|
|
16
|
+
// 设置页面标题
|
|
17
|
+
if (to.meta.title) {
|
|
18
|
+
document.title = `${to.meta.title} - ${import.meta.env.VITE_APP_TITLE || '系统模板'}`
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const token = getToken()
|
|
22
|
+
|
|
23
|
+
// 判断是否在白名单
|
|
24
|
+
if (whiteList.includes(to.path)) {
|
|
25
|
+
next()
|
|
26
|
+
return
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// 判断是否有 Token
|
|
30
|
+
// if (!token) {
|
|
31
|
+
// next(`/login?redirect=${to.path}`)
|
|
32
|
+
// NProgress.done()
|
|
33
|
+
// return
|
|
34
|
+
// }
|
|
35
|
+
|
|
36
|
+
next()
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
router.afterEach(() => {
|
|
40
|
+
// 关闭进度条
|
|
41
|
+
NProgress.done()
|
|
42
|
+
})
|
|
43
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { createRouter, createWebHistory } from 'vue-router'
|
|
2
|
+
import { setupRouterGuard } from './guard'
|
|
3
|
+
import { constantRoutes, notFoundRoute } from './base'
|
|
4
|
+
|
|
5
|
+
// 自动导入 ./modules 下的所有路由模块
|
|
6
|
+
const modules = import.meta.glob('./modules/*.js', { eager: true })
|
|
7
|
+
|
|
8
|
+
// 合并所有模块路由
|
|
9
|
+
const moduleRoutes = []
|
|
10
|
+
Object.keys(modules).forEach((key) => {
|
|
11
|
+
const mod = modules[key].default || {}
|
|
12
|
+
const modList = Array.isArray(mod) ? [...mod] : [mod]
|
|
13
|
+
moduleRoutes.push(...modList)
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
// 创建路由实例
|
|
17
|
+
const router = createRouter({
|
|
18
|
+
history: createWebHistory(import.meta.env.BASE_URL),
|
|
19
|
+
routes: [
|
|
20
|
+
...constantRoutes,
|
|
21
|
+
...moduleRoutes,
|
|
22
|
+
notFoundRoute, // 确保 404 路由在最后
|
|
23
|
+
],
|
|
24
|
+
scrollBehavior(_to, _from, savedPosition) {
|
|
25
|
+
if (savedPosition) {
|
|
26
|
+
return savedPosition
|
|
27
|
+
} else {
|
|
28
|
+
return { top: 0 }
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
// 设置路由守卫
|
|
34
|
+
setupRouterGuard(router)
|
|
35
|
+
|
|
36
|
+
export default router
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @FileDescription: 路由模块演示
|
|
3
|
+
* @Author:
|
|
4
|
+
* @Date: 2024
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
// 导出该模块的路由配置
|
|
8
|
+
export default [
|
|
9
|
+
// {
|
|
10
|
+
// path: '/example',
|
|
11
|
+
// name: 'Example',
|
|
12
|
+
// component: () => import('@views/Example.vue'),
|
|
13
|
+
// meta: {
|
|
14
|
+
// title: '示例页面'
|
|
15
|
+
// }
|
|
16
|
+
// }
|
|
17
|
+
]
|
|
Binary file
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { cache } from '@utils/RequestCache.js'
|
|
2
|
+
const _BaseUrl = 'login/'
|
|
3
|
+
|
|
4
|
+
// 通用登录接口
|
|
5
|
+
export async function loginApi(data) {
|
|
6
|
+
const response = await cache.request({
|
|
7
|
+
url: _BaseUrl + 'auth',
|
|
8
|
+
method: 'post',
|
|
9
|
+
data: data,
|
|
10
|
+
})
|
|
11
|
+
return response.data
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// 获取用户信息
|
|
15
|
+
export async function getUserInfoApi() {
|
|
16
|
+
const response = await cache.request({
|
|
17
|
+
url: _BaseUrl + 'userInfo',
|
|
18
|
+
method: 'get',
|
|
19
|
+
})
|
|
20
|
+
return response.data
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// 刷新token
|
|
24
|
+
export async function refreshAccessTokenApi(params) {
|
|
25
|
+
const response = await cache.request({
|
|
26
|
+
url: _BaseUrl + 'refreshAccessToken',
|
|
27
|
+
method: 'get',
|
|
28
|
+
params,
|
|
29
|
+
notUseCache: true,
|
|
30
|
+
})
|
|
31
|
+
return response.data
|
|
32
|
+
}
|
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
# Stores 使用说明
|
|
2
|
+
|
|
3
|
+
## 📁 目录结构
|
|
4
|
+
|
|
5
|
+
```
|
|
6
|
+
stores/
|
|
7
|
+
├── index.js # Store 入口文件(统一导出)
|
|
8
|
+
├── system.js # 系统管理 Store 入口(示例)
|
|
9
|
+
└── index/
|
|
10
|
+
├── global.js # 全局状态管理(主题、loading 等)
|
|
11
|
+
└── template.js # Store 使用示例
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## 🎯 设计理念
|
|
15
|
+
|
|
16
|
+
### 1. 延迟实例化模式
|
|
17
|
+
|
|
18
|
+
为了避免 Pinia 实例化时机问题,采用**延迟实例化**模式:
|
|
19
|
+
|
|
20
|
+
- **不直接导出 Store 实例**
|
|
21
|
+
- **导出创建函数**,在使用时再调用
|
|
22
|
+
|
|
23
|
+
```javascript
|
|
24
|
+
// ❌ 错误方式(可能导致 "getActivePinia()" was called but there was no active Pinia" 错误)
|
|
25
|
+
export const globalStore = defineStore('global', { ... })
|
|
26
|
+
export default globalStore() // 提前实例化
|
|
27
|
+
|
|
28
|
+
// ✅ 正确方式(延迟实例化)
|
|
29
|
+
export const globalStore = defineStore('global', { ... })
|
|
30
|
+
export { globalStore as createGlobalStore } // 导出创建函数
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### 2. 统一导出入口
|
|
34
|
+
|
|
35
|
+
所有 Store 通过 `stores/index.js` 统一导出,便于管理和使用。
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## 📖 使用方式
|
|
40
|
+
|
|
41
|
+
### 方式一:在组件中使用(推荐)
|
|
42
|
+
|
|
43
|
+
#### 1. 导入创建函数
|
|
44
|
+
|
|
45
|
+
```vue
|
|
46
|
+
<script setup>
|
|
47
|
+
import { createGlobalStore, createExampleStore } from '@stores/index'
|
|
48
|
+
</script>
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
#### 2. 在组件中实例化并使用
|
|
52
|
+
|
|
53
|
+
```vue
|
|
54
|
+
<script setup>
|
|
55
|
+
import { storeToRefs } from 'pinia'
|
|
56
|
+
import { createGlobalStore } from '@stores/index'
|
|
57
|
+
|
|
58
|
+
// 实例化 Store
|
|
59
|
+
const globalStore = createGlobalStore()
|
|
60
|
+
|
|
61
|
+
// 使用 storeToRefs 获取响应式状态
|
|
62
|
+
const { themeVal, isLoading } = storeToRefs(globalStore)
|
|
63
|
+
|
|
64
|
+
// 直接调用 actions
|
|
65
|
+
const { changeGlobalTheme, setIsLoadingStore } = globalStore
|
|
66
|
+
|
|
67
|
+
// 使用
|
|
68
|
+
const toggleTheme = () => {
|
|
69
|
+
changeGlobalTheme(themeVal.value === 'light' ? 'dark' : 'light')
|
|
70
|
+
}
|
|
71
|
+
</script>
|
|
72
|
+
|
|
73
|
+
<template>
|
|
74
|
+
<div>
|
|
75
|
+
<p>当前主题: {{ themeVal }}</p>
|
|
76
|
+
<p>Loading 状态: {{ isLoading }}</p>
|
|
77
|
+
<button @click="toggleTheme">切换主题</button>
|
|
78
|
+
</div>
|
|
79
|
+
</template>
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### 方式二:在工具函数中使用
|
|
83
|
+
|
|
84
|
+
#### 示例:在 `request.js` 中使用
|
|
85
|
+
|
|
86
|
+
```javascript
|
|
87
|
+
import { globalStore } from '@stores/index/global'
|
|
88
|
+
import { storeToRefs } from 'pinia'
|
|
89
|
+
|
|
90
|
+
// 在函数中实例化
|
|
91
|
+
const setGlobalLoading = (status) => {
|
|
92
|
+
const storeInstance = globalStore()
|
|
93
|
+
const { screenFlagStore, isLoading } = storeToRefs(storeInstance)
|
|
94
|
+
const { setIsLoadingStore } = storeInstance
|
|
95
|
+
|
|
96
|
+
// 使用状态和方法
|
|
97
|
+
if (screenFlagStore.value) {
|
|
98
|
+
setIsLoadingStore(false)
|
|
99
|
+
return
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
setIsLoadingStore(status)
|
|
103
|
+
}
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
## 📦 现有 Store 说明
|
|
109
|
+
|
|
110
|
+
### 1. `global.js` - 全局状态管理
|
|
111
|
+
|
|
112
|
+
**用途**:管理全局通用状态
|
|
113
|
+
|
|
114
|
+
**状态**:
|
|
115
|
+
|
|
116
|
+
- `themeVal` - 主题值('light' | 'dark')
|
|
117
|
+
- `isLoading` - 全局 loading 状态
|
|
118
|
+
- `screenFlagStore` - 投屏状态
|
|
119
|
+
|
|
120
|
+
**方法**:
|
|
121
|
+
|
|
122
|
+
- `changeGlobalTheme(theme)` - 更改主题
|
|
123
|
+
- `setIsLoadingStore(status)` - 设置 loading 状态
|
|
124
|
+
- `setScreenFlag(flag)` - 设置投屏状态
|
|
125
|
+
|
|
126
|
+
**使用示例**:
|
|
127
|
+
|
|
128
|
+
```vue
|
|
129
|
+
<script setup>
|
|
130
|
+
import { storeToRefs } from 'pinia'
|
|
131
|
+
import { createGlobalStore } from '@stores/index'
|
|
132
|
+
|
|
133
|
+
const globalStore = createGlobalStore()
|
|
134
|
+
const { themeVal, isLoading } = storeToRefs(globalStore)
|
|
135
|
+
const { changeGlobalTheme } = globalStore
|
|
136
|
+
|
|
137
|
+
// 切换主题
|
|
138
|
+
const toggleTheme = () => {
|
|
139
|
+
changeGlobalTheme(themeVal.value === 'light' ? 'dark' : 'light')
|
|
140
|
+
}
|
|
141
|
+
</script>
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
**实际使用位置**:
|
|
145
|
+
|
|
146
|
+
- `src/utils/request.js` - 用于管理全局 loading 状态
|
|
147
|
+
|
|
148
|
+
### 2. `template.js` - Store 示例
|
|
149
|
+
|
|
150
|
+
**用途**:展示如何创建和使用 Store
|
|
151
|
+
|
|
152
|
+
**状态**:
|
|
153
|
+
|
|
154
|
+
- `count` - 计数器
|
|
155
|
+
- `list` - 列表数据
|
|
156
|
+
|
|
157
|
+
**方法**:
|
|
158
|
+
|
|
159
|
+
- `increment(value)` - 增加计数
|
|
160
|
+
- `fetchList()` - 获取列表(示例)
|
|
161
|
+
|
|
162
|
+
**使用示例**:
|
|
163
|
+
|
|
164
|
+
```vue
|
|
165
|
+
<script setup>
|
|
166
|
+
import { storeToRefs } from 'pinia'
|
|
167
|
+
import { createExampleStore } from '@stores/index'
|
|
168
|
+
|
|
169
|
+
const exampleStore = createExampleStore()
|
|
170
|
+
const { count, list, doubleCount } = storeToRefs(exampleStore)
|
|
171
|
+
const { increment, fetchList } = exampleStore
|
|
172
|
+
|
|
173
|
+
// 使用
|
|
174
|
+
increment(5)
|
|
175
|
+
await fetchList()
|
|
176
|
+
</script>
|
|
177
|
+
|
|
178
|
+
<template>
|
|
179
|
+
<div>
|
|
180
|
+
<p>计数: {{ count }}</p>
|
|
181
|
+
<p>双倍计数: {{ doubleCount }}</p>
|
|
182
|
+
<button @click="increment(1)">+1</button>
|
|
183
|
+
</div>
|
|
184
|
+
</template>
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
---
|
|
188
|
+
|
|
189
|
+
## 🆕 创建新的 Store
|
|
190
|
+
|
|
191
|
+
### 步骤 1:创建 Store 文件
|
|
192
|
+
|
|
193
|
+
在 `stores/index/` 目录下创建新文件,例如 `user.js`:
|
|
194
|
+
|
|
195
|
+
```javascript
|
|
196
|
+
// stores/index/user.js
|
|
197
|
+
import { defineStore } from 'pinia'
|
|
198
|
+
|
|
199
|
+
export const useUserStore = defineStore('user', {
|
|
200
|
+
state: () => ({
|
|
201
|
+
userInfo: null,
|
|
202
|
+
token: '',
|
|
203
|
+
}),
|
|
204
|
+
|
|
205
|
+
getters: {
|
|
206
|
+
isLoggedIn: (state) => !!state.token,
|
|
207
|
+
},
|
|
208
|
+
|
|
209
|
+
actions: {
|
|
210
|
+
setUserInfo(userInfo) {
|
|
211
|
+
this.userInfo = userInfo
|
|
212
|
+
},
|
|
213
|
+
|
|
214
|
+
setToken(token) {
|
|
215
|
+
this.token = token
|
|
216
|
+
},
|
|
217
|
+
|
|
218
|
+
logout() {
|
|
219
|
+
this.userInfo = null
|
|
220
|
+
this.token = ''
|
|
221
|
+
},
|
|
222
|
+
},
|
|
223
|
+
})
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
### 步骤 2:在入口文件中导出
|
|
227
|
+
|
|
228
|
+
在 `stores/index.js` 中添加导出:
|
|
229
|
+
|
|
230
|
+
```javascript
|
|
231
|
+
import { useUserStore as createUserStore } from '@stores/index/user'
|
|
232
|
+
|
|
233
|
+
export { createGlobalStore, createExampleStore, createUserStore }
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
### 步骤 3:在组件中使用
|
|
237
|
+
|
|
238
|
+
```vue
|
|
239
|
+
<script setup>
|
|
240
|
+
import { storeToRefs } from 'pinia'
|
|
241
|
+
import { createUserStore } from '@stores/index'
|
|
242
|
+
|
|
243
|
+
const userStore = createUserStore()
|
|
244
|
+
const { userInfo, isLoggedIn } = storeToRefs(userStore)
|
|
245
|
+
const { setUserInfo, logout } = userStore
|
|
246
|
+
</script>
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
---
|
|
250
|
+
|
|
251
|
+
## 🔧 系统管理 Store(可选)
|
|
252
|
+
|
|
253
|
+
如果项目需要系统管理相关的 Store,可以在 `stores/system/` 目录下创建,并在 `stores/system.js` 中导出。
|
|
254
|
+
|
|
255
|
+
**示例结构**:
|
|
256
|
+
|
|
257
|
+
```
|
|
258
|
+
stores/
|
|
259
|
+
├── system.js # 系统管理 Store 入口
|
|
260
|
+
└── system/
|
|
261
|
+
├── user.js # 用户管理 Store
|
|
262
|
+
├── permission.js # 权限管理 Store
|
|
263
|
+
└── settings.js # 系统设置 Store
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
**在 `system.js` 中导出**:
|
|
267
|
+
|
|
268
|
+
```javascript
|
|
269
|
+
import { useUserStore as createUserStore } from '@stores/system/user'
|
|
270
|
+
import { usePermissionStore as createPermissionStore } from '@stores/system/permission'
|
|
271
|
+
|
|
272
|
+
export { createUserStore, createPermissionStore }
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
---
|
|
276
|
+
|
|
277
|
+
## ⚠️ 注意事项
|
|
278
|
+
|
|
279
|
+
### 1. 避免提前实例化
|
|
280
|
+
|
|
281
|
+
```javascript
|
|
282
|
+
// ❌ 错误:在模块顶层实例化
|
|
283
|
+
const store = globalStore() // 可能导致 Pinia 未初始化错误
|
|
284
|
+
|
|
285
|
+
// ✅ 正确:在函数或组件中实例化
|
|
286
|
+
const useStore = () => {
|
|
287
|
+
return globalStore()
|
|
288
|
+
}
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
### 2. 使用 storeToRefs 保持响应式
|
|
292
|
+
|
|
293
|
+
```javascript
|
|
294
|
+
// ❌ 错误:直接解构会失去响应式
|
|
295
|
+
const { themeVal } = globalStore()
|
|
296
|
+
|
|
297
|
+
// ✅ 正确:使用 storeToRefs
|
|
298
|
+
const { themeVal } = storeToRefs(globalStore())
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
### 3. Actions 可以直接解构
|
|
302
|
+
|
|
303
|
+
```javascript
|
|
304
|
+
// ✅ Actions 可以直接解构,不需要 storeToRefs
|
|
305
|
+
const { changeGlobalTheme, setIsLoadingStore } = globalStore()
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
---
|
|
309
|
+
|
|
310
|
+
## 📚 相关文档
|
|
311
|
+
|
|
312
|
+
- [Pinia 官方文档](https://pinia.vuejs.org/)
|
|
313
|
+
- [Vue 3 Composition API](https://vuejs.org/guide/extras/composition-api-faq.html)
|
|
314
|
+
|
|
315
|
+
---
|
|
316
|
+
|
|
317
|
+
**最后更新**:2026-01-04
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @FileDescription: 全局状态管理
|
|
3
|
+
* @Author: template
|
|
4
|
+
* @Date: 2026-01-04
|
|
5
|
+
* @Description: 管理全局通用状态,如主题、loading 等
|
|
6
|
+
*/
|
|
7
|
+
import { defineStore } from 'pinia'
|
|
8
|
+
import { setTheme } from '@/utils/theme'
|
|
9
|
+
|
|
10
|
+
export const globalStore = defineStore('global', {
|
|
11
|
+
state: () => ({
|
|
12
|
+
// 主题值:'light' | 'dark'
|
|
13
|
+
themeVal: localStorage.getItem('data-theme') || 'light',
|
|
14
|
+
// 全局 loading 状态
|
|
15
|
+
isLoading: false,
|
|
16
|
+
// 投屏状态(用于控制 loading 显示)
|
|
17
|
+
screenFlagStore: false,
|
|
18
|
+
}),
|
|
19
|
+
|
|
20
|
+
actions: {
|
|
21
|
+
/**
|
|
22
|
+
* 更改全局主题
|
|
23
|
+
* @param {string} theme - 主题值 'light' | 'dark'
|
|
24
|
+
* @returns {void}
|
|
25
|
+
*/
|
|
26
|
+
changeGlobalTheme(theme) {
|
|
27
|
+
this.themeVal = theme
|
|
28
|
+
setTheme(theme)
|
|
29
|
+
},
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* 设置全局 loading 状态
|
|
33
|
+
* @param {boolean} status - loading 状态
|
|
34
|
+
* @returns {void}
|
|
35
|
+
*/
|
|
36
|
+
setIsLoadingStore(status) {
|
|
37
|
+
this.isLoading = status
|
|
38
|
+
},
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* 设置投屏状态
|
|
42
|
+
* @param {boolean} flag - 投屏状态
|
|
43
|
+
* @returns {void}
|
|
44
|
+
*/
|
|
45
|
+
setScreenFlag(flag) {
|
|
46
|
+
this.screenFlagStore = flag
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
})
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @FileDescription: Store 示例文件
|
|
3
|
+
* @Author: template
|
|
4
|
+
* @Date: 2026-01-04
|
|
5
|
+
* @Description: 此文件为 Store 使用示例,可根据实际需求修改或删除
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { defineStore } from 'pinia'
|
|
9
|
+
|
|
10
|
+
export const useExampleStore = defineStore('example', {
|
|
11
|
+
state: () => ({
|
|
12
|
+
count: 0,
|
|
13
|
+
list: [],
|
|
14
|
+
}),
|
|
15
|
+
|
|
16
|
+
getters: {
|
|
17
|
+
doubleCount: (state) => state.count * 2,
|
|
18
|
+
},
|
|
19
|
+
|
|
20
|
+
actions: {
|
|
21
|
+
increment(value = 1) {
|
|
22
|
+
this.count += value
|
|
23
|
+
},
|
|
24
|
+
|
|
25
|
+
async fetchList() {
|
|
26
|
+
// 示例:异步获取数据
|
|
27
|
+
// const data = await api.getList()
|
|
28
|
+
// this.list = data
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
})
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @FileDescription: Store 入口文件
|
|
3
|
+
* @Author: template
|
|
4
|
+
* @Date: 2026-01-04
|
|
5
|
+
* @Description: 统一导出所有 Store
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { globalStore as createGlobalStore } from '@stores/index/global'
|
|
9
|
+
import { useExampleStore as createExampleStore } from '@stores/index/template'
|
|
10
|
+
|
|
11
|
+
// 只导出创建函数,不提前实例化
|
|
12
|
+
// 核心问题在于 store 实例化的时机可能过早,
|
|
13
|
+
// 容易触发 "getActivePinia()" was called but there was no active Pinia" 错误。
|
|
14
|
+
export { createGlobalStore, createExampleStore }
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
@FileDescription: Store 使用示例
|
|
3
|
+
@Author: template
|
|
4
|
+
@Date: 2026-01-04
|
|
5
|
+
@Description: 展示如何在组件中使用 Store
|
|
6
|
+
-->
|
|
7
|
+
<template>
|
|
8
|
+
<div class="store-demo">
|
|
9
|
+
<h2>Store 使用示例</h2>
|
|
10
|
+
|
|
11
|
+
<!-- 全局 Store 示例 -->
|
|
12
|
+
<div class="demo-section">
|
|
13
|
+
<h3>1. 全局 Store (globalStore)</h3>
|
|
14
|
+
<p>当前主题: <strong>{{ themeVal }}</strong></p>
|
|
15
|
+
<p>Loading 状态: <strong>{{ isLoading ? '加载中...' : '空闲' }}</strong></p>
|
|
16
|
+
<p>投屏状态: <strong>{{ screenFlagStore ? '投屏中' : '正常' }}</strong></p>
|
|
17
|
+
|
|
18
|
+
<div class="actions">
|
|
19
|
+
<el-button @click="toggleTheme">切换主题</el-button>
|
|
20
|
+
<el-button @click="toggleLoading">切换 Loading</el-button>
|
|
21
|
+
<el-button @click="toggleScreen">切换投屏</el-button>
|
|
22
|
+
</div>
|
|
23
|
+
</div>
|
|
24
|
+
|
|
25
|
+
<!-- 示例 Store 示例 -->
|
|
26
|
+
<div class="demo-section">
|
|
27
|
+
<h3>2. 示例 Store (exampleStore)</h3>
|
|
28
|
+
<p>计数: <strong>{{ count }}</strong></p>
|
|
29
|
+
<p>双倍计数: <strong>{{ doubleCount }}</strong></p>
|
|
30
|
+
<p>列表长度: <strong>{{ list.length }}</strong></p>
|
|
31
|
+
|
|
32
|
+
<div class="actions">
|
|
33
|
+
<el-button @click="increment">+1</el-button>
|
|
34
|
+
<el-button @click="increment(5)">+5</el-button>
|
|
35
|
+
<el-button @click="fetchList">获取列表</el-button>
|
|
36
|
+
</div>
|
|
37
|
+
</div>
|
|
38
|
+
</div>
|
|
39
|
+
</template>
|
|
40
|
+
|
|
41
|
+
<script setup>
|
|
42
|
+
import { storeToRefs } from 'pinia'
|
|
43
|
+
import { createGlobalStore, createExampleStore } from '@stores/index'
|
|
44
|
+
|
|
45
|
+
// ========== 全局 Store ==========
|
|
46
|
+
const globalStore = createGlobalStore()
|
|
47
|
+
const { themeVal, isLoading, screenFlagStore } = storeToRefs(globalStore)
|
|
48
|
+
const { changeGlobalTheme, setIsLoadingStore, setScreenFlag } = globalStore
|
|
49
|
+
|
|
50
|
+
const toggleTheme = () => {
|
|
51
|
+
changeGlobalTheme(themeVal.value === 'light' ? 'dark' : 'light')
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const toggleLoading = () => {
|
|
55
|
+
setIsLoadingStore(!isLoading.value)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const toggleScreen = () => {
|
|
59
|
+
setScreenFlag(!screenFlagStore.value)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// ========== 示例 Store ==========
|
|
63
|
+
const exampleStore = createExampleStore()
|
|
64
|
+
const { count, list, doubleCount } = storeToRefs(exampleStore)
|
|
65
|
+
const { increment, fetchList } = exampleStore
|
|
66
|
+
</script>
|
|
67
|
+
|
|
68
|
+
<style scoped>
|
|
69
|
+
.store-demo {
|
|
70
|
+
padding: 20px;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
.demo-section {
|
|
74
|
+
margin-bottom: 30px;
|
|
75
|
+
padding: 20px;
|
|
76
|
+
border: 1px solid #e4e7ed;
|
|
77
|
+
border-radius: 4px;
|
|
78
|
+
background: var(--table-body-bg-color, #f5f7fa);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
.demo-section h3 {
|
|
82
|
+
margin-top: 0;
|
|
83
|
+
color: var(--main-color, #067dff);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
.actions {
|
|
87
|
+
margin-top: 15px;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
.actions .el-button {
|
|
91
|
+
margin-right: 10px;
|
|
92
|
+
}
|
|
93
|
+
</style>
|
|
94
|
+
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @FileDescription: 项目样式入口文件
|
|
3
|
+
* @Author: template
|
|
4
|
+
* @Date: 2026-01-04
|
|
5
|
+
* @Description:
|
|
6
|
+
* 1. 公共样式已通过 vite.config.js 的 additionalData 全局引入:
|
|
7
|
+
* - @huitu/ui/src/styles/variables.scss (公共变量)
|
|
8
|
+
* - @huitu/ui/src/styles/mixin.scss (公共 mixin)
|
|
9
|
+
* 2. 必须显式引入以下产生 CSS 输出的全局样式文件(不能放在 additionalData 中):
|
|
10
|
+
* - src/styles/theme/variables.scss (项目主题变量定义)
|
|
11
|
+
* - @huitu/ui/src/styles/reset.scss (Element Plus 样式重置)
|
|
12
|
+
* 3. 项目自定义样式请放在 theme/ 目录下,并在此处引入
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
// 引入 CSS 变量定义(主题配置)
|
|
16
|
+
@import "./theme/variables.scss";
|
|
17
|
+
|
|
18
|
+
// 引入 Element Plus 重置样式(依赖上面的变量)
|
|
19
|
+
// @import '@huitu/ui/src/styles/reset.scss';
|
|
20
|
+
|
|
21
|
+
// 项目自定义样式
|
|
22
|
+
// 如需添加其他自定义样式,请在 theme/ 目录下创建文件并在此处引入
|
|
23
|
+
// @import './theme/custom.scss';
|