@bndynet/vue-site 1.0.2 → 1.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.
- package/README.md +229 -10
- package/README.zh.md +538 -0
- package/bin/vue-site.mjs +212 -18
- package/dist/components/LocaleSwitch.vue.d.ts +10 -0
- package/dist/composables/useLocale.d.ts +19 -0
- package/dist/composables/useLocalize.d.ts +18 -0
- package/dist/i18n-messages.d.ts +9 -0
- package/dist/i18n-utils.d.ts +81 -0
- package/dist/index.d.ts +6 -1
- package/dist/index.es.js +10993 -10562
- package/dist/router.d.ts +9 -3
- package/dist/style.css +1 -1
- package/dist/theme/presets.d.ts +7 -0
- package/dist/theme/resolve-palettes.d.ts +7 -1
- package/dist/types.d.ts +129 -17
- package/package.json +3 -2
package/README.zh.md
ADDED
|
@@ -0,0 +1,538 @@
|
|
|
1
|
+
# @bndynet/vue-site
|
|
2
|
+
|
|
3
|
+
可配置的 Vue 3 站点框架:一个依赖包、一份 `site.config.ts` 以及 Markdown 页面 —— 即可获得侧边栏、语法高亮代码块、亮色/暗色主题。无需手写 `main.ts`、`index.html` 或 `vite.config.ts`。
|
|
4
|
+
|
|
5
|
+
## 特性
|
|
6
|
+
|
|
7
|
+
- 配置驱动的导航(使用 Lucide 图标名)
|
|
8
|
+
- 通过 `visible` 断言进行权限控制的导航(同步或异步;隐藏条目并跳过其路由)
|
|
9
|
+
- 通过 `auth` + 中心化的 `authorize` 策略实现按页面授权(导航守卫 + 登录重定向)
|
|
10
|
+
- Hash 或 HTML5(`web`)路由历史模式,可在 `site.config.ts` 中配置
|
|
11
|
+
- 支持 Markdown(`?raw`)或 Vue 页面
|
|
12
|
+
- highlight.js、亮色/暗色主题 + localStorage 持久化
|
|
13
|
+
- 内置多语言支持(语言切换器、`LocalizedString` 配置、按语言区分的页面)—— 参见[国际化](#国际化-i18n)
|
|
14
|
+
- 将项目的 `README.md` 作为首页
|
|
15
|
+
- 完整的 TypeScript 类型
|
|
16
|
+
|
|
17
|
+
## 快速开始
|
|
18
|
+
|
|
19
|
+
**安装**
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npm install @bndynet/vue-site
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
**`site.config.ts`**
|
|
26
|
+
|
|
27
|
+
```typescript
|
|
28
|
+
import { defineConfig } from '@bndynet/vue-site'
|
|
29
|
+
|
|
30
|
+
export default defineConfig({
|
|
31
|
+
title: 'My Project',
|
|
32
|
+
nav: [
|
|
33
|
+
{ label: 'Home', icon: 'home', page: () => import('./README.md?raw') },
|
|
34
|
+
{ label: 'Guide', icon: 'book-open', page: () => import('./pages/guide.md?raw') },
|
|
35
|
+
],
|
|
36
|
+
})
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
**目录结构**
|
|
40
|
+
|
|
41
|
+
```
|
|
42
|
+
my-site/
|
|
43
|
+
package.json
|
|
44
|
+
site.config.ts
|
|
45
|
+
README.md
|
|
46
|
+
pages/guide.md
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
**CLI**(`vue-site` 与 `vs` 等价)
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
npx vue-site dev
|
|
53
|
+
npx vue-site build
|
|
54
|
+
npx vue-site preview
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
子路径部署:在 CLI 上传入 Vite 的公共 base(会覆盖 `site.config` 中的 `env.vite.base`):
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
npx vue-site build --base=/app/
|
|
61
|
+
# 或
|
|
62
|
+
npx vue-site build --base /app/
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
如果愿意,可以在 `package.json` 的 scripts 中加上 `"dev": "vue-site dev"`(或 `vs dev`)。
|
|
66
|
+
|
|
67
|
+
## 配置参考
|
|
68
|
+
|
|
69
|
+
### `SiteConfig`
|
|
70
|
+
|
|
71
|
+
| 属性 | 说明 |
|
|
72
|
+
|----------|-------------|
|
|
73
|
+
| `title` | 站点标题(侧边栏 + 标签页)。`LocalizedString` |
|
|
74
|
+
| `nav` | `NavItem[]` |
|
|
75
|
+
| `defaultPath` | 站点打开时的路径;`/` 和未知路径会重定向到此。必须匹配一个已注册的路由(某个 `nav` 条目解析后的路径,或某个 `pages` 条目的 `path`)。默认为第一个顶级 `nav` 条目 |
|
|
76
|
+
| `logo` | Logo 的 URL 或导入的图片 |
|
|
77
|
+
| `theme` | 见下方 `ThemeConfig`;设为 `false` 可禁用主题(隐藏切换器、强制使用固定的 `light` 调色板、不进行 localStorage 持久化) |
|
|
78
|
+
| `i18n` | 多语言配置(`I18nConfig`)—— 参见[国际化](#国际化-i18n) |
|
|
79
|
+
| `footer` | 页脚文本。`LocalizedString` |
|
|
80
|
+
| `readme` | 当没有 `README.md` 时作为首页的原始内容 |
|
|
81
|
+
| `links` | 头部链接:Lucide `icon` + `link`,可选的 `title`(`LocalizedString`) |
|
|
82
|
+
| `pages` | `StandalonePage[]` —— 位于 `nav` 树之外的全屏路由(无顶部栏/侧边栏/页脚) |
|
|
83
|
+
| `auth` | 中心化的授权策略(`AuthConfig`)—— 参见[按页面授权](#按页面授权-auth) |
|
|
84
|
+
| `router` | 历史模式(`RouterConfig`)—— `hash`(默认)或 HTML5 `web`;参见[路由历史](#路由历史-router) |
|
|
85
|
+
| `packageRepository` | 通常由 CLI 从 `package.json` 设置;单独使用 `createSiteApp` 时可省略 |
|
|
86
|
+
| `env` | 开发/构建选项 —— 见下文 |
|
|
87
|
+
| `bootstrap` | 可选的站点根目录相对路径(如 `./bootstrap.ts`)—— 在 Vue 应用之前加载一次的模块 |
|
|
88
|
+
| `configureApp` | 可选的 `(app) => void \| Promise<void>`,在路由安装之后、`mount` 之前执行(参见 [`configureApp` 中的本地包](#在-configureapp-中使用本地包)) |
|
|
89
|
+
|
|
90
|
+
### `NavItem`
|
|
91
|
+
|
|
92
|
+
| 属性 | 说明 |
|
|
93
|
+
|----------|-------------|
|
|
94
|
+
| `label` | 侧边栏文本。`LocalizedString` |
|
|
95
|
+
| `icon` | [Lucide](https://lucide.dev/icons) 图标名 |
|
|
96
|
+
| `page` | 页面内容。最简单的是一个**文件路径字符串**,如 `'./pages/AdminView.vue'` 或 `'./README.md'`(自动加载各语言的同名文件,找不到时回退到基础文件)。也接受加载器(`() => import('./Page.vue')` / `() => import('./page.md?raw')`)或 `localizedPage(...)` 的返回值。见[按语言区分的页面内容](#按语言区分的页面内容)与[高级页面加载器](#高级页面加载器) |
|
|
97
|
+
| `path` | 路由路径(省略时从 `label` 的默认语言值派生;切换语言时保持稳定) |
|
|
98
|
+
| `children` | 嵌套分组 |
|
|
99
|
+
| `link` | 渲染为超链接(内部路由路径或外部 URL),而非页面路由 |
|
|
100
|
+
| `visible` | `() => boolean \| Promise<boolean>`,在启动时等待执行一次。返回 `false` 会从导航中隐藏该条目并跳过其路由(无法通过直接 URL 访问)。被隐藏的父项会隐藏其整个子树;没有剩余子项的分组会被剪除。对后续变化不具响应性。 |
|
|
101
|
+
| `auth` | 由 `auth.authorize` 解释的授权规则(`AuthRule`)。会保持路由注册并通过导航守卫强制执行(因此直接访问 URL 会重定向到登录页)。需要配置 `SiteConfig.auth`。参见[按页面授权](#按页面授权-auth)。 |
|
|
102
|
+
|
|
103
|
+
### `ThemeConfig`
|
|
104
|
+
|
|
105
|
+
内置主题为 `light`、`dark`,外加始终可用的额外主题 `sepia` 和 `ocean`(在每个站点的切换器中都会显示)。在 `SiteConfig` 上设置 `theme: false` 可完全禁用主题。
|
|
106
|
+
|
|
107
|
+
| 属性 | 默认值 | 说明 |
|
|
108
|
+
|----------|---------|-------------|
|
|
109
|
+
| `default` | `light` | `light`、`dark`、内置额外主题 id(`sepia`、`ocean`),或某个 `extraThemes[].id` |
|
|
110
|
+
| `colors` | — | 全局 CSS 变量覆盖 |
|
|
111
|
+
| `palettes` | — | 仅针对内置 light/dark 的部分覆盖 |
|
|
112
|
+
| `extraThemes` | — | 额外主题:`id`、`label`、`icon`,可选 `basedOn`、`palette`;复用内置 id(`sepia`/`ocean`)即可覆盖它。导入 `builtinThemePalettes` 获取完整默认值 |
|
|
113
|
+
|
|
114
|
+
## 国际化(`i18n`)
|
|
115
|
+
|
|
116
|
+
设置 `i18n` 以启用多语言支持。框架会在头部添加语言切换器,
|
|
117
|
+
针对当前激活的语言解析每一个 `LocalizedString` 字段(`title`、`nav[].label`、`footer`、`links[].title`),
|
|
118
|
+
并通过 `useLocale()` / `useLocalize()` 暴露当前语言。
|
|
119
|
+
|
|
120
|
+
```typescript
|
|
121
|
+
import { defineConfig } from '@bndynet/vue-site'
|
|
122
|
+
|
|
123
|
+
export default defineConfig({
|
|
124
|
+
i18n: {
|
|
125
|
+
locales: [
|
|
126
|
+
{ code: 'en', label: 'English' },
|
|
127
|
+
{ code: 'zh', label: '简体中文', icon: 'languages' },
|
|
128
|
+
],
|
|
129
|
+
defaultLocale: 'en',
|
|
130
|
+
},
|
|
131
|
+
title: { en: 'My Site', zh: '我的站点' }, // 一个 LocalizedString
|
|
132
|
+
footer: { en: '© 2026', zh: '© 2026 版权所有' },
|
|
133
|
+
nav: [
|
|
134
|
+
{ label: { en: 'Home', zh: '首页' }, icon: 'home', page: '../README.md' },
|
|
135
|
+
{
|
|
136
|
+
label: { en: 'Guide', zh: '指南' },
|
|
137
|
+
icon: 'book',
|
|
138
|
+
// 按语言区分的内容:./pages/guide.md(基础)+ guide.zh.md,由 CLI 自动发现。
|
|
139
|
+
page: './pages/guide.md',
|
|
140
|
+
},
|
|
141
|
+
],
|
|
142
|
+
})
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### 按语言区分的页面内容
|
|
146
|
+
|
|
147
|
+
把 `page` 指向一个**文件路径字符串**,框架就会为当前语言加载正确的文件 —— 无需任何额外接线:
|
|
148
|
+
|
|
149
|
+
```typescript
|
|
150
|
+
import { defineConfig, tk } from '@bndynet/vue-site'
|
|
151
|
+
|
|
152
|
+
export default defineConfig({
|
|
153
|
+
i18n: { locales: [{ code: 'en' }, { code: 'zh' }], defaultLocale: 'en' },
|
|
154
|
+
nav: [
|
|
155
|
+
// 加载 ../README.md;当语言为 `zh` 时自动改用 ../README.zh.md。
|
|
156
|
+
{ label: tk('nav.home'), icon: 'home', page: '../README.md' },
|
|
157
|
+
// Vue 页面同理:Dashboard.vue + Dashboard.zh.vue。
|
|
158
|
+
{ label: tk('nav.dash'), icon: 'gauge', page: './pages/Dashboard.vue' },
|
|
159
|
+
],
|
|
160
|
+
})
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
- 在基础文件旁边以 `名.<code>.<ext>` 命名各语言变体 —— `README.zh.md`、`Dashboard.zh.vue`……
|
|
164
|
+
- 没有对应文件的语言会回退到**基础文件**(`README.md`)。解析顺序:精确匹配 → 主子标签(`zh-TW` → `zh`)→ 基础文件。
|
|
165
|
+
- **新增语言只需放入一个 `名.<code>` 文件 —— 无需改配置。**
|
|
166
|
+
- 支持 Markdown(`.md`)和 Vue(`.vue`)。基础名称不能包含点号(`README.md` ✓,`my.page.md` ✗)。
|
|
167
|
+
|
|
168
|
+
> 字符串形式由 `vue-site` CLI 在构建时解析。如果你自行嵌入该库(不使用 CLI —— 见[库模式](#库模式)),
|
|
169
|
+
> 请改用[高级页面加载器](#高级页面加载器)中的加载器。
|
|
170
|
+
|
|
171
|
+
### 高级页面加载器
|
|
172
|
+
|
|
173
|
+
> **一般用不到。** 上面的文件路径字符串已能覆盖大多数站点。只有在你需要一个普通的单文件加载器、
|
|
174
|
+
> 各语言文件**不同名**,或在**不使用 CLI**(库模式)时,才需要用到这些。
|
|
175
|
+
|
|
176
|
+
除了字符串,`page` 还接受一个**加载器函数**或 `localizedPage(...)` 的返回值:
|
|
177
|
+
|
|
178
|
+
- **单文件、不做多语言** —— 一个普通的动态导入:
|
|
179
|
+
|
|
180
|
+
```typescript
|
|
181
|
+
page: () => import('./pages/Dashboard.vue') // Markdown 用 () => import('./guide.md?raw')
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
- **显式语言映射** —— 当各语言文件不共享基础名称(字符串形式无法推断)时:
|
|
185
|
+
|
|
186
|
+
```typescript
|
|
187
|
+
import { localizedPage } from '@bndynet/vue-site'
|
|
188
|
+
|
|
189
|
+
page: localizedPage({
|
|
190
|
+
en: () => import('./pages/guide-en.md?raw'),
|
|
191
|
+
zh: () => import('./pages/guide-zh.md?raw'),
|
|
192
|
+
})
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
- **glob(库模式)** —— 与字符串形式相同的自动发现,但显式写出,因此不依赖 CLI:
|
|
196
|
+
|
|
197
|
+
```typescript
|
|
198
|
+
page: localizedPage(import.meta.glob(['../README.md', '../README.*.md'], { query: '?raw' }))
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
`page: '../README.md'` 正是 CLI 帮你生成的这一段。(Vue 页面去掉 `{ query: '?raw' }` 即可。)
|
|
202
|
+
|
|
203
|
+
所有 `localizedPage` 形式在当前语言没有对应文件时都会回退(精确匹配 → 主子标签 → 基础文件 → 第一个)。
|
|
204
|
+
|
|
205
|
+
### 工作原理
|
|
206
|
+
|
|
207
|
+
- **初始语言**:已存储的选择(localStorage)> 浏览器语言(`navigator.language`,当开启 `detectBrowser` 时)> `defaultLocale` > 第一个条目。
|
|
208
|
+
- **`LocalizedString`** 为 `string | Record<LocaleCode, string> | MessageRef`。普通字符串会原样返回
|
|
209
|
+
(单语言配置照常工作),语言映射保存内联的按语言文本,而 `MessageRef`(用 `tk('id')` 构建)
|
|
210
|
+
引用中心化消息文件中的某个键 —— 参见[中心化消息文件](#消息文件与键-tk--t)。
|
|
211
|
+
- **稳定的 URL**:路由路径从**默认语言**的 label(或显式 `path`)派生,因此切换语言永远不会改变 URL。
|
|
212
|
+
- **响应式**:切换语言会实时更新 label、标题、页脚和页面内容(页面内容通过 `localizedPage` 重新加载)。
|
|
213
|
+
仅当 `locales.length > 1` 时才会出现切换器。
|
|
214
|
+
- **UI 字符串**:框架自带内置字符串(目前为 `en`、`zh`)用于主题/语言切换器和页面错误提示;
|
|
215
|
+
可通过 `i18n.messages` 按语言覆盖或扩展它们。
|
|
216
|
+
|
|
217
|
+
### `I18nConfig`
|
|
218
|
+
|
|
219
|
+
| 属性 | 默认值 | 说明 |
|
|
220
|
+
|----------|---------|-------------|
|
|
221
|
+
| `locales` | 自动发现的 `locales/*.json` | `{ code, label, icon? }[]` —— 支持的语言,按显示顺序排列。可选:省略时从自动加载的文件名派生(使用内置标签)。第一个条目为回退项 |
|
|
222
|
+
| `defaultLocale` | `locales[0].code` | 当没有存储值且检测未命中时使用的初始语言 |
|
|
223
|
+
| `detectBrowser` | `true` | 首次访问时从 `navigator.language(s)` 检测初始语言 |
|
|
224
|
+
| `storageKey` | `vue-site-locale` | 用于存储所选语言的 localStorage 键 |
|
|
225
|
+
| `messages` | 自动从 `locales/<code>.json` 加载 | `Record<LocaleCode, Record<string, string>>` —— 额外/覆盖的翻译,会合并到自动加载的文件和内置 UI 字符串之上 |
|
|
226
|
+
|
|
227
|
+
### 消息文件与键(`tk` / `t`)
|
|
228
|
+
|
|
229
|
+
与其到处内联 `{ en, zh }`,不如把所有翻译放在纯 JSON 中 —— **每种语言一个文件** —— 并通过键引用它们。
|
|
230
|
+
这是零配置的:CLI 会在你的 `site.config.ts` 旁自动发现 `locales/<code>.json`。
|
|
231
|
+
你无需编写任何粘合代码(无需 `index.ts`,无需 `messages` 字段),甚至不必列出语言。
|
|
232
|
+
|
|
233
|
+
```jsonc
|
|
234
|
+
// locales/en.json —— 嵌套分组(推荐),会被展平为点号分隔的 id
|
|
235
|
+
{
|
|
236
|
+
"site": { "title": "My Site" },
|
|
237
|
+
"nav": { "home": "Home", "guide": "Guide" }
|
|
238
|
+
}
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
```jsonc
|
|
242
|
+
// locales/zh.json
|
|
243
|
+
{
|
|
244
|
+
"site": { "title": "我的站点" },
|
|
245
|
+
"nav": { "home": "首页", "guide": "指南" }
|
|
246
|
+
}
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
> 文件可以是**嵌套的**(如上)或**扁平的**(`{ "site.title": "My Site" }`)—— 嵌套分组会被
|
|
250
|
+
> 展平为点号分隔的 id,因此 `tk('site.title')` / `t('site.title')` 两种写法都能工作。
|
|
251
|
+
|
|
252
|
+
```typescript
|
|
253
|
+
// site.config.ts —— 在配置中用 tk() 引用键,在页面中用 t()
|
|
254
|
+
import { defineConfig, tk } from '@bndynet/vue-site'
|
|
255
|
+
|
|
256
|
+
export default defineConfig({
|
|
257
|
+
// `i18n` 可以完全省略:语言列表会从文件名(en、zh、……)派生,
|
|
258
|
+
// 并带有友好的内置标签。仅当需要自定义 label/icon/顺序时才声明它。
|
|
259
|
+
i18n: {
|
|
260
|
+
locales: [
|
|
261
|
+
{ code: 'en', label: 'English' },
|
|
262
|
+
{ code: 'zh', label: '简体中文', icon: 'languages' },
|
|
263
|
+
],
|
|
264
|
+
defaultLocale: 'en',
|
|
265
|
+
},
|
|
266
|
+
title: tk('site.title'),
|
|
267
|
+
nav: [
|
|
268
|
+
{ label: tk('nav.home'), icon: 'home', page: '../README.md' },
|
|
269
|
+
{
|
|
270
|
+
label: tk('nav.guide'),
|
|
271
|
+
icon: 'book',
|
|
272
|
+
page: './pages/guide.md',
|
|
273
|
+
},
|
|
274
|
+
],
|
|
275
|
+
})
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
键的解析会依次回退:当前语言的主子标签 → `defaultLocale` → `en` → id 本身,并对 `{name}`
|
|
279
|
+
占位符进行插值。`tk()` 与内联 `{ en, zh }` 映射可以自由混用 —— 对共享/集中管理的文本用 `tk()`,
|
|
280
|
+
对一次性字符串用内联映射。
|
|
281
|
+
|
|
282
|
+
**自动发现的细节与覆盖**
|
|
283
|
+
|
|
284
|
+
- 约定是 `locales/<code>.json`(如 `locales/en.json`、`locales/zh.json`),相对于配置目录解析。
|
|
285
|
+
`code` 即文件名(一个 `LocaleCode`,如 `en` 或 `zh-TW`)。
|
|
286
|
+
- 仍支持显式的 `i18n.messages`,并会**覆盖**自动加载的键(按 id);显式的 `i18n.locales`
|
|
287
|
+
控制 label/icon/顺序。两者均为可选。
|
|
288
|
+
- 自动发现是 **CLI** 特性。如果你自行嵌入该库(不使用 `vue-site` CLI 而直接调用 `createSiteApp`),
|
|
289
|
+
请直接传入 `i18n.messages` —— 例如用显式 import 从 JSON 构建(避免在 `site.config.ts` 中使用
|
|
290
|
+
`import.meta.glob`,CLI 会在 Node 中预加载它)。
|
|
291
|
+
|
|
292
|
+
### 在你自己的页面中本地化
|
|
293
|
+
|
|
294
|
+
`useLocalize()` 返回 `t(id, params?)`(从中心目录解析消息 id,并对 `{name}` 进行插值)、
|
|
295
|
+
`localize(value)`(解析任意 `LocalizedString` —— `tk()` 引用、内联映射或普通字符串),
|
|
296
|
+
以及响应式的 `locale` ref。`useLocale()` 返回 `{ locale, setLocale, locales }`,
|
|
297
|
+
用于构建自定义切换器。它们都可在由 `createSiteApp` 渲染的任意组件中工作。
|
|
298
|
+
|
|
299
|
+
```vue
|
|
300
|
+
<script setup lang="ts">
|
|
301
|
+
import { useLocalize } from '@bndynet/vue-site'
|
|
302
|
+
|
|
303
|
+
const { t, localize, locale } = useLocalize()
|
|
304
|
+
</script>
|
|
305
|
+
|
|
306
|
+
<template>
|
|
307
|
+
<!-- 来自中心消息文件的键 -->
|
|
308
|
+
<h1>{{ t('nav.home') }}</h1>
|
|
309
|
+
<!-- 或内联,用于一次性文本 -->
|
|
310
|
+
<p>{{ localize({ en: 'Hello', zh: '你好' }) }} — {{ locale }}</p>
|
|
311
|
+
</template>
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
## 按页面授权(`auth`)
|
|
315
|
+
|
|
316
|
+
基于当前用户对单个页面进行门控。为任意 `NavItem` 或 `StandalonePage` 添加 `auth` 规则,
|
|
317
|
+
并在 `SiteConfig` 中提供单一的 `auth.authorize` 策略来决定访问权限。
|
|
318
|
+
|
|
319
|
+
```typescript
|
|
320
|
+
import { defineConfig } from '@bndynet/vue-site'
|
|
321
|
+
|
|
322
|
+
export default defineConfig({
|
|
323
|
+
title: 'My Site',
|
|
324
|
+
auth: {
|
|
325
|
+
loginPath: '/login',
|
|
326
|
+
authorize: ({ rule }) => {
|
|
327
|
+
const user = getCurrentUser() // 你自己的认证状态
|
|
328
|
+
if (!user) return '/login' // 未登录 -> 重定向(字符串)
|
|
329
|
+
if (rule === true) return true // `auth: true` -> 任意已登录用户
|
|
330
|
+
if (typeof rule === 'string') return user.roles.includes(rule)
|
|
331
|
+
if (Array.isArray(rule)) return rule.some((r) => user.roles.includes(r))
|
|
332
|
+
return true
|
|
333
|
+
},
|
|
334
|
+
},
|
|
335
|
+
nav: [
|
|
336
|
+
{ label: 'Home', icon: 'home', page: () => import('../README.md?raw') },
|
|
337
|
+
{ label: 'Dashboard', icon: 'gauge', auth: true, page: () => import('./pages/Dash.vue') },
|
|
338
|
+
{ label: 'Admin', icon: 'shield', auth: ['admin'], page: () => import('./pages/Admin.vue') },
|
|
339
|
+
],
|
|
340
|
+
pages: [
|
|
341
|
+
{ path: '/login', page: () => import('./pages/Login.vue') }, // 无 `auth` -> 始终可访问
|
|
342
|
+
],
|
|
343
|
+
})
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
### 工作原理
|
|
347
|
+
|
|
348
|
+
- `authorize` 在**每次导航时**运行(一个 Vue Router `beforeEach` 守卫)。它接收 `{ rule, item, to, from }`,
|
|
349
|
+
并返回 `true`(允许)、`false`(拒绝)或一个路径 `string`(重定向,例如到登录页)。
|
|
350
|
+
- 返回 `false` 时,用户会被发送到 `auth.loginPath`,并把请求的路径作为 `redirect` 查询参数
|
|
351
|
+
(`/login?redirect=/admin`);如果未设置 `loginPath`,则取消该次导航。
|
|
352
|
+
- `authorize` 也会在**启动时运行一次**(仅带 `rule` / `item`),以从导航菜单中隐藏未授权的条目。
|
|
353
|
+
与 `visible` 一样,此菜单过滤不具响应性 —— 它反映应用创建时的状态,因此需要通过重建应用来更新它
|
|
354
|
+
(例如登录后整页刷新)。
|
|
355
|
+
- 受保护的路由仍保持**注册**,因此直接访问受保护 URL 会触发守卫(及你的登录重定向),
|
|
356
|
+
而不是静默地 404。
|
|
357
|
+
- 登录页本身**不能**携带 `auth` 规则(且 `loginPath` 始终被守卫允许),以避免重定向循环。
|
|
358
|
+
|
|
359
|
+
### `AuthConfig`
|
|
360
|
+
|
|
361
|
+
| 属性 | 说明 |
|
|
362
|
+
|----------|-------------|
|
|
363
|
+
| `authorize` | `(ctx: AuthContext) => boolean \| string \| Promise<boolean \| string>`。`true` 允许,`false` 拒绝,`string` 重定向。 |
|
|
364
|
+
| `loginPath` | 将被拒绝的用户发送到何处(带 `?redirect=`)。可选;不设置时,拒绝将取消导航。 |
|
|
365
|
+
|
|
366
|
+
`AuthRule` 为 `boolean \| string \| string[] \| ((ctx: AuthContext) => boolean \| Promise<boolean>)`。
|
|
367
|
+
框架从不检查规则;它会将规则转发给 `authorize`,因此其含义完全由你决定。
|
|
368
|
+
|
|
369
|
+
### `visible` 与 `auth` 对比
|
|
370
|
+
|
|
371
|
+
| | `visible` | `auth` |
|
|
372
|
+
|--|-----------|--------|
|
|
373
|
+
| 决定 | 条目/路由是否**存在** | **当前用户**是否可以进入 |
|
|
374
|
+
| 时机 | 构建/启动(一次) | 导航(每次)+ 启动时的菜单过滤 |
|
|
375
|
+
| 路由是否注册 | 否(无法通过 URL 访问) | 是(受守卫保护;可重定向到登录) |
|
|
376
|
+
| 对登录/登出的响应 | 否 | 守卫:是;菜单过滤:否 |
|
|
377
|
+
| 最适合 | 环境 / 功能开关 / 静态裁剪 | 登录状态、角色、登录重定向 |
|
|
378
|
+
|
|
379
|
+
用 `visible` 进行静态存在性裁剪,用 `auth` 进行基于用户的访问控制。它们可以组合在同一个条目上。
|
|
380
|
+
|
|
381
|
+
## 路由历史(`router`)
|
|
382
|
+
|
|
383
|
+
默认情况下路由使用 **hash** 历史模式(`#/path`),它无需任何服务器配置即可在任意静态主机上工作,
|
|
384
|
+
且与公共 base 无关。要获得干净的 URL,请切换到 HTML5 历史模式:
|
|
385
|
+
|
|
386
|
+
```typescript
|
|
387
|
+
export default defineConfig({
|
|
388
|
+
title: 'My Site',
|
|
389
|
+
router: { mode: 'web' }, // /app/admin 而不是 /app/#/admin
|
|
390
|
+
nav: [/* ... */],
|
|
391
|
+
})
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
### `RouterConfig`
|
|
395
|
+
|
|
396
|
+
| 属性 | 默认值 | 说明 |
|
|
397
|
+
|----------|---------|-------------|
|
|
398
|
+
| `mode` | `hash` | `hash`(`#/path`,无需服务器配置)或 `web`(HTML5 干净 URL) |
|
|
399
|
+
| `base` | `import.meta.env.BASE_URL` | `web` 模式的 base 路径。CLI 会自动从 `--base` / `env.vite.base` 设置它;仅当从自定义入口调用 `createSiteApp` 时才需要覆盖。 |
|
|
400
|
+
|
|
401
|
+
注意:
|
|
402
|
+
|
|
403
|
+
- **`web` 模式需要 SPA 回退**:请配置你的主机为未知路径提供 `index.html`,否则深链接 / 刷新会 404。
|
|
404
|
+
Hash 模式无需任何配置。
|
|
405
|
+
- **子路径部署**:使用 CLI 时,`--base=/app/` 会在 `web` 模式下被自动用作历史 base。
|
|
406
|
+
在[库模式](#库模式)下,请从你自己的入口传入 `router: { mode: 'web', base: import.meta.env.BASE_URL }`。
|
|
407
|
+
|
|
408
|
+
## `env`(`SiteEnvConfig`)
|
|
409
|
+
|
|
410
|
+
| 属性 | 说明 |
|
|
411
|
+
|----------|-------------|
|
|
412
|
+
| `port` | 开发服务器端口 |
|
|
413
|
+
| `outDir` | 构建输出(相对于站点根目录;默认 `{folder}-dist`) |
|
|
414
|
+
| `customElements` | 自定义元素的标签前缀(如 `['chat-', 'i-']`) |
|
|
415
|
+
| `watchPackages` | 本地包 —— 参见 [env.watchPackages](#envwatchpackages) |
|
|
416
|
+
| `vite` | Vite 覆盖配置(不含 `root`);框架会合并别名、`server.fs.allow`、`build.outDir` 等 |
|
|
417
|
+
|
|
418
|
+
### `env.watchPackages`
|
|
419
|
+
|
|
420
|
+
- **字符串** —— 仅包名(如工作区软链接 / `npm link`)。会跳过依赖预打包;文件监听遵循 Vite 默认行为。
|
|
421
|
+
- **`{ name, entryPath }`** —— `name` 必须匹配导入说明符(如 `@scope/pkg`)。`entryPath` **相对于
|
|
422
|
+
你运行 CLI 的目录**(包含 `site.config.*` 的文件夹)。Vite 会将该包解析为你的**源码入口**以支持开发 HMR,
|
|
423
|
+
并将包目录加入 `server.fs.allow`。
|
|
424
|
+
- 如果你将 **`env.vite.resolve.alias` 用作数组**(`{ find, replacement }[]`),CLI 仍会正确合并
|
|
425
|
+
`watchPackages` 的别名(仅对象展开会破坏这一点)。
|
|
426
|
+
- 如果某个包列在此处,**不要**在 `site.config.ts` 中再为同一个包添加**顶层值导入** —— 配置预加载在
|
|
427
|
+
Node 中运行并会解析到 `node_modules`,而应用使用的是 Vite。请改用 **`configureApp` + 动态 `import()`**
|
|
428
|
+
(见下一节)。
|
|
429
|
+
|
|
430
|
+
### 在 `configureApp` 中使用本地包
|
|
431
|
+
|
|
432
|
+
`configureApp` 可以是 **`async`** 的,因此你可以在路由安装之后 `await import('your-package')`。
|
|
433
|
+
该动态导入在浏览器中、在 Vite 下运行,因此它遵循 `watchPackages`,且不会在 CLI 配置加载期间运行。
|
|
434
|
+
|
|
435
|
+
```typescript
|
|
436
|
+
export default defineConfig({
|
|
437
|
+
env: {
|
|
438
|
+
watchPackages: [{ name: '@acme/widgets', entryPath: '../widgets/src/index.ts' }],
|
|
439
|
+
},
|
|
440
|
+
async configureApp(app) {
|
|
441
|
+
const w = await import('@acme/widgets')
|
|
442
|
+
w.register(app)
|
|
443
|
+
},
|
|
444
|
+
})
|
|
445
|
+
```
|
|
446
|
+
|
|
447
|
+
### 开发服务器文件系统访问
|
|
448
|
+
|
|
449
|
+
CLI 允许 `server.fs` 读取站点根目录下、已安装的 `vue-site` 包目录、站点根目录的**父目录**
|
|
450
|
+
(用于 `../…` 导入),以及 —— 当不会扩展到文件系统根目录时 —— **祖父目录**(在 monorepo 中常见,
|
|
451
|
+
如 `../../packages/...`)。如有需要,可通过 `env.vite.server.fs.allow` 添加更多路径。
|
|
452
|
+
带 `{ entryPath }` 的 `watchPackages` 条目也会为这些包树扩展 `fs.allow`。
|
|
453
|
+
|
|
454
|
+
## 库模式
|
|
455
|
+
|
|
456
|
+
拥有自己的 `index.html` + Vite 配置:
|
|
457
|
+
|
|
458
|
+
```typescript
|
|
459
|
+
import { createSiteApp } from '@bndynet/vue-site'
|
|
460
|
+
import '@bndynet/vue-site/style.css'
|
|
461
|
+
import config from './site.config'
|
|
462
|
+
|
|
463
|
+
const app = await createSiteApp(config)
|
|
464
|
+
app.mount('#app')
|
|
465
|
+
```
|
|
466
|
+
|
|
467
|
+
在你的入口中使用顶层 `await`(或一个 async IIFE):`createSiteApp` 是异步的,当 `configureApp`
|
|
468
|
+
返回 `Promise` 时会 **await** 它。如果你在配置中设置了可选的 `bootstrap`,该模块会在应用创建前加载;
|
|
469
|
+
如果省略 `bootstrap`,则跳过该步骤。
|
|
470
|
+
|
|
471
|
+
导出:`createSiteApp`、`defineConfig`、`useTheme`、`useSiteConfig`、`useLocale`、`useLocalize`、`tk`、`resolveLocalized`、`resolveField`、`resolveMessage`、`mergeCatalog`、`flattenMessages`、`isMessageRef`、`localizedPage`、`builtinMessages`、`themeRefKey`、`localeRefKey`。类型:`SiteConfig`、`SiteEnvConfig`、`SiteViteConfig`、`SiteExternalLink`、`NavItem`、`StandalonePage`、`AuthRule`、`AuthContext`、`AuthConfig`、`RouterConfig`、`ThemeConfig`、`ThemeOption`、`ThemePaletteVars`、`ResolvedNavItem`、`I18nConfig`、`LocaleOption`、`LocaleCode`、`LocalizedString`、`MessageRef`、`MessageTree`、`MessageCatalog`、`PageLoader`、`LocalizedPageOptions`。
|
|
472
|
+
|
|
473
|
+
### 在 Vue 页面中使用主题(`useTheme`)
|
|
474
|
+
|
|
475
|
+
从 `@bndynet/vue-site` 导入 `useTheme`。它返回一个响应式的 `theme` ref(当前激活的主题 id,
|
|
476
|
+
如 `light`、`dark` 或某个 `extraThemes[].id`),外加 `setTheme` 和 `toggleTheme`。
|
|
477
|
+
根布局还会在 `document.documentElement` 上设置 `data-theme` 属性并应用 CSS 变量,
|
|
478
|
+
因此你可以用 `var(--color-*)` 来设置样式,无需 JavaScript。
|
|
479
|
+
|
|
480
|
+
theme ref 由 `createSiteApp()` **提供**(与你的应用同一个 Vue 运行时),所以 `watch(theme, …)`
|
|
481
|
+
能可靠工作,即使其他工具可能会从嵌套的 `node_modules` 加载第二份 `vue`。
|
|
482
|
+
|
|
483
|
+
```vue
|
|
484
|
+
<script setup lang="ts">
|
|
485
|
+
import { watch } from 'vue'
|
|
486
|
+
import { useTheme } from '@bndynet/vue-site'
|
|
487
|
+
|
|
488
|
+
const { theme, setTheme, toggleTheme } = useTheme()
|
|
489
|
+
|
|
490
|
+
watch(theme, (next, prev) => {
|
|
491
|
+
if (prev !== undefined) console.log('theme:', prev, '→', next)
|
|
492
|
+
})
|
|
493
|
+
</script>
|
|
494
|
+
|
|
495
|
+
<template>
|
|
496
|
+
<p>当前主题:{{ theme }}</p>
|
|
497
|
+
</template>
|
|
498
|
+
```
|
|
499
|
+
|
|
500
|
+
`watch` 仅在你的组件初始化之后主题**发生变化**时运行(例如用户点击主题控件之后)。
|
|
501
|
+
除非你传入 `{ immediate: true }`,否则它不会在初始值上运行。
|
|
502
|
+
|
|
503
|
+
## 升级
|
|
504
|
+
|
|
505
|
+
```bash
|
|
506
|
+
npm update @bndynet/vue-site
|
|
507
|
+
```
|
|
508
|
+
|
|
509
|
+
## 开发本仓库
|
|
510
|
+
|
|
511
|
+
```bash
|
|
512
|
+
git clone https://github.com/bndynet/vue-site.git && cd vue-site
|
|
513
|
+
npm install
|
|
514
|
+
npm run dev # 监听构建 lib + 示例站点
|
|
515
|
+
npm run build # `dist/` + `example/example-dist`
|
|
516
|
+
```
|
|
517
|
+
|
|
518
|
+
### 在其他项目中使用本地构建
|
|
519
|
+
|
|
520
|
+
已发布的入口指向 `dist/`。在更改 `src/` 下的库**源码**后,需在使用方看到更新之前运行
|
|
521
|
+
`npm run build:lib`(或 `build:lib:watch`)。对 **`bin/vue-site.mjs`** 的更改会在下一次运行
|
|
522
|
+
`vue-site` 时生效,无需重新构建 lib。
|
|
523
|
+
|
|
524
|
+
```bash
|
|
525
|
+
cd /path/to/vue-site
|
|
526
|
+
npm install && npm run build:lib
|
|
527
|
+
npm link
|
|
528
|
+
|
|
529
|
+
cd /path/to/consumer
|
|
530
|
+
npm link @bndynet/vue-site
|
|
531
|
+
```
|
|
532
|
+
|
|
533
|
+
编辑文件后无需再次运行 `npm link`;软链接会保持。完成后在使用方运行
|
|
534
|
+
`npm unlink @bndynet/vue-site` 和 `npm install`。
|
|
535
|
+
|
|
536
|
+
## 许可证
|
|
537
|
+
|
|
538
|
+
MIT
|