component-auto-docs 0.1.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/package.json ADDED
@@ -0,0 +1,22 @@
1
+ {
2
+ "name": "component-auto-docs",
3
+ "version": "0.1.0",
4
+ "description": "Component docs automation CLI for uni-app component libraries.",
5
+ "type": "module",
6
+ "bin": {
7
+ "component-auto-docs": "bin/component-auto-docs.mjs"
8
+ },
9
+ "files": [
10
+ "bin",
11
+ "core",
12
+ "templates",
13
+ "README.md",
14
+ "SHARING.md"
15
+ ],
16
+ "peerDependencies": {
17
+ "typescript": ">=5.0.0"
18
+ },
19
+ "engines": {
20
+ "node": ">=18.18.0"
21
+ }
22
+ }
@@ -0,0 +1,110 @@
1
+ # 组件文档接入规范
2
+
3
+ ## 自动化方案
4
+
5
+ 项目组件文档由 `component-auto-docs` 统一生成,并通过根目录 `docs.config.mjs` 配置目录、产物、路由和运行时导入。
6
+
7
+ 生成器会合并两类信息:
8
+
9
+ - 组件源码:自动抽取 `.vue` 中的 `defineProps`、`defineEmits`、slots、JSDoc 和本组件目录下的类型别名。
10
+ - 人工元数据:读取 `src/components/<component-name>/docs.meta.json`,维护中文标题、分类、使用场景、注意事项和高质量示例。
11
+
12
+ 生成产物分为三类:
13
+
14
+ - 人类阅读:`docs/components/*.md`。
15
+ - App 内页面:`src/docs/data.json`、`src/docs/components/*/data.json`、`src/docs/components/*/index.vue`。
16
+ - AI/检索:`docs/ai/components.catalog.json`、`docs/ai/components.index.md`、`docs/ai/llms.txt`、`docs/ai/components.quality.json`。
17
+
18
+ ## 新组件接入
19
+
20
+ 1. 执行 `pnpm docs:init`,自动补齐或升级 `docs.meta.json`、自动交互文档页、文档入口路由和组件文档路由。
21
+ 2. 执行 `pnpm docs:gen`,生成 AI 文档、Markdown 文档、菜单数据和页面数据。
22
+ 3. 执行 `pnpm docs:check`,确认文档质量报告无 error/warning。
23
+
24
+ `docs:init` 生成的组件文档页默认使用 `ComponentDocPage`,会继承统一的 `app-page` 页面壳。`docs:init` 不覆盖自定义交互页;如果某个自动生成页需要更精细的业务示例,可以直接编辑对应 `src/docs/components/<component-name>/index.vue`。自定义文档页应继续使用 `ComponentDocPage`,或直接用 `app-page` 包裹页面内容。
25
+
26
+ ## docs.meta.json 建议字段
27
+
28
+ - `title`:组件中文名。
29
+ - `category`:组件分类。
30
+ - `description`:组件用途概述。
31
+ - `useWhen`:适用场景。
32
+ - `avoidWhen`:不建议使用场景。
33
+ - `related`:相关组件名。
34
+ - `notes`:实现或使用注意事项。
35
+ - `examples`:人工维护的高质量示例。
36
+
37
+ ## JSDoc 约定
38
+
39
+ ```ts
40
+ /**
41
+ * 组件描述
42
+ * @property {String} name 字段说明
43
+ * @event {Function} change 事件说明
44
+ * @example <app-demo v-model="value" />
45
+ */
46
+ ```
47
+
48
+ `@property` 名称应和 `defineProps` 字段一致,`@event` 名称应和 `defineEmits` 字段一致。
49
+
50
+ ## 校验
51
+
52
+ ```bash
53
+ pnpm docs:init
54
+ pnpm docs:gen
55
+ pnpm docs:check
56
+ ```
57
+
58
+ 质量报告输出到 `docs/ai/components.quality.json`。
59
+
60
+ ## 多项目部署
61
+
62
+ 推荐把 `component-auto-docs` 作为开发依赖接入其他 uni-app 项目。跨项目部署时优先修改 `docs.config.mjs`,不要改生成器主体。
63
+
64
+ ```bash
65
+ pnpm add -D component-auto-docs
66
+ pnpm exec component-auto-docs scaffold
67
+ pnpm exec component-auto-docs init
68
+ pnpm exec component-auto-docs gen
69
+ pnpm exec component-auto-docs check
70
+ ```
71
+
72
+ `scaffold` 会复制 `docs.config.mjs`、`src/docs/runtime/*`、`src/docs/index.vue` 和本文档规范,并向 `package.json` 写入 `docs:init`、`docs:gen`、`docs:check`、`docs:dev` 脚本。已有文件默认跳过;如需覆盖模板文件和脚本,可使用:
73
+
74
+ ```bash
75
+ pnpm exec component-auto-docs scaffold --force
76
+ ```
77
+
78
+ 关键配置项:
79
+
80
+ - `componentRoot`:组件目录,例如 `src/components`。
81
+ - `metaFileName`:组件元数据文件名,默认 `docs.meta.json`。
82
+ - `output`:AI 文档、Markdown、页面数据和质量报告输出位置。
83
+ - `routes`:是否自动写入 `pages.json`、文档入口路由、组件文档路由前缀、插入锚点和标题后缀。
84
+ - `runtime`:自动文档页使用的页面壳、hook 和组件导入别名。
85
+ - `quality`:质量检查是否要求 App 内文档页,以及页面壳识别规则。
86
+
87
+ 如果其他项目目录结构不同,可以通过命令指定配置文件:
88
+
89
+ ```bash
90
+ pnpm exec component-auto-docs gen --config ./docs.config.mjs
91
+ ```
92
+
93
+ 也可以通过环境变量指定:
94
+
95
+ ```bash
96
+ DOCS_CONFIG=./docs.config.mjs pnpm docs:gen
97
+ ```
98
+
99
+ 如果项目只需要 Markdown 和 AI catalog,不需要 App 内文档页,可以在配置中关闭路由并放宽页面检查:
100
+
101
+ ```js
102
+ export default {
103
+ routes: {
104
+ enabled: false,
105
+ },
106
+ quality: {
107
+ requireDocPage: false,
108
+ },
109
+ };
110
+ ```
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Component docs automation config.
3
+ *
4
+ * Copy this file into another project and adjust the paths/imports to deploy
5
+ * the same docs generator without changing the component-auto-docs package.
6
+ */
7
+ export default {
8
+ projectName: 'uni-vue3-ts-vite',
9
+ componentRoot: 'src/components',
10
+ metaFileName: 'docs.meta.json',
11
+ output: {
12
+ aiDir: 'docs/ai',
13
+ markdownDir: 'docs/components',
14
+ pageDataRoot: 'src/docs/components',
15
+ indexDataFile: 'src/docs/data.json',
16
+ catalogFile: 'components.catalog.json',
17
+ aiIndexFile: 'components.index.md',
18
+ llmsFile: 'llms.txt',
19
+ qualityFile: 'components.quality.json',
20
+ },
21
+ routes: {
22
+ enabled: true,
23
+ pagesJson: 'src/pages.json',
24
+ indexPath: 'docs/index',
25
+ indexTitle: '组件文档',
26
+ basePath: 'docs/components',
27
+ insertBeforeMarker: ' // -- 示例 demo --',
28
+ titleSuffix: ' 文档',
29
+ },
30
+ runtime: {
31
+ componentDocPageImport: '@/docs/runtime/component-doc-page.vue',
32
+ autoDocHookImport: '@/docs/runtime/use-auto-component-doc',
33
+ componentImportBase: '@/components',
34
+ componentDocPageName: 'ComponentDocPage',
35
+ autoDocHookName: 'useAutoComponentDoc',
36
+ },
37
+ quality: {
38
+ requireDocPage: true,
39
+ shellHints: ['<ComponentDocPage', '<app-page', '<AppPage'],
40
+ },
41
+ };
@@ -0,0 +1,294 @@
1
+ <script setup lang="ts">
2
+ import { computed, ref } from 'vue';
3
+ import docsIndex from './data.json';
4
+
5
+ type MenuComponent = {
6
+ name: string;
7
+ title: string;
8
+ category: string;
9
+ description: string;
10
+ route: string;
11
+ source: string;
12
+ propsCount: number;
13
+ eventsCount: number;
14
+ slotsCount: number;
15
+ };
16
+
17
+ const keyword = ref('');
18
+ const activeCategory = ref('全部');
19
+ const components = docsIndex.components as MenuComponent[];
20
+
21
+ const categories = computed(() => {
22
+ return ['全部', ...new Set(components.map((component) => component.category))];
23
+ });
24
+
25
+ const filteredComponents = computed(() => {
26
+ const text = keyword.value.trim().toLowerCase();
27
+
28
+ return components.filter((component) => {
29
+ const matchCategory = activeCategory.value === '全部' || component.category === activeCategory.value;
30
+ const matchKeyword =
31
+ !text ||
32
+ component.name.toLowerCase().includes(text) ||
33
+ component.title.toLowerCase().includes(text) ||
34
+ component.description.toLowerCase().includes(text);
35
+
36
+ return matchCategory && matchKeyword;
37
+ });
38
+ });
39
+
40
+ const categoryStats = computed(() => {
41
+ return categories.value.map((category) => ({
42
+ name: category,
43
+ count:
44
+ category === '全部'
45
+ ? components.length
46
+ : components.filter((component) => component.category === category).length,
47
+ }));
48
+ });
49
+
50
+ function chooseCategory(category: string) {
51
+ activeCategory.value = category;
52
+ }
53
+
54
+ function openComponent(component: MenuComponent) {
55
+ uni.navigateTo({
56
+ url: component.route,
57
+ });
58
+ }
59
+ </script>
60
+
61
+ <template>
62
+ <app-page title="组件文档" bg-color="#f6f7f9" :show-back="false">
63
+ <view class="docs-page">
64
+ <view class="docs-head">
65
+ <text class="docs-title">组件文档</text>
66
+ <text class="docs-desc">公共组件库 API、示例与交互预览入口</text>
67
+ <view class="docs-stats">
68
+ <text class="stat-pill">{{ components.length }} 个组件</text>
69
+ <text class="stat-pill">{{ categories.length - 1 }} 个分类</text>
70
+ </view>
71
+ </view>
72
+
73
+ <view class="search-card">
74
+ <input v-model="keyword" class="search-input" placeholder="搜索组件名、标题或描述" />
75
+ </view>
76
+
77
+ <view class="category-list">
78
+ <text
79
+ v-for="category in categoryStats"
80
+ :key="category.name"
81
+ class="category-tag"
82
+ :class="{ active: activeCategory === category.name }"
83
+ @click="chooseCategory(category.name)"
84
+ >
85
+ {{ category.name }} · {{ category.count }}
86
+ </text>
87
+ </view>
88
+
89
+ <view class="component-list">
90
+ <view
91
+ v-for="component in filteredComponents"
92
+ :key="component.name"
93
+ class="component-card"
94
+ @click="openComponent(component)"
95
+ >
96
+ <view class="card-main">
97
+ <view class="card-title-row">
98
+ <text class="component-name">{{ component.name }}</text>
99
+ <text class="component-category">{{ component.category }}</text>
100
+ </view>
101
+ <text class="component-title">{{ component.title }}</text>
102
+ <text class="component-desc">{{ component.description }}</text>
103
+ <view class="meta-list">
104
+ <text class="meta-pill">props {{ component.propsCount }}</text>
105
+ <text class="meta-pill">events {{ component.eventsCount }}</text>
106
+ <text class="meta-pill">slots {{ component.slotsCount }}</text>
107
+ </view>
108
+ </view>
109
+ <text class="arrow">›</text>
110
+ </view>
111
+
112
+ <view v-if="!filteredComponents.length" class="empty-card">
113
+ <text class="empty-title">没有匹配的组件</text>
114
+ <text class="empty-desc">换个关键词或分类试试。</text>
115
+ </view>
116
+ </view>
117
+ </view>
118
+ </app-page>
119
+ </template>
120
+
121
+ <style lang="scss">
122
+ page {
123
+ overflow-x: hidden;
124
+ background: #f6f7f9;
125
+ }
126
+
127
+ .docs-page {
128
+ box-sizing: border-box;
129
+ width: 100%;
130
+ min-height: 100vh;
131
+ padding: 20rpx;
132
+ overflow-x: hidden;
133
+ color: #111827;
134
+ }
135
+
136
+ .docs-head,
137
+ .search-card,
138
+ .component-card,
139
+ .empty-card {
140
+ box-sizing: border-box;
141
+ width: 100%;
142
+ background: #fff;
143
+ border: 1rpx solid #dfe5ee;
144
+ border-radius: 8rpx;
145
+ }
146
+
147
+ .docs-head {
148
+ display: flex;
149
+ flex-direction: column;
150
+ gap: 12rpx;
151
+ padding: 24rpx 22rpx;
152
+ }
153
+
154
+ .docs-title {
155
+ font-size: 38rpx;
156
+ font-weight: 700;
157
+ line-height: 1.25;
158
+ }
159
+
160
+ .docs-desc {
161
+ font-size: 26rpx;
162
+ line-height: 1.55;
163
+ color: #475569;
164
+ overflow-wrap: anywhere;
165
+ }
166
+
167
+ .docs-stats,
168
+ .category-list,
169
+ .meta-list {
170
+ display: flex;
171
+ flex-wrap: wrap;
172
+ gap: 8rpx;
173
+ }
174
+
175
+ .stat-pill,
176
+ .category-tag,
177
+ .meta-pill,
178
+ .component-category {
179
+ max-width: 100%;
180
+ padding: 7rpx 12rpx;
181
+ font-size: 22rpx;
182
+ line-height: 1.35;
183
+ color: #334155;
184
+ overflow-wrap: anywhere;
185
+ background: #f8fafc;
186
+ border: 1rpx solid #e2e8f0;
187
+ border-radius: 8rpx;
188
+ }
189
+
190
+ .search-card {
191
+ margin-top: 16rpx;
192
+ padding: 14rpx 16rpx;
193
+ }
194
+
195
+ .search-input {
196
+ width: 100%;
197
+ height: 56rpx;
198
+ font-size: 26rpx;
199
+ line-height: 56rpx;
200
+ }
201
+
202
+ .category-list {
203
+ margin-top: 16rpx;
204
+ }
205
+
206
+ .category-tag.active {
207
+ color: #9a3412;
208
+ background: #fff7ed;
209
+ border-color: #fed7aa;
210
+ }
211
+
212
+ .component-list {
213
+ display: flex;
214
+ flex-direction: column;
215
+ gap: 14rpx;
216
+ margin-top: 16rpx;
217
+ }
218
+
219
+ .component-card {
220
+ display: flex;
221
+ gap: 16rpx;
222
+ align-items: center;
223
+ padding: 18rpx;
224
+ box-shadow: 0 4rpx 14rpx rgb(15 23 42 / 3%);
225
+ }
226
+
227
+ .card-main {
228
+ display: flex;
229
+ flex: 1;
230
+ flex-direction: column;
231
+ gap: 10rpx;
232
+ min-width: 0;
233
+ }
234
+
235
+ .card-title-row {
236
+ display: flex;
237
+ flex-wrap: wrap;
238
+ gap: 10rpx;
239
+ align-items: center;
240
+ justify-content: space-between;
241
+ }
242
+
243
+ .component-name {
244
+ min-width: 0;
245
+ font-family: Menlo, Monaco, Consolas, monospace;
246
+ font-size: 30rpx;
247
+ font-weight: 600;
248
+ line-height: 1.35;
249
+ overflow-wrap: anywhere;
250
+ }
251
+
252
+ .component-category {
253
+ color: #1d4ed8;
254
+ background: #eff6ff;
255
+ border-color: #bfdbfe;
256
+ }
257
+
258
+ .component-title {
259
+ font-size: 26rpx;
260
+ font-weight: 600;
261
+ line-height: 1.4;
262
+ }
263
+
264
+ .component-desc,
265
+ .empty-desc {
266
+ font-size: 24rpx;
267
+ line-height: 1.55;
268
+ color: #475569;
269
+ overflow-wrap: anywhere;
270
+ }
271
+
272
+ .meta-pill {
273
+ font-family: Menlo, Monaco, Consolas, monospace;
274
+ }
275
+
276
+ .arrow {
277
+ flex: 0 0 auto;
278
+ font-size: 42rpx;
279
+ line-height: 1;
280
+ color: #94a3b8;
281
+ }
282
+
283
+ .empty-card {
284
+ display: flex;
285
+ flex-direction: column;
286
+ gap: 8rpx;
287
+ padding: 28rpx 20rpx;
288
+ }
289
+
290
+ .empty-title {
291
+ font-size: 28rpx;
292
+ font-weight: 700;
293
+ }
294
+ </style>