component-auto-docs 0.1.1 → 0.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 +127 -34
- package/bin/component-auto-docs.mjs +0 -0
- package/core/generate-component-docs.mjs +658 -86
- package/package.json +2 -1
- package/templates/docs/components/README.md +2 -1
- package/templates/docs.config.mjs +1 -0
- package/templates/src/docs/index.vue +1 -1
- package/templates/src/docs/runtime/component-doc-page.vue +166 -71
- package/templates/src/docs/runtime/use-auto-component-doc.ts +87 -44
- package/SHARING.md +0 -435
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { pathToFileURL } from 'node:url';
|
|
4
|
+
import { parse as parseSfc } from '@vue/compiler-sfc';
|
|
4
5
|
import ts from 'typescript';
|
|
5
6
|
|
|
6
7
|
const rootDir = process.cwd();
|
|
@@ -19,6 +20,7 @@ const defaultDocsConfig = {
|
|
|
19
20
|
markdownDir: 'docs/components',
|
|
20
21
|
pageDataRoot: 'src/docs/components',
|
|
21
22
|
indexDataFile: 'src/docs/data.json',
|
|
23
|
+
pageDataModuleFileName: 'data.js',
|
|
22
24
|
catalogFile: 'components.catalog.json',
|
|
23
25
|
aiIndexFile: 'components.index.md',
|
|
24
26
|
llmsFile: 'llms.txt',
|
|
@@ -107,7 +109,7 @@ function createPageRouteEntry(routePath, title) {
|
|
|
107
109
|
"style": {
|
|
108
110
|
"navigationBarTitleText": "${title}"
|
|
109
111
|
}
|
|
110
|
-
|
|
112
|
+
},
|
|
111
113
|
`;
|
|
112
114
|
}
|
|
113
115
|
|
|
@@ -157,6 +159,57 @@ function writeText(filePath, content) {
|
|
|
157
159
|
fs.writeFileSync(filePath, `${content.trimEnd()}\n`);
|
|
158
160
|
}
|
|
159
161
|
|
|
162
|
+
function hashText(text) {
|
|
163
|
+
let hash = 5381;
|
|
164
|
+
|
|
165
|
+
for (const char of String(text)) {
|
|
166
|
+
hash = (hash * 33) ^ char.codePointAt(0);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return (hash >>> 0).toString(36);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function toDataModuleIdentifier(name, fallback) {
|
|
173
|
+
const words = String(name).match(/[A-Za-z0-9]+/g) || [];
|
|
174
|
+
const base = words
|
|
175
|
+
.map((word, index) => {
|
|
176
|
+
const lower = word.toLowerCase();
|
|
177
|
+
return index === 0 ? lower : lower.charAt(0).toUpperCase() + lower.slice(1);
|
|
178
|
+
})
|
|
179
|
+
.join('');
|
|
180
|
+
const identifierBase = base && /^[A-Za-z_$]/.test(base) ? base : fallback;
|
|
181
|
+
|
|
182
|
+
return `${identifierBase}Data_${hashText(name)}`;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function serializeJsValue(value) {
|
|
186
|
+
return JSON.stringify(value, null, 2)
|
|
187
|
+
.replace(/\u2028/g, '\\u2028')
|
|
188
|
+
.replace(/\u2029/g, '\\u2029');
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function renderDataModule(data, identifier) {
|
|
192
|
+
return `// @generated by ${generatedBy}. Keep this file in sync by running docs:gen.
|
|
193
|
+
const ${identifier} = ${serializeJsValue(data)};
|
|
194
|
+
|
|
195
|
+
export default ${identifier};
|
|
196
|
+
`;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function writeDataModule(filePath, data, identifier) {
|
|
200
|
+
writeText(filePath, renderDataModule(data, identifier));
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function getIndexDataModuleFile() {
|
|
204
|
+
const parsed = path.parse(indexDataFile);
|
|
205
|
+
|
|
206
|
+
return path.join(parsed.dir, `${parsed.name}.js`);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function getPageDataModuleFileName() {
|
|
210
|
+
return docsConfig.output.pageDataModuleFileName || 'data.js';
|
|
211
|
+
}
|
|
212
|
+
|
|
160
213
|
function normalizeTypeText(typeText) {
|
|
161
214
|
return typeText
|
|
162
215
|
.replace(/\s+/g, ' ')
|
|
@@ -280,12 +333,29 @@ function readLiteralValue(node, sourceFile) {
|
|
|
280
333
|
return node.getText(sourceFile);
|
|
281
334
|
}
|
|
282
335
|
|
|
283
|
-
function
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
336
|
+
function formatSfcParseError(error) {
|
|
337
|
+
if (typeof error === 'string') return error;
|
|
338
|
+
if (error?.message) return error.message;
|
|
339
|
+
|
|
340
|
+
return String(error);
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
function readSfcBlocks(filePath) {
|
|
344
|
+
const source = fs.readFileSync(filePath, 'utf8');
|
|
345
|
+
const { descriptor, errors } = parseSfc(source, { filename: filePath });
|
|
346
|
+
|
|
347
|
+
if (errors.length) {
|
|
348
|
+
throw new Error(
|
|
349
|
+
`Failed to parse Vue SFC ${toPosix(path.relative(rootDir, filePath))}: ${errors
|
|
350
|
+
.map(formatSfcParseError)
|
|
351
|
+
.join('; ')}`,
|
|
352
|
+
);
|
|
353
|
+
}
|
|
287
354
|
|
|
288
|
-
return
|
|
355
|
+
return {
|
|
356
|
+
script: descriptor.scriptSetup?.content || descriptor.script?.content || '',
|
|
357
|
+
template: descriptor.template?.content || '',
|
|
358
|
+
};
|
|
289
359
|
}
|
|
290
360
|
|
|
291
361
|
function parseJsdoc(script) {
|
|
@@ -675,6 +745,7 @@ function normalizeExamples(metaExamples = [], jsdocExamples = []) {
|
|
|
675
745
|
const normalized = typeof example === 'string' ? { title: '示例', description: '', code: example } : example;
|
|
676
746
|
const code = normalized.code?.trim();
|
|
677
747
|
if (!code || seen.has(code)) continue;
|
|
748
|
+
if (normalized.title === '基础用法' && normalized.description?.startsWith('自动根据 ')) continue;
|
|
678
749
|
|
|
679
750
|
seen.add(code);
|
|
680
751
|
examples.push({
|
|
@@ -687,6 +758,525 @@ function normalizeExamples(metaExamples = [], jsdocExamples = []) {
|
|
|
687
758
|
return examples;
|
|
688
759
|
}
|
|
689
760
|
|
|
761
|
+
function escapeRegExp(text) {
|
|
762
|
+
return text.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
function kebabToCamel(text) {
|
|
766
|
+
return text.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase());
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
function decodeHtmlText(text) {
|
|
770
|
+
return text
|
|
771
|
+
.replace(/</g, '<')
|
|
772
|
+
.replace(/>/g, '>')
|
|
773
|
+
.replace(/"/g, '"')
|
|
774
|
+
.replace(/'/g, "'")
|
|
775
|
+
.replace(/&/g, '&');
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
function getObjectKeyText(nameNode, sourceFile) {
|
|
779
|
+
if (!nameNode) return '';
|
|
780
|
+
if (ts.isIdentifier(nameNode) || ts.isStringLiteralLike(nameNode) || ts.isNumericLiteral(nameNode)) {
|
|
781
|
+
return nameNode.text;
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
return nameNode.getText(sourceFile).replace(/^['"]|['"]$/g, '');
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
function getIdentifierSampleValue(name) {
|
|
788
|
+
const samples = {
|
|
789
|
+
text: '这是一段足够长的公告文字,用于触发滚动预览效果',
|
|
790
|
+
title: '标题',
|
|
791
|
+
content: '内容',
|
|
792
|
+
label: '选项',
|
|
793
|
+
value: '1',
|
|
794
|
+
name: '名称',
|
|
795
|
+
status: 'done',
|
|
796
|
+
time: '10:30',
|
|
797
|
+
src: '/static/icon/avatar.svg',
|
|
798
|
+
url: '/static/icon/avatar.svg',
|
|
799
|
+
poster: '/static/icon/avatar.svg',
|
|
800
|
+
};
|
|
801
|
+
|
|
802
|
+
return samples[name] || name;
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
function evaluateStaticNode(node, sourceFile, bindings = {}, options = {}) {
|
|
806
|
+
if (!node) return undefined;
|
|
807
|
+
|
|
808
|
+
if (ts.isParenthesizedExpression(node) || ts.isAsExpression(node) || ts.isTypeAssertionExpression(node)) {
|
|
809
|
+
return evaluateStaticNode(node.expression, sourceFile, bindings, options);
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
if (ts.isStringLiteralLike(node) || ts.isNoSubstitutionTemplateLiteral(node)) return decodeHtmlText(node.text);
|
|
813
|
+
if (ts.isNumericLiteral(node)) return Number(node.text);
|
|
814
|
+
if (node.kind === ts.SyntaxKind.TrueKeyword) return true;
|
|
815
|
+
if (node.kind === ts.SyntaxKind.FalseKeyword) return false;
|
|
816
|
+
if (node.kind === ts.SyntaxKind.NullKeyword) return null;
|
|
817
|
+
|
|
818
|
+
if (ts.isIdentifier(node)) {
|
|
819
|
+
if (Object.prototype.hasOwnProperty.call(bindings, node.text)) return bindings[node.text];
|
|
820
|
+
|
|
821
|
+
return options.allowIdentifierFallback ? getIdentifierSampleValue(node.text) : undefined;
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
if (ts.isArrayLiteralExpression(node)) {
|
|
825
|
+
return node.elements
|
|
826
|
+
.map((item) => evaluateStaticNode(item, sourceFile, bindings, { ...options, allowIdentifierFallback: true }))
|
|
827
|
+
.filter((item) => item !== undefined);
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
if (ts.isObjectLiteralExpression(node)) {
|
|
831
|
+
const result = {};
|
|
832
|
+
|
|
833
|
+
for (const property of node.properties) {
|
|
834
|
+
if (ts.isPropertyAssignment(property)) {
|
|
835
|
+
const key = getObjectKeyText(property.name, sourceFile);
|
|
836
|
+
if (!key) continue;
|
|
837
|
+
const value = evaluateStaticNode(property.initializer, sourceFile, bindings, {
|
|
838
|
+
...options,
|
|
839
|
+
allowIdentifierFallback: true,
|
|
840
|
+
});
|
|
841
|
+
if (value !== undefined) result[key] = value;
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
if (ts.isShorthandPropertyAssignment(property)) {
|
|
845
|
+
const key = property.name.text;
|
|
846
|
+
result[key] = Object.prototype.hasOwnProperty.call(bindings, key) ? bindings[key] : getIdentifierSampleValue(key);
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
return result;
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
return undefined;
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
function evaluateStaticExpression(expression, bindings = {}) {
|
|
857
|
+
const source = `const __value = (${expression});`;
|
|
858
|
+
const sourceFile = ts.createSourceFile('preview-expression.ts', source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
|
|
859
|
+
const statement = sourceFile.statements[0];
|
|
860
|
+
const declaration = statement?.declarationList?.declarations?.[0];
|
|
861
|
+
|
|
862
|
+
return evaluateStaticNode(declaration?.initializer, sourceFile, bindings, { allowIdentifierFallback: false });
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
function parseExampleBindings(code) {
|
|
866
|
+
const bindings = {};
|
|
867
|
+
const declarationPattern = /(?:const|let|var)\s+([\w$\u00a0-\uffff]+)\s*=\s*([^;\n]+)/g;
|
|
868
|
+
let match;
|
|
869
|
+
|
|
870
|
+
while ((match = declarationPattern.exec(code))) {
|
|
871
|
+
const [, name, expression] = match;
|
|
872
|
+
const value = evaluateStaticExpression(expression, bindings);
|
|
873
|
+
if (value !== undefined) bindings[name] = value;
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
return bindings;
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
function parseExampleExpression(expression, bindings = {}) {
|
|
880
|
+
const value = expression.trim();
|
|
881
|
+
if (!value) return undefined;
|
|
882
|
+
if (/^-?\d+(?:\.\d+)?$/.test(value)) return Number(value);
|
|
883
|
+
if (value === 'true') return true;
|
|
884
|
+
if (value === 'false') return false;
|
|
885
|
+
if (value === 'null') return null;
|
|
886
|
+
|
|
887
|
+
const quoted = value.match(/^(['"])([\s\S]*)\1$/);
|
|
888
|
+
if (quoted) return decodeHtmlText(quoted[2]);
|
|
889
|
+
|
|
890
|
+
if (value.startsWith('{') || value.startsWith('[')) {
|
|
891
|
+
try {
|
|
892
|
+
return JSON.parse(value);
|
|
893
|
+
} catch {
|
|
894
|
+
// Fall through to the TypeScript AST evaluator for JS-style literals.
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
return evaluateStaticExpression(value, bindings);
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
function coerceStaticExampleValue(rawValue, prop) {
|
|
902
|
+
if (rawValue === undefined) return true;
|
|
903
|
+
|
|
904
|
+
const type = prop?.type || '';
|
|
905
|
+
const value = decodeHtmlText(rawValue);
|
|
906
|
+
if (type.includes('array') || type.includes('Array') || type.includes('object') || type.includes('Object')) {
|
|
907
|
+
const parsed = parseExampleExpression(value);
|
|
908
|
+
if (parsed !== undefined) return parsed;
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
if (type.includes('boolean')) return value === 'true';
|
|
912
|
+
if (type.includes('number') || type.includes('Number')) {
|
|
913
|
+
const numberValue = Number(value);
|
|
914
|
+
return Number.isFinite(numberValue) ? numberValue : value;
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
return value;
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
function normalizeSlotContent(content) {
|
|
921
|
+
return decodeHtmlText(content)
|
|
922
|
+
.replace(/<[^>]+>/g, '')
|
|
923
|
+
.replace(/\s+/g, ' ')
|
|
924
|
+
.trim();
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
const previewKinds = new Set([
|
|
928
|
+
'inline',
|
|
929
|
+
'form',
|
|
930
|
+
'data-list',
|
|
931
|
+
'measure',
|
|
932
|
+
'overlay',
|
|
933
|
+
'page-shell',
|
|
934
|
+
'native',
|
|
935
|
+
'composite',
|
|
936
|
+
]);
|
|
937
|
+
|
|
938
|
+
const commentPreviewKindRules = [
|
|
939
|
+
{
|
|
940
|
+
kind: 'page-shell',
|
|
941
|
+
keywords: ['页面根', '页面容器', '页面壳', '页面布局', '标题栏', '导航栏', '状态栏', '安全区', 'safearea', 'status bar'],
|
|
942
|
+
},
|
|
943
|
+
{
|
|
944
|
+
kind: 'measure',
|
|
945
|
+
keywords: ['跑马灯', '内容超出', '超出时', '横向滚动', '循环滚动', '滚动到', '吸顶', '尺寸测量', '测量', 'selectorquery'],
|
|
946
|
+
},
|
|
947
|
+
{
|
|
948
|
+
kind: 'overlay',
|
|
949
|
+
keywords: ['弹窗', '弹层', '浮层', '遮罩', 'popover', 'popup', 'dialog', 'modal'],
|
|
950
|
+
},
|
|
951
|
+
{
|
|
952
|
+
kind: 'native',
|
|
953
|
+
keywords: ['上传', '图片预览', '预览图片', '二维码', 'qrcode', 'canvas', 'video', '视频', '拨打', '联系', '原生能力', '相册'],
|
|
954
|
+
},
|
|
955
|
+
{
|
|
956
|
+
kind: 'composite',
|
|
957
|
+
keywords: ['装修', '设计页', '楼层', '复合', '业务组件', '复杂业务'],
|
|
958
|
+
},
|
|
959
|
+
{
|
|
960
|
+
kind: 'data-list',
|
|
961
|
+
keywords: ['列表', '数据列表', '树形', '树节点', '时间线', '步骤', '瀑布流', 'tabs', '选项卡'],
|
|
962
|
+
},
|
|
963
|
+
{
|
|
964
|
+
kind: 'form',
|
|
965
|
+
keywords: ['输入', '表单', '选择', '选择器', '勾选', '单选', '多选', '开关', '验证码', 'textarea', 'input', 'radio', 'checkbox', 'switch'],
|
|
966
|
+
},
|
|
967
|
+
];
|
|
968
|
+
|
|
969
|
+
function normalizePreviewKind(kind) {
|
|
970
|
+
return previewKinds.has(kind) ? kind : '';
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
function textIncludesAny(text, keywords) {
|
|
974
|
+
const normalizedText = text.toLowerCase();
|
|
975
|
+
|
|
976
|
+
return keywords.some((keyword) => normalizedText.includes(keyword.toLowerCase()));
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
function inferPreviewKindFromText(text) {
|
|
980
|
+
if (!text) return '';
|
|
981
|
+
|
|
982
|
+
for (const rule of commentPreviewKindRules) {
|
|
983
|
+
if (textIncludesAny(text, rule.keywords)) return rule.kind;
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
return '';
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
function inferPreviewKindFromStructure(componentName, props, events, slots) {
|
|
990
|
+
const lowerName = componentName.toLowerCase();
|
|
991
|
+
const propNames = new Set(props.map((prop) => prop.name));
|
|
992
|
+
const hasModel = propNames.has('modelValue');
|
|
993
|
+
const hasListData = props.some((prop) => ['items', 'list', 'range', 'tabs', 'arr', 'data', 'options'].includes(prop.name));
|
|
994
|
+
const hasUploadLike = /upload|qrcode|video|contact/.test(lowerName);
|
|
995
|
+
const hasPageShellName = /page|header|footer|safearea|status-bar/.test(lowerName);
|
|
996
|
+
const hasOverlayName = /dialog|pop|popover|modal|popup/.test(lowerName);
|
|
997
|
+
const hasMeasureName = /marquee|tabs-scroll|swiper/.test(lowerName);
|
|
998
|
+
const hasDataName = /list|tree|timeline|steps|tabs/.test(lowerName);
|
|
999
|
+
|
|
1000
|
+
if (hasPageShellName) return 'page-shell';
|
|
1001
|
+
if (hasMeasureName) return 'measure';
|
|
1002
|
+
if (hasOverlayName) return 'overlay';
|
|
1003
|
+
if (hasUploadLike) return 'native';
|
|
1004
|
+
if (/design|calculate|citys/.test(lowerName)) return 'composite';
|
|
1005
|
+
if (hasDataName || hasListData) return 'data-list';
|
|
1006
|
+
if (hasModel || /input|textarea|radio|checkbox|switch|slider|sms|search/.test(lowerName)) return 'form';
|
|
1007
|
+
if (events.some((event) => event.name === 'click') || slots.length) return 'inline';
|
|
1008
|
+
|
|
1009
|
+
return 'inline';
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
function inferPreviewKind(componentName, props, events, slots, jsdoc, metaPreview) {
|
|
1013
|
+
const explicitKind = normalizePreviewKind(metaPreview?.kind);
|
|
1014
|
+
if (explicitKind) return explicitKind;
|
|
1015
|
+
|
|
1016
|
+
const jsdocKind = inferPreviewKindFromText(jsdoc.description);
|
|
1017
|
+
if (jsdocKind) return jsdocKind;
|
|
1018
|
+
|
|
1019
|
+
return inferPreviewKindFromStructure(componentName, props, events, slots);
|
|
1020
|
+
}
|
|
1021
|
+
|
|
1022
|
+
function isBooleanType(type = '') {
|
|
1023
|
+
return type.toLowerCase().includes('boolean');
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1026
|
+
function isNumberType(type = '') {
|
|
1027
|
+
return type.includes('number') || type.includes('Number');
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1030
|
+
function isArrayType(type = '') {
|
|
1031
|
+
return type.includes('array') || type.includes('Array') || type.endsWith('[]');
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1034
|
+
function isObjectType(type = '') {
|
|
1035
|
+
return type.includes('object') || type.includes('Object') || /\{[\s\S]*\}/.test(type);
|
|
1036
|
+
}
|
|
1037
|
+
|
|
1038
|
+
function getPropByName(props, name) {
|
|
1039
|
+
return props.find((prop) => prop.name === name);
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
function getFirstOptionValue(previewProps) {
|
|
1043
|
+
const options = previewProps.items || previewProps.options || previewProps.range || previewProps.tabs || previewProps.list;
|
|
1044
|
+
if (!Array.isArray(options) || !options.length) return undefined;
|
|
1045
|
+
|
|
1046
|
+
const first = options[0];
|
|
1047
|
+
if (first && typeof first === 'object') return first.value ?? first.id ?? first.key ?? first.name ?? first.label;
|
|
1048
|
+
|
|
1049
|
+
return first;
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
function createListMock(componentName, propName) {
|
|
1053
|
+
if (/timeline/.test(componentName)) {
|
|
1054
|
+
return [
|
|
1055
|
+
{ label: '提交订单', time: '10:00' },
|
|
1056
|
+
{ label: '商家确认', time: '10:30' },
|
|
1057
|
+
];
|
|
1058
|
+
}
|
|
1059
|
+
|
|
1060
|
+
if (/steps/.test(componentName)) {
|
|
1061
|
+
return [
|
|
1062
|
+
{ name: '提交订单', status: 'done' },
|
|
1063
|
+
{ name: '支付成功', status: 'active' },
|
|
1064
|
+
{ name: '等待发货', status: 'todo' },
|
|
1065
|
+
];
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1068
|
+
if (/tree/.test(componentName)) {
|
|
1069
|
+
return [
|
|
1070
|
+
{
|
|
1071
|
+
label: '一级节点',
|
|
1072
|
+
value: '1',
|
|
1073
|
+
children: [{ label: '二级节点', value: '1-1' }],
|
|
1074
|
+
},
|
|
1075
|
+
];
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
if (/radar/.test(componentName)) {
|
|
1079
|
+
return [
|
|
1080
|
+
{ n: '服务', p: 90 },
|
|
1081
|
+
{ n: '物流', p: 80 },
|
|
1082
|
+
{ n: '质量', p: 88 },
|
|
1083
|
+
];
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1086
|
+
if (propName === 'tabs') return ['精选', '最新', '销量'];
|
|
1087
|
+
if (propName === 'range') return ['男', '女', '保密'];
|
|
1088
|
+
if (propName === 'arr') {
|
|
1089
|
+
return [
|
|
1090
|
+
{ src: '/static/icon/avatar.svg', title: '示例图片' },
|
|
1091
|
+
{ src: '/static/icon/tab-home.png', title: '示例图片' },
|
|
1092
|
+
];
|
|
1093
|
+
}
|
|
1094
|
+
|
|
1095
|
+
return [
|
|
1096
|
+
{ label: '选项一', value: '1' },
|
|
1097
|
+
{ label: '选项二', value: '2' },
|
|
1098
|
+
];
|
|
1099
|
+
}
|
|
1100
|
+
|
|
1101
|
+
function createObjectMock(propName) {
|
|
1102
|
+
if (propName === 'value') return { label: '示例', value: '1' };
|
|
1103
|
+
|
|
1104
|
+
return { label: '示例', value: '1' };
|
|
1105
|
+
}
|
|
1106
|
+
|
|
1107
|
+
function createNamedPropMock(componentName, prop, kind, previewProps) {
|
|
1108
|
+
const { name, type } = prop;
|
|
1109
|
+
|
|
1110
|
+
if (name === 'modelValue') {
|
|
1111
|
+
const firstOptionValue = getFirstOptionValue(previewProps);
|
|
1112
|
+
if (firstOptionValue !== undefined) return firstOptionValue;
|
|
1113
|
+
if (kind === 'overlay' && isBooleanType(type)) return false;
|
|
1114
|
+
if (isBooleanType(type)) return true;
|
|
1115
|
+
if (isNumberType(type)) return 1;
|
|
1116
|
+
|
|
1117
|
+
return '1';
|
|
1118
|
+
}
|
|
1119
|
+
|
|
1120
|
+
if (name === 'checked') return true;
|
|
1121
|
+
if (name === 'active') return 0;
|
|
1122
|
+
if (name === 'percent') return 60;
|
|
1123
|
+
if (name === 'num' || name === 'count') return 3;
|
|
1124
|
+
if (name === 'phone') return '13800138000';
|
|
1125
|
+
if (name === 'text') {
|
|
1126
|
+
return kind === 'measure' || /marquee/.test(componentName)
|
|
1127
|
+
? '这是一段足够长的公告文字,用于触发跑马灯滚动预览效果'
|
|
1128
|
+
: '示例文本';
|
|
1129
|
+
}
|
|
1130
|
+
if (name === 'title') return '标题';
|
|
1131
|
+
if (name === 'placeholder') return '请选择';
|
|
1132
|
+
if (name === 'src' || name === 'poster' || name === 'icon') return '/static/icon/avatar.svg';
|
|
1133
|
+
if (name === 'code') return 'preview-code';
|
|
1134
|
+
if (name === 'label') return 'label';
|
|
1135
|
+
if (name === 'time') return 'time';
|
|
1136
|
+
if (name === 'valueKey') return 'value';
|
|
1137
|
+
if (name === 'labelKey') return 'label';
|
|
1138
|
+
|
|
1139
|
+
if (['items', 'list', 'range', 'tabs', 'arr', 'options'].includes(name) || isArrayType(type)) {
|
|
1140
|
+
return createListMock(componentName, name);
|
|
1141
|
+
}
|
|
1142
|
+
|
|
1143
|
+
if (isObjectType(type)) return createObjectMock(name);
|
|
1144
|
+
if (isBooleanType(type)) return false;
|
|
1145
|
+
if (isNumberType(type)) return 1;
|
|
1146
|
+
|
|
1147
|
+
return '';
|
|
1148
|
+
}
|
|
1149
|
+
|
|
1150
|
+
function shouldAutoMockProp(prop, kind) {
|
|
1151
|
+
if (kind === 'overlay') return ['modelValue', 'title'].includes(prop.name);
|
|
1152
|
+
if (kind === 'measure') return ['text', 'tabs', 'arr', 'list'].includes(prop.name);
|
|
1153
|
+
if (kind === 'data-list') return ['items', 'list', 'range', 'tabs', 'arr', 'options', 'active', 'modelValue'].includes(prop.name);
|
|
1154
|
+
if (kind === 'form') return ['items', 'range', 'options', 'tabs', 'modelValue'].includes(prop.name);
|
|
1155
|
+
|
|
1156
|
+
return false;
|
|
1157
|
+
}
|
|
1158
|
+
|
|
1159
|
+
function applyPreviewMocks(componentName, props, preview, kind) {
|
|
1160
|
+
const previewProps = { ...(preview.props || {}) };
|
|
1161
|
+
|
|
1162
|
+
for (const prop of props) {
|
|
1163
|
+
if (Object.prototype.hasOwnProperty.call(previewProps, prop.name)) continue;
|
|
1164
|
+
if (!shouldAutoMockProp(prop, kind)) continue;
|
|
1165
|
+
|
|
1166
|
+
const value = createNamedPropMock(componentName, prop, kind, previewProps);
|
|
1167
|
+
if (value !== '' && value !== undefined) previewProps[prop.name] = value;
|
|
1168
|
+
}
|
|
1169
|
+
|
|
1170
|
+
if (kind === 'overlay') {
|
|
1171
|
+
const modelProp = getPropByName(props, 'modelValue');
|
|
1172
|
+
if (modelProp && isBooleanType(modelProp.type)) previewProps.modelValue = false;
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1175
|
+
const previewSlots = { ...(preview.slots || {}) };
|
|
1176
|
+
if (!previewSlots.default && kind === 'overlay') previewSlots.default = '这里是弹层内容';
|
|
1177
|
+
if (!previewSlots.default && props.length === 0) previewSlots.default = '内容';
|
|
1178
|
+
|
|
1179
|
+
return {
|
|
1180
|
+
...preview,
|
|
1181
|
+
props: previewProps,
|
|
1182
|
+
slots: previewSlots,
|
|
1183
|
+
};
|
|
1184
|
+
}
|
|
1185
|
+
|
|
1186
|
+
function inferPreviewFromExamples(componentName, props, examples, kind) {
|
|
1187
|
+
const escapedName = escapeRegExp(componentName);
|
|
1188
|
+
const propMap = new Map(props.map((prop) => [prop.name.toLowerCase(), prop]));
|
|
1189
|
+
|
|
1190
|
+
for (const example of examples) {
|
|
1191
|
+
const code = example.code || '';
|
|
1192
|
+
const bindings = parseExampleBindings(code);
|
|
1193
|
+
const closedMatch = code.match(new RegExp(`<${escapedName}\\b([^>]*)>([\\s\\S]*?)<\\/${escapedName}>`, 'i'));
|
|
1194
|
+
const selfClosingMatch = closedMatch ? null : code.match(new RegExp(`<${escapedName}\\b([^>]*)\\/\\s*>`, 'i'));
|
|
1195
|
+
const match = closedMatch || selfClosingMatch;
|
|
1196
|
+
if (!match) continue;
|
|
1197
|
+
|
|
1198
|
+
const attrs = match[1] || '';
|
|
1199
|
+
const propsFromExample = {};
|
|
1200
|
+
const slots = {};
|
|
1201
|
+
const preview = {
|
|
1202
|
+
source: 'example',
|
|
1203
|
+
code: example.code,
|
|
1204
|
+
props: propsFromExample,
|
|
1205
|
+
slots,
|
|
1206
|
+
};
|
|
1207
|
+
const attrPattern = /([:@]?[\w:$-]+|v-model(?::[\w-]+)?)(?:\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s>]+)))?/g;
|
|
1208
|
+
let attrMatch;
|
|
1209
|
+
|
|
1210
|
+
while ((attrMatch = attrPattern.exec(attrs))) {
|
|
1211
|
+
const rawName = attrMatch[1];
|
|
1212
|
+
const rawValue = attrMatch[2] ?? attrMatch[3] ?? attrMatch[4];
|
|
1213
|
+
if (!rawName || rawName.startsWith('@')) continue;
|
|
1214
|
+
|
|
1215
|
+
if (rawName.startsWith('v-model')) {
|
|
1216
|
+
const [, modelName] = rawName.split(':');
|
|
1217
|
+
const propName = modelName ? kebabToCamel(modelName) : 'modelValue';
|
|
1218
|
+
const prop = propMap.get(propName.toLowerCase());
|
|
1219
|
+
preview.vModel = {
|
|
1220
|
+
prop: propName,
|
|
1221
|
+
variable: rawValue || 'value',
|
|
1222
|
+
};
|
|
1223
|
+
if (prop?.default !== null && prop?.default !== undefined && prop.default !== '') {
|
|
1224
|
+
propsFromExample[propName] = prop.default;
|
|
1225
|
+
}
|
|
1226
|
+
continue;
|
|
1227
|
+
}
|
|
1228
|
+
|
|
1229
|
+
const isDynamic = rawName.startsWith(':');
|
|
1230
|
+
const attrName = isDynamic ? rawName.slice(1) : rawName;
|
|
1231
|
+
const propName = kebabToCamel(attrName);
|
|
1232
|
+
const prop = propMap.get(propName.toLowerCase());
|
|
1233
|
+
if (!prop) continue;
|
|
1234
|
+
|
|
1235
|
+
let value = isDynamic ? parseExampleExpression(rawValue || '', bindings) : coerceStaticExampleValue(rawValue, prop);
|
|
1236
|
+
if (isDynamic && value === undefined) {
|
|
1237
|
+
value = createNamedPropMock(componentName, prop, kind, propsFromExample);
|
|
1238
|
+
}
|
|
1239
|
+
if (value !== undefined && value !== '') propsFromExample[prop.name] = value;
|
|
1240
|
+
}
|
|
1241
|
+
|
|
1242
|
+
if (closedMatch) {
|
|
1243
|
+
const defaultSlot = normalizeSlotContent(closedMatch[2] || '');
|
|
1244
|
+
if (defaultSlot) slots.default = defaultSlot;
|
|
1245
|
+
}
|
|
1246
|
+
|
|
1247
|
+
return preview;
|
|
1248
|
+
}
|
|
1249
|
+
|
|
1250
|
+
return null;
|
|
1251
|
+
}
|
|
1252
|
+
|
|
1253
|
+
function normalizePreviewConfig(preview) {
|
|
1254
|
+
if (!preview || typeof preview !== 'object') return null;
|
|
1255
|
+
|
|
1256
|
+
return {
|
|
1257
|
+
source: preview.source || 'meta',
|
|
1258
|
+
kind: normalizePreviewKind(preview.kind),
|
|
1259
|
+
code: preview.code,
|
|
1260
|
+
props: preview.props && typeof preview.props === 'object' ? preview.props : {},
|
|
1261
|
+
slots: preview.slots && typeof preview.slots === 'object' ? preview.slots : preview.slot ? { default: preview.slot } : {},
|
|
1262
|
+
vModel: preview.vModel,
|
|
1263
|
+
};
|
|
1264
|
+
}
|
|
1265
|
+
|
|
1266
|
+
function buildPreview(componentName, props, events, slots, examples, jsdoc, metaPreview) {
|
|
1267
|
+
const kind = inferPreviewKind(componentName, props, events, slots, jsdoc, metaPreview);
|
|
1268
|
+
const preview = normalizePreviewConfig(metaPreview) || inferPreviewFromExamples(componentName, props, examples, kind) || {
|
|
1269
|
+
source: 'inferred',
|
|
1270
|
+
props: {},
|
|
1271
|
+
slots: {},
|
|
1272
|
+
};
|
|
1273
|
+
|
|
1274
|
+
return {
|
|
1275
|
+
...applyPreviewMocks(componentName, props, preview, kind),
|
|
1276
|
+
kind: preview.kind || kind,
|
|
1277
|
+
};
|
|
1278
|
+
}
|
|
1279
|
+
|
|
690
1280
|
function findAllComponentDirs() {
|
|
691
1281
|
if (!fs.existsSync(componentRoot)) return [];
|
|
692
1282
|
|
|
@@ -758,69 +1348,10 @@ function buildUsageHints(componentName, title, props, events, slots) {
|
|
|
758
1348
|
return { useWhen, avoidWhen, notes };
|
|
759
1349
|
}
|
|
760
1350
|
|
|
761
|
-
function buildSamplePropValue(prop) {
|
|
762
|
-
if (prop.default !== null && prop.default !== undefined && prop.default !== '') return prop.default;
|
|
763
|
-
if (prop.values?.length) return prop.values[0];
|
|
764
|
-
|
|
765
|
-
const type = prop.type || '';
|
|
766
|
-
const name = prop.name.toLowerCase();
|
|
767
|
-
|
|
768
|
-
if (type.includes('boolean')) return false;
|
|
769
|
-
if (type.includes('number') || type.includes('Number')) {
|
|
770
|
-
if (name.includes('percent')) return 60;
|
|
771
|
-
if (name.includes('max')) return 10;
|
|
772
|
-
if (name.includes('min')) return 0;
|
|
773
|
-
if (name.includes('size')) return 32;
|
|
774
|
-
if (name === 'w' || name.includes('width')) return 240;
|
|
775
|
-
if (name === 'h' || name.includes('height')) return 80;
|
|
776
|
-
return 1;
|
|
777
|
-
}
|
|
778
|
-
if (type.includes('Array') || type.includes('array') || ['items', 'list', 'range', 'tabs', 'arr'].includes(prop.name)) {
|
|
779
|
-
return [
|
|
780
|
-
{ label: '选项一', value: 'one' },
|
|
781
|
-
{ label: '选项二', value: 'two' },
|
|
782
|
-
];
|
|
783
|
-
}
|
|
784
|
-
if (type.includes('Object') || type.includes('object')) return {};
|
|
785
|
-
if (name.includes('placeholder')) return '请输入';
|
|
786
|
-
if (name.includes('title')) return '标题';
|
|
787
|
-
if (name.includes('label')) return '标签';
|
|
788
|
-
if (name.includes('text') || name.includes('content')) return '示例内容';
|
|
789
|
-
if (name.includes('phone')) return '13800000000';
|
|
790
|
-
if (name === 'icon') return '/static/icon/avatar.svg';
|
|
791
|
-
if (name === 'src' || name.includes('poster')) return '';
|
|
792
|
-
|
|
793
|
-
return '';
|
|
794
|
-
}
|
|
795
|
-
|
|
796
|
-
function renderExampleFromProps(componentName, props) {
|
|
797
|
-
const importantProps = props
|
|
798
|
-
.filter((prop) => prop.required || ['modelValue', 'title', 'text', 'label', 'items', 'range', 'tabs', 'src', 'icon', 'type', 'size'].includes(prop.name))
|
|
799
|
-
.slice(0, 5);
|
|
800
|
-
const attrs = importantProps
|
|
801
|
-
.map((prop) => {
|
|
802
|
-
if (prop.name === 'modelValue') return 'v-model="value"';
|
|
803
|
-
const value = buildSamplePropValue(prop);
|
|
804
|
-
if (typeof value === 'boolean') return value ? prop.name : `:${prop.name}="false"`;
|
|
805
|
-
if (typeof value === 'number') return `:${prop.name}="${value}"`;
|
|
806
|
-
if (Array.isArray(value) || (value && typeof value === 'object')) return `:${prop.name}="${prop.name}"`;
|
|
807
|
-
if (!value) return '';
|
|
808
|
-
|
|
809
|
-
return `${prop.name}="${value}"`;
|
|
810
|
-
})
|
|
811
|
-
.filter(Boolean)
|
|
812
|
-
.join(' ');
|
|
813
|
-
const attrText = attrs ? ` ${attrs}` : '';
|
|
814
|
-
|
|
815
|
-
return `<${componentName}${attrText}>示例内容</${componentName}>`;
|
|
816
|
-
}
|
|
817
|
-
|
|
818
1351
|
function inferComponentInfo(componentDir) {
|
|
819
1352
|
const componentName = path.basename(componentDir);
|
|
820
1353
|
const componentFile = findComponentFile(componentDir);
|
|
821
|
-
const
|
|
822
|
-
const script = extractSfcBlock(sfcSource, 'script', (attrs) => attrs.includes('setup'));
|
|
823
|
-
const template = extractSfcBlock(sfcSource, 'template');
|
|
1354
|
+
const { script, template } = readSfcBlocks(componentFile);
|
|
824
1355
|
const jsdoc = parseJsdoc(script);
|
|
825
1356
|
const aliases = collectTypeAliases(componentDir);
|
|
826
1357
|
const props = extractProps(script, componentFile, aliases, jsdoc);
|
|
@@ -842,7 +1373,6 @@ function inferInitialMeta(componentDir) {
|
|
|
842
1373
|
const examples = normalizeExamples([], jsdoc.examples);
|
|
843
1374
|
const title = jsdoc.description?.split('\n')[0]?.trim() || toTitleCase(componentName);
|
|
844
1375
|
const usageHints = buildUsageHints(componentName, title, props, events, slots);
|
|
845
|
-
const exampleCode = renderExampleFromProps(componentName, props);
|
|
846
1376
|
|
|
847
1377
|
return {
|
|
848
1378
|
title,
|
|
@@ -852,15 +1382,7 @@ function inferInitialMeta(componentDir) {
|
|
|
852
1382
|
avoidWhen: usageHints.avoidWhen,
|
|
853
1383
|
related: [],
|
|
854
1384
|
notes: usageHints.notes,
|
|
855
|
-
examples
|
|
856
|
-
? examples
|
|
857
|
-
: [
|
|
858
|
-
{
|
|
859
|
-
title: '基础用法',
|
|
860
|
-
description: `自动根据 ${componentName} 的 props 生成,可按业务语义继续调整。`,
|
|
861
|
-
code: exampleCode,
|
|
862
|
-
},
|
|
863
|
-
],
|
|
1385
|
+
examples,
|
|
864
1386
|
};
|
|
865
1387
|
}
|
|
866
1388
|
|
|
@@ -881,6 +1403,10 @@ function mergeMeta(existingMeta, inferredMeta) {
|
|
|
881
1403
|
}
|
|
882
1404
|
}
|
|
883
1405
|
|
|
1406
|
+
if (Array.isArray(merged.examples)) {
|
|
1407
|
+
merged.examples = normalizeExamples(merged.examples, inferredMeta.examples || []);
|
|
1408
|
+
}
|
|
1409
|
+
|
|
884
1410
|
return merged;
|
|
885
1411
|
}
|
|
886
1412
|
|
|
@@ -892,7 +1418,30 @@ function isAutoGeneratedDocPage(filePath) {
|
|
|
892
1418
|
}
|
|
893
1419
|
|
|
894
1420
|
function renderGeneratedControlStyles() {
|
|
895
|
-
return `.
|
|
1421
|
+
return `.preview-actions {
|
|
1422
|
+
display: flex;
|
|
1423
|
+
flex-direction: row;
|
|
1424
|
+
justify-content: flex-start;
|
|
1425
|
+
width: 100%;
|
|
1426
|
+
margin-bottom: 16rpx;
|
|
1427
|
+
}
|
|
1428
|
+
|
|
1429
|
+
.preview-open-btn {
|
|
1430
|
+
box-sizing: border-box;
|
|
1431
|
+
display: inline-flex;
|
|
1432
|
+
align-items: center;
|
|
1433
|
+
justify-content: center;
|
|
1434
|
+
min-height: 64rpx;
|
|
1435
|
+
padding: 0 24rpx;
|
|
1436
|
+
font-size: 26rpx;
|
|
1437
|
+
font-weight: 600;
|
|
1438
|
+
line-height: 1.35;
|
|
1439
|
+
color: #fff;
|
|
1440
|
+
background: #2563eb;
|
|
1441
|
+
border-radius: 8rpx;
|
|
1442
|
+
}
|
|
1443
|
+
|
|
1444
|
+
.control-panel,
|
|
896
1445
|
.control-list {
|
|
897
1446
|
box-sizing: border-box;
|
|
898
1447
|
display: flex;
|
|
@@ -1035,6 +1584,7 @@ function renderAutoDocPage(componentName, componentFile) {
|
|
|
1035
1584
|
const importName = componentName
|
|
1036
1585
|
.replace(/(^|-)(\w)/g, (_, __, char) => char.toUpperCase())
|
|
1037
1586
|
.replace(/[^\w]/g, '');
|
|
1587
|
+
const dataModuleImport = `./${getPageDataModuleFileName()}`;
|
|
1038
1588
|
const {
|
|
1039
1589
|
componentDocPageImport,
|
|
1040
1590
|
autoDocHookImport,
|
|
@@ -1048,13 +1598,18 @@ function renderAutoDocPage(componentName, componentFile) {
|
|
|
1048
1598
|
import ${componentDocPageName} from '${componentDocPageImport}';
|
|
1049
1599
|
import { ${autoDocHookName} } from '${autoDocHookImport}';
|
|
1050
1600
|
import ${importName} from '${componentImportBase}/${componentName}/${fileName}';
|
|
1051
|
-
import doc from '
|
|
1601
|
+
import doc from '${dataModuleImport}';
|
|
1052
1602
|
|
|
1053
1603
|
const {
|
|
1054
1604
|
propControls,
|
|
1055
|
-
|
|
1605
|
+
renderedPreviewProps,
|
|
1606
|
+
previewKey,
|
|
1056
1607
|
slotText,
|
|
1057
1608
|
codeSnippet,
|
|
1609
|
+
isOverlayPreview,
|
|
1610
|
+
overlayTriggerText,
|
|
1611
|
+
openOverlayPreview,
|
|
1612
|
+
handlePreviewModelValueUpdate,
|
|
1058
1613
|
isBooleanProp,
|
|
1059
1614
|
isNumberProp,
|
|
1060
1615
|
isArrayProp,
|
|
@@ -1070,7 +1625,11 @@ const {
|
|
|
1070
1625
|
<${componentDocPageName} :doc="doc" :show-controls="doc.props.length > 0">
|
|
1071
1626
|
<template #preview>
|
|
1072
1627
|
<view class="preview-frame">
|
|
1073
|
-
|
|
1628
|
+
<view v-if="isOverlayPreview" class="preview-actions">
|
|
1629
|
+
<text class="preview-open-btn" @click="openOverlayPreview">{{ overlayTriggerText }}</text>
|
|
1630
|
+
</view>
|
|
1631
|
+
|
|
1632
|
+
<${importName} :key="previewKey" v-bind="renderedPreviewProps" @update:modelValue="handlePreviewModelValueUpdate('modelValue', $event)">
|
|
1074
1633
|
{{ slotText }}
|
|
1075
1634
|
</${importName}>
|
|
1076
1635
|
</view>
|
|
@@ -1144,10 +1703,11 @@ ${renderGeneratedControlStyles()}
|
|
|
1144
1703
|
|
|
1145
1704
|
function renderGenericDocPage() {
|
|
1146
1705
|
const { componentDocPageImport, componentDocPageName } = docsConfig.runtime;
|
|
1706
|
+
const dataModuleImport = `./${getPageDataModuleFileName()}`;
|
|
1147
1707
|
|
|
1148
1708
|
return `<script setup lang="ts">
|
|
1149
1709
|
import ${componentDocPageName} from '${componentDocPageImport}';
|
|
1150
|
-
import doc from '
|
|
1710
|
+
import doc from '${dataModuleImport}';
|
|
1151
1711
|
</script>
|
|
1152
1712
|
|
|
1153
1713
|
<template>
|
|
@@ -1271,15 +1831,15 @@ function collectComponentDoc(componentDir) {
|
|
|
1271
1831
|
if (!componentFile) return null;
|
|
1272
1832
|
|
|
1273
1833
|
const componentName = path.basename(componentDir);
|
|
1274
|
-
const
|
|
1275
|
-
const script = extractSfcBlock(sfcSource, 'script', (attrs) => attrs.includes('setup'));
|
|
1276
|
-
const template = extractSfcBlock(sfcSource, 'template');
|
|
1834
|
+
const { script, template } = readSfcBlocks(componentFile);
|
|
1277
1835
|
const meta = readJson(getMetaFile(componentDir));
|
|
1278
1836
|
const jsdoc = parseJsdoc(script);
|
|
1279
1837
|
const aliases = collectTypeAliases(componentDir);
|
|
1280
1838
|
const props = extractProps(script, componentFile, aliases, jsdoc);
|
|
1281
1839
|
const events = extractEvents(script, componentFile, jsdoc);
|
|
1282
1840
|
const slots = extractSlots(template);
|
|
1841
|
+
const examples = normalizeExamples(meta.examples, jsdoc.examples);
|
|
1842
|
+
const preview = buildPreview(componentName, props, events, slots, examples, jsdoc, meta.preview);
|
|
1283
1843
|
|
|
1284
1844
|
const component = {
|
|
1285
1845
|
name: componentName,
|
|
@@ -1291,7 +1851,8 @@ function collectComponentDoc(componentDir) {
|
|
|
1291
1851
|
props,
|
|
1292
1852
|
events,
|
|
1293
1853
|
slots,
|
|
1294
|
-
|
|
1854
|
+
preview,
|
|
1855
|
+
examples,
|
|
1295
1856
|
useWhen: meta.useWhen || [],
|
|
1296
1857
|
avoidWhen: meta.avoidWhen || [],
|
|
1297
1858
|
notes: meta.notes || [],
|
|
@@ -1621,13 +2182,24 @@ function main() {
|
|
|
1621
2182
|
writeJson(path.join(aiDocsDir, docsConfig.output.catalogFile), catalog);
|
|
1622
2183
|
writeText(path.join(aiDocsDir, docsConfig.output.aiIndexFile), renderAiIndex(catalog));
|
|
1623
2184
|
writeText(path.join(aiDocsDir, docsConfig.output.llmsFile), renderLlmsTxt(catalog));
|
|
1624
|
-
|
|
2185
|
+
const pageIndex = buildPageIndex(components);
|
|
2186
|
+
writeJson(indexDataFile, pageIndex);
|
|
2187
|
+
writeDataModule(
|
|
2188
|
+
getIndexDataModuleFile(),
|
|
2189
|
+
pageIndex,
|
|
2190
|
+
toDataModuleIdentifier(docsConfig.projectName || 'docs-index', 'docsIndex'),
|
|
2191
|
+
);
|
|
1625
2192
|
const qualityReport = collectQualityReport(components);
|
|
1626
2193
|
writeJson(path.join(aiDocsDir, docsConfig.output.qualityFile), qualityReport);
|
|
1627
2194
|
|
|
1628
2195
|
for (const component of components) {
|
|
1629
2196
|
writeText(path.join(markdownDocsDir, `${component.name}.md`), renderComponentMarkdown(component));
|
|
1630
2197
|
writeJson(path.join(pageDataRoot, component.name, 'data.json'), component);
|
|
2198
|
+
writeDataModule(
|
|
2199
|
+
path.join(pageDataRoot, component.name, getPageDataModuleFileName()),
|
|
2200
|
+
component,
|
|
2201
|
+
toDataModuleIdentifier(component.name, 'componentDoc'),
|
|
2202
|
+
);
|
|
1631
2203
|
}
|
|
1632
2204
|
|
|
1633
2205
|
console.log(`Generated component docs for ${components.length} component(s): ${components.map((item) => item.name).join(', ')}`);
|