softleader-nuxt-core 1.2.0 → 2.2.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.
@@ -28,8 +28,11 @@ type FeaturePath =
28
28
  * @returns 功能是否啟用
29
29
  */
30
30
  function getFeatureValue(config: FeatureConfig): boolean {
31
+ // 安全檢查:確保是在 Node 環境或有 process polyfill
32
+ const hasProcess = typeof process !== 'undefined' && process.env
33
+
31
34
  // 優先使用環境變數
32
- if (config.envKey) {
35
+ if (config.envKey && hasProcess) {
33
36
  const envValue = process.env[config.envKey]
34
37
  if (envValue !== undefined) {
35
38
  return envValue === 'true' || envValue === '1'
@@ -38,7 +41,7 @@ function getFeatureValue(config: FeatureConfig): boolean {
38
41
 
39
42
  // 檢查環境限制
40
43
  if (config.environments && config.environments.length > 0) {
41
- const currentEnv = process.env.NODE_ENV || 'development'
44
+ const currentEnv = (hasProcess ? process.env.NODE_ENV : 'development') || 'development'
42
45
  const isAllEnvironments = config.environments.includes('all')
43
46
  const isCurrentEnvironment = config.environments.includes(
44
47
  currentEnv as 'development' | 'staging' | 'production'
@@ -1,18 +1,18 @@
1
- /**
2
- * 核心邏輯模組入口 (Centralized Logic Modules Entry)
3
- * 提供專案規範中要求的核心 Composable。
4
- */
5
- export function useModules() {
6
- return {
7
- /** 分頁管理 */
8
- usePagination,
9
- /** API 請求 */
10
- useApi,
11
- /** 檔案下載 */
12
- useFileDownload,
13
- /** 檔案上傳 */
14
- useFileUpload,
15
- /** 資料驗證 */
16
- useValidation
17
- }
18
- }
1
+ /**
2
+ * 核心邏輯模組入口 (Centralized Logic Modules Entry)
3
+ * 提供專案規範中要求的核心 Composable。
4
+ */
5
+ export function useModules() {
6
+ return {
7
+ /** 分頁管理 */
8
+ usePagination,
9
+ /** API 請求 */
10
+ useApi,
11
+ /** 檔案下載 */
12
+ useFileDownload,
13
+ /** 檔案上傳 */
14
+ useFileUpload,
15
+ /** 資料驗證 */
16
+ useValidation
17
+ }
18
+ }
@@ -1,7 +1,5 @@
1
1
  import { productConfig } from '../../scripts/product-loader'
2
2
 
3
- import { productConfig } from '../../scripts/product-loader'
4
-
5
3
  /**
6
4
  * 全域主題設計變數 (Design Tokens)
7
5
  * 供 Vuetify、Tailwind 或其他 UI 框架共用的基礎變數
@@ -1,43 +1,43 @@
1
1
  <template>
2
2
  <div class="layout-default">
3
3
  <!-- Header: 頂部導覽列 -->
4
- <header v-if="layout.header.visible" class="header" :style="{ height: `${layout.header.height}px` }">
4
+ <header v-if="layout?.header?.visible" class="header" :style="{ height: `${layout?.header?.height || 64}px` }">
5
5
  <div class="container d-flex align-center justify-space-between h-100">
6
6
  <div class="branding d-flex align-center gap-2">
7
- <IIcon v-if="layout.branding.logo.icon && !layout.branding.logo.image" :name="layout.branding.logo.icon" size="28" color="primary" />
8
- <img v-if="layout.branding.logo.image" :src="layout.branding.logo.image" class="logo-img" alt="logo" />
7
+ <IIcon v-if="layout?.branding?.logo?.icon && !layout?.branding?.logo?.image" :name="layout.branding.logo.icon" size="28" color="primary" />
8
+ <img v-if="layout?.branding?.logo?.image" :src="layout.branding.logo.image" class="logo-img" alt="logo" />
9
9
  <div class="branding-text">
10
- <h1 class="logo-title">{{ layout.branding.title }}</h1>
11
- <p v-if="layout.branding.subtitle" class="logo-subtitle">{{ layout.branding.subtitle }}</p>
10
+ <h1 class="logo-title">{{ layout?.branding?.title }}</h1>
11
+ <p v-if="layout?.branding?.subtitle" class="logo-subtitle">{{ layout.branding.subtitle }}</p>
12
12
  </div>
13
13
  </div>
14
14
 
15
15
  <div class="actions d-flex align-center gap-4">
16
16
  <!-- 搜尋框 -->
17
- <div v-if="layout.header.search" class="search-bar">
18
- <input type="text" :placeholder="layout.header.searchPlaceholder" />
17
+ <div v-if="layout?.header?.search" class="search-bar">
18
+ <input type="text" :placeholder="layout?.header?.searchPlaceholder" />
19
19
  </div>
20
20
 
21
21
  <slot name="header-actions" />
22
22
 
23
23
  <!-- 使用者選單按鈕 (示意) -->
24
- <div v-if="layout.header.userMenu.visible" class="user-action">
24
+ <div v-if="layout?.header?.userMenu?.visible" class="user-action">
25
25
  <IIcon name="mdi-account-circle" size="32" class="cursor-pointer" />
26
26
  </div>
27
27
  </div>
28
28
  </div>
29
29
  </header>
30
30
 
31
- <main class="main" :class="{ 'has-header': layout.header.visible }">
31
+ <main class="main" :class="{ 'has-header': layout?.header?.visible }">
32
32
  <div class="container py-10">
33
33
  <slot />
34
34
  </div>
35
35
  </main>
36
36
 
37
37
  <!-- Footer: 頁尾 -->
38
- <footer v-if="layout.footer.visible" class="footer">
38
+ <footer v-if="layout?.footer?.visible" class="footer">
39
39
  <div class="container">
40
- <div class="footer-content" v-html="layout.footer.content"></div>
40
+ <div class="footer-content" v-html="layout?.footer?.content"></div>
41
41
  </div>
42
42
  </footer>
43
43
  </div>
@@ -57,24 +57,25 @@ const layout = computed(() => core.layout)
57
57
  useHead({
58
58
  style: [
59
59
  {
60
- innerHTML: `
60
+ innerHTML: computed(() => `
61
61
  :root {
62
62
  /* 基礎語意色彩 */
63
- --color-primary: ${themeConfig.colors.primary};
64
- --color-success: ${themeConfig.colors.success};
65
- --color-warning: ${themeConfig.colors.warning};
66
- --color-error: ${themeConfig.colors.error};
67
- --color-info: ${themeConfig.colors.info};
63
+ --color-primary: ${layout.value?.theme?.primaryColor || core.theme?.primaryColor || themeConfig.colors.primary};
64
+ --color-primary-alpha: ${layout.value?.theme?.primaryColor || core.theme?.primaryColor || themeConfig.colors.primary}40;
65
+ --color-success: ${core.theme?.successColor || themeConfig.colors.success};
66
+ --color-warning: ${core.theme?.warningColor || themeConfig.colors.warning};
67
+ --color-error: ${core.theme?.errorColor || themeConfig.colors.error};
68
+ --color-info: ${core.theme?.infoColor || themeConfig.colors.info};
68
69
 
69
70
  /* 覆寫 main.css 中的特定變數以達成 100% 連動 */
70
- --color-primary-600: ${themeConfig.colors.primary};
71
- --color-primary-500: ${themeConfig.colors.primary}e6; /* 加入透明度模擬 */
71
+ --color-primary-600: ${layout.value?.theme?.primaryColor || core.theme?.primaryColor || themeConfig.colors.primary};
72
+ --color-primary-500: ${layout.value?.theme?.primaryColor || core.theme?.primaryColor || themeConfig.colors.primary}e6; /* 加入透明度模擬 */
72
73
 
73
74
  /* 基礎圓角 */
74
- --radius-md: ${themeConfig.shape.borderRadius}px;
75
- --radius-lg: ${themeConfig.shape.borderRadius + 4}px;
75
+ --radius-md: ${core.theme?.borderRadius || themeConfig.shape.borderRadius}px;
76
+ --radius-lg: ${(core.theme?.borderRadius || themeConfig.shape.borderRadius) + 4}px;
76
77
  }
77
- `
78
+ `)
78
79
  }
79
80
  ]
80
81
  })
package/nuxt.config.ts CHANGED
@@ -23,6 +23,22 @@ const { resolve } = createResolver(import.meta.url)
23
23
  export default defineNuxtConfig({
24
24
  ssr: false,
25
25
 
26
+ // Server-side Log
27
+ hooks: {
28
+ ready: () => {
29
+ console.log('🚀 Nuxt Core started with product:', productConfig.meta?.title, 'Color:', productConfig.theme?.primaryColor)
30
+ }
31
+ },
32
+
33
+ /** 支援配置驅動:監控 JSON 變動並自動重啟伺服器 (即改即變) */
34
+ watch: [
35
+ 'configs/*.json',
36
+ 'configs/**/*.json',
37
+ // 使用絕對路徑確保在不同 CWD 下都能監控到目前專案的 configs
38
+ `${process.cwd()}/configs/*.json`,
39
+ `${process.cwd()}/configs/**/*.json`
40
+ ],
41
+
26
42
  compatibilityDate: '2024-04-03',
27
43
 
28
44
  /** 編譯與打包設定 */
@@ -61,7 +77,8 @@ export default defineNuxtConfig({
61
77
  app: {
62
78
  ...projectRuntimeConfig.public.app,
63
79
  uaIdentifier:
64
- projectRuntimeConfig.public.app.uaIdentifier || productConfig.branding?.uaIdentifier
80
+ projectRuntimeConfig.public.app.uaIdentifier || productConfig.branding?.uaIdentifier,
81
+ productConfig
65
82
  },
66
83
  // 將 JSON 裡的網路設定同步到 runtimeConfig (環境變數優先)
67
84
  api: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "softleader-nuxt-core",
3
- "version": "1.2.0",
3
+ "version": "2.2.0",
4
4
  "description": "Nuxt 3 Core Layer - 可重用的基礎架構",
5
5
  "type": "module",
6
6
  "files": [
@@ -19,6 +19,7 @@
19
19
  "utils",
20
20
  "stores",
21
21
  "types",
22
+ "repositories",
22
23
  "bin",
23
24
  "schemas",
24
25
  "nuxt.config.ts",
@@ -53,7 +54,7 @@
53
54
  "typecheck": "nuxi typecheck",
54
55
  "sync-configs": "node scripts/sync-configs.mjs",
55
56
  "release": "node scripts/release.mjs",
56
- "test:init": "npm pack && node bin/init.mjs init tmp-test-project && rm *.tgz"
57
+ "test:init": "npm pack && node bin/init.mjs init ${1:-tmp-test-project} && rm *.tgz"
57
58
  },
58
59
  "dependencies": {
59
60
  "@nuxt/eslint-config": "^0.5.7",
@@ -0,0 +1,18 @@
1
+ import user from './modules/user'
2
+ import auth from './modules/auth'
3
+
4
+ /**
5
+ * Repository Index (資料層)
6
+ *
7
+ * 職責:
8
+ * 1. 自動掃描 modules 資料夾下的所有檔案
9
+ * 2. 將它們打包成一個大物件匯出
10
+ * 3. 不依賴 Nuxt,純粹的資料定義
11
+ */
12
+
13
+ const repositories = {
14
+ user,
15
+ auth
16
+ }
17
+
18
+ export default repositories
@@ -0,0 +1,29 @@
1
+ import type { UseFetchOptions } from 'nuxt/app'
2
+
3
+ // 定義 Client,指向 /auth (假設後端路由是 /api/auth)
4
+ const api = useClient('/auth')
5
+
6
+ export default {
7
+ /**
8
+ * 使用者登入
9
+ * @param credentials { username, password }
10
+ * @param options
11
+ */
12
+ login(credentials: any, options: UseFetchOptions<any> = {}) {
13
+ return api.post('/login', credentials, options)
14
+ },
15
+
16
+ /**
17
+ * 使用者登出 (如果有的話)
18
+ */
19
+ logout() {
20
+ return api.post('/logout')
21
+ },
22
+
23
+ /**
24
+ * 取得當前使用者資訊 (Session/Me)
25
+ */
26
+ getProfile() {
27
+ return api.get('/me')
28
+ }
29
+ }
@@ -0,0 +1,100 @@
1
+ import type { UseFetchOptions } from 'nuxt/app'
2
+ import type { UserListResponse } from '../../types/api'
3
+
4
+ type MaybeRef<T> = T | Ref<T>
5
+
6
+ interface UserQueryParams {
7
+ page?: MaybeRef<number>
8
+ itemsPerPage?: MaybeRef<number>
9
+ q?: MaybeRef<string>
10
+ }
11
+
12
+ // 1. 定義 Client
13
+ const api = useClient('/users')
14
+
15
+ export default {
16
+ /**
17
+ * 取得使用者列表
18
+ * @param params - 查詢參數 (如分頁、搜尋關鍵字)
19
+ * @param options - 其他 Fetch 選項
20
+ * @returns List of users
21
+ */
22
+ getUsers(params: UserQueryParams = {}, options: UseFetchOptions<UserListResponse> = {}) {
23
+ return api.get<UserListResponse>('/', {
24
+ query: params,
25
+ ...options
26
+ })
27
+ },
28
+
29
+ /**
30
+ * 根據 ID 取得單一使用者
31
+ * @param id - 使用者 ID
32
+ * @returns User detail
33
+ */
34
+ getUserById(id: MaybeRef<number>) {
35
+ return api.get<any>(`/${unref(id)}`)
36
+ },
37
+
38
+ /**
39
+ * [範例] 建立使用者
40
+ * @param userData - 使用者資料物件
41
+ * @returns Created user data
42
+ */
43
+ createUser(userData: any) {
44
+ return api.post('/', userData)
45
+ },
46
+
47
+ /**
48
+ * [範例] 搜尋使用者
49
+ * @param keyword - 搜尋關鍵字
50
+ * @returns Search results
51
+ */
52
+ searchUsers(keyword: Ref<string>) {
53
+ return api.get('/search', {
54
+ query: { q: keyword },
55
+ watch: [keyword]
56
+ })
57
+ },
58
+
59
+ /**
60
+ * [範例] 更新使用者
61
+ * @param id - 使用者 ID
62
+ * @param userData - 更新的使用者資料
63
+ * @returns Updated user data
64
+ */
65
+ updateUser(id: number, userData: any) {
66
+ return api.put(`/${id}`, userData)
67
+ },
68
+
69
+ /**
70
+ * [範例] 刪除使用者
71
+ * @param id - 使用者 ID
72
+ * @returns Deletion result
73
+ */
74
+ deleteUser(id: number) {
75
+ return api.delete(`/${id}`)
76
+ },
77
+
78
+ /**
79
+ * [範例] 取得大量資料 (延遲載入)
80
+ * @returns Heavy data report
81
+ */
82
+ getHeavyData() {
83
+ // 注意:這裡是 /heavy-report,因為 api base 是 /users
84
+ // 假設 heavy-report 也是在 /users 下,否則需另外處理
85
+ // 原始碼是 useApi('/heavy-report'),這可能不在 /users 下
86
+ // 為了安全,這裡假設它是獨立的
87
+ // 如果是獨立的,應該用 useApi 或另外一個 useClient
88
+ // 這裡我判斷它可能是 /users/heavy-report 的筆誤,或者真的是 root level
89
+ // 為了保持功能,我這裡用 root client 處理 (假設它不是 users/*)
90
+ // 但原檔放在 user.ts 卻叫 /heavy-report 有點怪
91
+ // 我先維持原路徑:/heavy-report (不接在 /users 後面)
92
+
93
+ // 這裡特別展示:如何在 policy 模式下呼叫「外面」的 API
94
+ // 方法 1: 使用 useApi
95
+ // return useApi('/heavy-report', { lazy: true })
96
+
97
+ // 方法 2 (如果它其實是 user 報表):
98
+ return useApi('/heavy-report', { lazy: true })
99
+ }
100
+ }
@@ -121,34 +121,107 @@ export interface ProductConfig {
121
121
  * @param rootDir 專案根目錄 (預設抓目前位置)
122
122
  * @returns 讀到的設定內容
123
123
  */
124
- export function getProductConfig(rootDir: string = process.cwd()): ProductConfig {
124
+ export function getProductConfig(rootDir?: string): ProductConfig {
125
+ // 核心預設結構,確保不會噴 undefined 錯誤 (visible, enabled 等)
126
+ let config: ProductConfig = {
127
+ branding: {},
128
+ meta: {},
129
+ layout: {
130
+ header: { visible: true },
131
+ footer: { visible: true },
132
+ sidebar: { visible: true }
133
+ },
134
+ theme: {},
135
+ network: {},
136
+ features: {},
137
+ auth: {},
138
+ modules: {}
139
+ }
140
+
141
+ // 如果是在瀏覽器環境,直接回傳預設結構 (瀏覽器應該透過 useAppConfig 取得)
142
+ if (typeof process === 'undefined' || !process.cwd) {
143
+ return config
144
+ }
145
+
146
+ // 診斷日誌:寫入檔案以便查閱 (因為看見不到終端機)
147
+ const debugLog = (msg: string) => {
148
+ try {
149
+ fs.appendFileSync('c:\\Users\\gino.huang\\Desktop\\nuxt-layer-core-playground\\product-loader-debug.log', `[${new Date().toISOString()}] ${msg}\n`)
150
+ } catch (e) {}
151
+ console.log(msg)
152
+ }
153
+ debugLog(`>>> getProductConfig start. CWD: ${process.cwd()}, ENV: Ref=${process.env.npm_package_name}`)
154
+
125
155
  // 1. 決定要讀哪個設定檔
126
- // 可以在 package.json 裡面用 cross-env PRODUCT_CONFIG=xxx 來指定
127
156
  const productConfigName = process.env.PRODUCT_CONFIG || 'default'
128
157
 
129
- // 2. 算出檔案在哪裡
130
- const configPath = path.resolve(rootDir, 'configs', `${productConfigName}.json`)
158
+ // 2. 智慧路徑搜尋:找出最近的 configs/ 資料夾
159
+ // 我們會檢查:
160
+ // A. 目前執行目錄 (CWD) 下的 configs/
161
+ // B. 往上找 5 層 (適用於從根目錄跑 workspace 指令的情況)
162
+ let currentSearchDir = process.cwd()
163
+ let configPath = ''
164
+
165
+ console.log(`[Config Debug] CWD: ${currentSearchDir}`)
166
+
167
+ for (let i = 0; i < 5; i++) {
168
+ const targetPath = path.resolve(currentSearchDir, 'configs', `${productConfigName}.json`)
169
+ debugLog(`[Config Path Check ${i}] ${targetPath}`)
170
+
171
+ if (fs.existsSync(targetPath)) {
172
+ configPath = targetPath
173
+ break
174
+ }
175
+
176
+ // 額外支援:如果在根目錄跑,嘗試找 apps/${npm_package_name}/configs
177
+ if (i === 0) {
178
+ const activePackage = process.env.npm_package_name
179
+ const appsDir = path.resolve(currentSearchDir, 'apps')
180
+ debugLog(`[Config Context Check] Package: ${activePackage}, appsDir: ${appsDir}`)
181
+
182
+ if (activePackage && fs.existsSync(path.resolve(appsDir, activePackage))) {
183
+ const appConfigPath = path.resolve(appsDir, activePackage, 'configs', `${productConfigName}.json`)
184
+ debugLog(`[Config Workspace Check] ${appConfigPath}`)
185
+ if (fs.existsSync(appConfigPath)) {
186
+ configPath = appConfigPath
187
+ break
188
+ }
189
+ }
131
190
 
132
- // 預設給一個空的,免得找不到檔案時出錯
133
- let config: ProductConfig = { modules: {} }
191
+ if (!configPath && fs.existsSync(appsDir)) {
192
+ const apps = fs.readdirSync(appsDir)
193
+ for (const app of apps) {
194
+ const appConfigPath = path.resolve(appsDir, app, 'configs', `${productConfigName}.json`)
195
+ if (fs.existsSync(appConfigPath)) {
196
+ configPath = appConfigPath
197
+ break
198
+ }
199
+ }
200
+ }
201
+ }
202
+
203
+ if (configPath) break
204
+
205
+ const nextDir = path.dirname(currentSearchDir)
206
+ if (nextDir === currentSearchDir) break
207
+ currentSearchDir = nextDir
208
+ }
134
209
 
135
- // 3. 真的去讀檔案
136
- if (fs.existsSync(configPath)) {
210
+ if (configPath) {
211
+ debugLog(`[Config Success] 最終確認讀取路徑: ${configPath}`)
137
212
  try {
138
213
  const fileContents = fs.readFileSync(configPath, 'utf8')
139
214
  const loaded = JSON.parse(fileContents)
140
215
 
141
- // 有讀到東西就合併進去
142
216
  if (loaded) {
143
217
  config = { ...config, ...loaded }
218
+ debugLog(`[Config Success] 載入內容: title=${config.meta?.title}, color=${config.theme?.primaryColor}`)
144
219
  }
145
- console.log(`[Config] 成功載入設定: ${productConfigName}`)
146
220
  } catch (e) {
147
- console.error(`[Config] 讀取設定失敗 ${configPath}:`, e)
221
+ debugLog(`[Config Error] 讀取 JSON 失敗 ${configPath}: ${e}`)
148
222
  }
149
223
  } else {
150
- // 找不到檔案,提醒一下
151
- console.warn(`[Config] 找不到設定檔: ${configPath},將不載入任何模組。`)
224
+ debugLog(`[Config Warning] 在任何路徑中都找不到 ${productConfigName}.json`)
152
225
  }
153
226
 
154
227
  return config
@@ -1,24 +1,24 @@
1
- import { execSync } from 'child_process'
2
-
3
- /**
4
- * 自動化發布腳本
5
- * 用法:
6
- * npm run release (預設 patch: 1.1.0 -> 1.1.1)
7
- * npm run release minor (1.1.0 -> 1.2.0)
8
- * npm run release major (1.1.0 -> 2.0.0)
9
- */
10
-
11
- const type = process.argv[2] || 'patch'
12
-
13
- try {
14
- console.log(`\n\x1b[36m[Release] Step 1: Bumping version (${type})...\x1b[0m`)
15
- execSync(`npm version ${type} --no-git-tag-version`, { stdio: 'inherit' })
16
-
17
- console.log(`\n\x1b[36m[Release] Step 2: Publishing to npm...\x1b[0m`)
18
- execSync('npm publish', { stdio: 'inherit' })
19
-
20
- console.log('\n\x1b[32m[Release] Successfully published! 🎉\x1b[0m\n')
21
- } catch (error) {
22
- console.error('\n\x1b[31m[Release] Failed to publish:\x1b[0m', error.message)
23
- process.exit(1)
24
- }
1
+ import { execSync } from 'child_process'
2
+
3
+ /**
4
+ * 自動化發布腳本
5
+ * 用法:
6
+ * npm run release (預設 patch: 1.1.0 -> 1.1.1)
7
+ * npm run release minor (1.1.0 -> 1.2.0)
8
+ * npm run release major (1.1.0 -> 2.0.0)
9
+ */
10
+
11
+ const type = process.argv[2] || 'patch'
12
+
13
+ try {
14
+ console.log(`\n\x1b[36m[Release] Step 1: Bumping version (${type})...\x1b[0m`)
15
+ execSync(`npm version ${type} --no-git-tag-version`, { stdio: 'inherit' })
16
+
17
+ console.log(`\n\x1b[36m[Release] Step 2: Publishing to npm...\x1b[0m`)
18
+ execSync('npm publish', { stdio: 'inherit' })
19
+
20
+ console.log('\n\x1b[32m[Release] Successfully published! 🎉\x1b[0m\n')
21
+ } catch (error) {
22
+ console.error('\n\x1b[31m[Release] Failed to publish:\x1b[0m', error.message)
23
+ process.exit(1)
24
+ }
package/stores/app.ts CHANGED
@@ -2,8 +2,31 @@ import { defineStore } from 'pinia'
2
2
  import { defaultLayoutConfig, type LayoutConfig } from '../core/config/layout'
3
3
 
4
4
  export const useAppStore = defineStore('app', () => {
5
- // 狀態
6
- const config = ref<LayoutConfig>({ ...defaultLayoutConfig })
5
+ const appConfig = useAppConfig()
6
+ const coreConfig = appConfig.core as any
7
+
8
+ // 狀態:從 appConfig.core 獲取初始值,若無則回退到 defaultLayoutConfig
9
+ const config = ref<LayoutConfig>({
10
+ ...defaultLayoutConfig,
11
+ ...(coreConfig?.layout || {}),
12
+ // 深度處理特定區塊
13
+ theme: {
14
+ ...defaultLayoutConfig.theme,
15
+ ...(coreConfig?.theme || {})
16
+ },
17
+ header: {
18
+ ...defaultLayoutConfig.header,
19
+ ...(coreConfig?.layout?.header || {})
20
+ },
21
+ footer: {
22
+ ...defaultLayoutConfig.footer,
23
+ ...(coreConfig?.layout?.footer || {})
24
+ },
25
+ sidebar: {
26
+ ...defaultLayoutConfig.sidebar,
27
+ ...(coreConfig?.layout?.sidebar || {})
28
+ }
29
+ })
7
30
  const drawer = ref(true)
8
31
  const loading = ref(false)
9
32