create-component-template-cli 1.0.0 → 1.0.1

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.
Files changed (75) hide show
  1. package/.changeset/config.json +1 -1
  2. package/AGENTS.md +114 -0
  3. package/CHANGELOG.md +6 -0
  4. package/CLAUDE.md +19 -7
  5. package/README.md +40 -20
  6. package/bin/cli.mjs +38 -29
  7. package/docs/2026-05-11T10-04-44-/344/270/211/345/245/227/346/250/241/346/235/277/351/207/215/346/236/204/2026-05-11T10-04-44-/344/270/211/345/245/227/346/250/241/346/235/277/351/207/215/346/236/204-/346/200/235/350/267/257/346/226/207/346/241/243.md +167 -0
  8. package/docs/2026-05-11T10-04-44-/344/270/211/345/245/227/346/250/241/346/235/277/351/207/215/346/236/204/2026-05-11T10-04-44-/344/270/211/345/245/227/346/250/241/346/235/277/351/207/215/346/236/204-/346/200/273/347/273/223/346/226/207/346/241/243.md +80 -0
  9. package/docs/2026-05-11T10-04-44-/344/270/211/345/245/227/346/250/241/346/235/277/351/207/215/346/236/204/2026-05-11T10-04-44-/344/270/211/345/245/227/346/250/241/346/235/277/351/207/215/346/236/204-/350/256/276/350/256/241/346/226/271/346/241/210.md +477 -0
  10. package/package.json +1 -1
  11. package/plop-templates/general/data.ts.hbs +16 -0
  12. package/plop-templates/general/hook.ts.hbs +11 -0
  13. package/plop-templates/general/index.normal.vue.hbs +28 -0
  14. package/plop-templates/general/index.scss.hbs +5 -0
  15. package/plop-templates/general/index.setup.vue.hbs +20 -0
  16. package/plop-templates/{index.ts.hbs → general/index.ts.hbs} +2 -1
  17. package/plop-templates/general/typing.ts.hbs +13 -0
  18. package/plop-templates/multiple-pick/data.ts.hbs +20 -0
  19. package/plop-templates/multiple-pick/hook.ts.hbs +11 -0
  20. package/plop-templates/multiple-pick/index.normal.vue.hbs +37 -0
  21. package/plop-templates/multiple-pick/index.scss.hbs +5 -0
  22. package/plop-templates/multiple-pick/index.setup.vue.hbs +22 -0
  23. package/plop-templates/multiple-pick/index.ts.hbs +4 -0
  24. package/plop-templates/multiple-pick/typing.ts.hbs +13 -0
  25. package/plop-templates/single-pick/data.ts.hbs +16 -0
  26. package/plop-templates/single-pick/hook.ts.hbs +11 -0
  27. package/plop-templates/single-pick/index.normal.vue.hbs +36 -0
  28. package/plop-templates/single-pick/index.scss.hbs +5 -0
  29. package/plop-templates/single-pick/index.setup.vue.hbs +23 -0
  30. package/plop-templates/single-pick/index.ts.hbs +4 -0
  31. package/plop-templates/single-pick/typing.ts.hbs +13 -0
  32. package/plopfile.mjs +68 -59
  33. package/src/generate.mjs +101 -31
  34. package/tmp-output/GeneralNormalDemo/index.ts +4 -0
  35. package/tmp-output/GeneralNormalDemo/src/data.ts +16 -0
  36. package/tmp-output/GeneralNormalDemo/src/hook.ts +11 -0
  37. package/tmp-output/GeneralNormalDemo/src/index.scss +5 -0
  38. package/tmp-output/GeneralNormalDemo/src/index.vue +28 -0
  39. package/tmp-output/GeneralNormalDemo/src/typing.ts +13 -0
  40. package/tmp-output/GeneralSetupDemo/index.ts +4 -0
  41. package/tmp-output/GeneralSetupDemo/src/data.ts +16 -0
  42. package/tmp-output/GeneralSetupDemo/src/hook.ts +11 -0
  43. package/tmp-output/GeneralSetupDemo/src/index.scss +5 -0
  44. package/tmp-output/GeneralSetupDemo/src/index.vue +20 -0
  45. package/tmp-output/GeneralSetupDemo/src/typing.ts +13 -0
  46. package/tmp-output/MultiplePickNormalDemo/index.ts +4 -0
  47. package/tmp-output/MultiplePickNormalDemo/src/data.ts +20 -0
  48. package/tmp-output/MultiplePickNormalDemo/src/hook.ts +11 -0
  49. package/tmp-output/MultiplePickNormalDemo/src/index.scss +5 -0
  50. package/tmp-output/MultiplePickNormalDemo/src/index.vue +37 -0
  51. package/tmp-output/MultiplePickNormalDemo/src/typing.ts +13 -0
  52. package/tmp-output/MultiplePickSetupDemo/index.ts +4 -0
  53. package/tmp-output/MultiplePickSetupDemo/src/data.ts +20 -0
  54. package/tmp-output/MultiplePickSetupDemo/src/hook.ts +11 -0
  55. package/tmp-output/MultiplePickSetupDemo/src/index.scss +5 -0
  56. package/tmp-output/MultiplePickSetupDemo/src/index.vue +22 -0
  57. package/tmp-output/MultiplePickSetupDemo/src/typing.ts +13 -0
  58. package/tmp-output/SinglePickNormalDemo/index.ts +4 -0
  59. package/tmp-output/SinglePickNormalDemo/src/data.ts +16 -0
  60. package/tmp-output/SinglePickNormalDemo/src/hook.ts +11 -0
  61. package/tmp-output/SinglePickNormalDemo/src/index.scss +5 -0
  62. package/tmp-output/SinglePickNormalDemo/src/index.vue +36 -0
  63. package/tmp-output/SinglePickNormalDemo/src/typing.ts +13 -0
  64. package/tmp-output/SinglePickSetupDemo/index.ts +4 -0
  65. package/tmp-output/SinglePickSetupDemo/src/data.ts +16 -0
  66. package/tmp-output/SinglePickSetupDemo/src/hook.ts +11 -0
  67. package/tmp-output/SinglePickSetupDemo/src/index.scss +5 -0
  68. package/tmp-output/SinglePickSetupDemo/src/index.vue +23 -0
  69. package/tmp-output/SinglePickSetupDemo/src/typing.ts +13 -0
  70. package/plop-templates/data.ts.hbs +0 -7
  71. package/plop-templates/hook.ts.hbs +0 -29
  72. package/plop-templates/index.scss.hbs +0 -1
  73. package/plop-templates/index.setup.vue.hbs +0 -25
  74. package/plop-templates/index.vue.hbs +0 -38
  75. package/plop-templates/typing.ts.hbs +0 -14
@@ -0,0 +1,13 @@
1
+ export enum {{ interfaceName }}Enum {
2
+ DEFAULT = 'default',
3
+ }
4
+
5
+ export interface {{ interfaceName }}Props {
6
+ user?: Record<string, any>
7
+ modelValue?: string[]
8
+ }
9
+
10
+ export interface DataType {
11
+ label: string
12
+ value: string
13
+ }
@@ -0,0 +1,16 @@
1
+ import type { DataType } from './typing'
2
+
3
+ const data: Array<DataType> = [
4
+ {
5
+ label: '单选项1',
6
+ value: 'single-1',
7
+ },
8
+ {
9
+ label: '单选项2',
10
+ value: 'single-2',
11
+ },
12
+ ]
13
+
14
+ export {
15
+ data,
16
+ }
@@ -0,0 +1,11 @@
1
+ import { ref } from 'vue'
2
+ import { data } from './data'
3
+ import type { {{ componentName }}Props } from './typing'
4
+
5
+ export const use{{ componentName }} = (props: {{ componentName }}Props) => {
6
+ const selectedValue = ref(props.modelValue ?? data[0]?.value ?? '')
7
+
8
+ return {
9
+ selectedValue,
10
+ }
11
+ }
@@ -0,0 +1,36 @@
1
+ <template>
2
+ <div class="{{ class }}">
3
+ <SinglePickButtonGroup />
4
+ </div>
5
+ </template>
6
+
7
+ <script lang="ts">
8
+ import { defineComponent, PropType } from 'vue'
9
+ import SinglePickButtonGroup from '@/components/common/SinglePickButtonGroup'
10
+ import { {{ componentName }}Props } from './typing'
11
+ import { use{{ componentName }} } from './hook'
12
+
13
+ export default defineComponent({
14
+ name: '{{ componentName }}',
15
+ components: {
16
+ SinglePickButtonGroup,
17
+ },
18
+ props: {
19
+ user: {
20
+ type: Object as PropType<{{ componentName }}Props['user']>,
21
+ required: false,
22
+ },
23
+ },
24
+ setup(props) {
25
+ const { selectedValue } = use{{ componentName }}(props as {{ componentName }}Props)
26
+
27
+ return {
28
+ selectedValue,
29
+ }
30
+ },
31
+ })
32
+ </script>
33
+
34
+ <style lang="scss" scoped>
35
+ @import './index.scss';
36
+ </style>
@@ -0,0 +1,5 @@
1
+ .{{class}} {
2
+ position: absolute;
3
+ top: 0;
4
+ left: 0;
5
+ }
@@ -0,0 +1,23 @@
1
+ <template>
2
+ <div class="{{ class }}">
3
+ <SinglePickButtonGroup />
4
+ </div>
5
+ </template>
6
+
7
+ <script setup lang="ts">
8
+ import SinglePickButtonGroup from '@/components/common/SinglePickButtonGroup'
9
+ import { {{ componentName }}Props } from './typing'
10
+ import { use{{ componentName }} } from './hook'
11
+
12
+ defineOptions({
13
+ name: '{{ componentName }}',
14
+ })
15
+
16
+ const props = defineProps<{{ componentName }}Props>()
17
+
18
+ const { selectedValue } = use{{ componentName }}(props)
19
+ </script>
20
+
21
+ <style lang="scss" scoped>
22
+ @import './index.scss';
23
+ </style>
@@ -0,0 +1,4 @@
1
+ import {{ componentName }} from './src/index.vue'
2
+
3
+ export default {{ componentName }}
4
+ export * from './src/typing'
@@ -0,0 +1,13 @@
1
+ export enum {{ interfaceName }}Enum {
2
+ DEFAULT = 'default',
3
+ }
4
+
5
+ export interface {{ interfaceName }}Props {
6
+ user?: Record<string, any>
7
+ modelValue?: string
8
+ }
9
+
10
+ export interface DataType {
11
+ label: string
12
+ value: string
13
+ }
package/plopfile.mjs CHANGED
@@ -5,85 +5,110 @@ import fs from 'node:fs';
5
5
 
6
6
  const __filename = fileURLToPath(import.meta.url);
7
7
  const __dirname = path.dirname(__filename);
8
- const rootPath = `src/components/platform`;
8
+
9
+ const BUSINESS_TEMPLATE_CHOICES = [
10
+ { name: '通用模板', value: 'general' },
11
+ { name: '产品组单选模板', value: 'single-pick' },
12
+ { name: '产品组多选模板', value: 'multiple-pick' },
13
+ ];
14
+
15
+ const VUE_TEMPLATE_STYLE_CHOICES = [
16
+ { name: 'script setup 风格', value: 'setup' },
17
+ { name: 'defineComponent 风格', value: 'normal' },
18
+ ];
19
+
20
+ const BUSINESS_TEMPLATE_DIR_MAP = {
21
+ general: 'general',
22
+ 'single-pick': 'single-pick',
23
+ 'multiple-pick': 'multiple-pick',
24
+ };
25
+
26
+ const VUE_TEMPLATE_FILE_MAP = {
27
+ setup: 'index.setup.vue.hbs',
28
+ normal: 'index.normal.vue.hbs',
29
+ };
30
+
31
+ const TEMPLATE_FILES = [
32
+ { file: 'index.scss', output: 'src/index.scss', templateFile: 'index.scss.hbs', getData: (componentName, className) => ({ class: className, componentName }) },
33
+ { file: 'index.vue', output: 'src/index.vue', type: 'vue-entry', getData: (componentName, className) => ({ class: className, componentName }) },
34
+ { file: 'typing.ts', output: 'src/typing.ts', templateFile: 'typing.ts.hbs', getData: (componentName) => ({ interfaceName: componentName, componentName }) },
35
+ { file: 'hook.ts', output: 'src/hook.ts', templateFile: 'hook.ts.hbs', getData: (componentName) => ({ componentName }) },
36
+ { file: 'data.ts', output: 'src/data.ts', templateFile: 'data.ts.hbs', getData: (componentName) => ({ componentName }) },
37
+ { file: 'index.ts', output: 'index.ts', templateFile: 'index.ts.hbs', getData: (componentName) => ({ componentName }) },
38
+ ];
9
39
 
10
40
  export default (plop) => {
11
41
  plop.setGenerator('create-component-template', {
12
- description: '----组件模板生成器----',
42
+ description: '----组件模板生成器---',
13
43
  prompts: [
14
44
  {
15
45
  type: 'input',
16
46
  name: 'rootPath',
17
47
  message: '请输入组件生成路径:',
18
- // 逻辑:如果命令行传入了 --rootPath,则使用它,否则使用默认值
19
- default: (data) => data.rootPath || `src/components/platform`,
48
+ default: (data) => data.rootPath || 'src/components/platform',
20
49
  },
21
50
  {
22
51
  type: 'list',
23
52
  name: 'templateType',
24
- message: '请选择组件模板(使用上下箭头进行选择):',
25
- choices: [
26
- { name: 'VUE的setup语法糖组件模板', value: 'setup' },
27
- { name: 'VUE普通组合式API组件模板', value: 'normal' },
28
- ],
29
- default: 'setup'
53
+ message: '请选择业务模板类型:',
54
+ choices: BUSINESS_TEMPLATE_CHOICES,
55
+ default: 'general',
56
+ },
57
+ {
58
+ type: 'list',
59
+ name: 'vueTemplateType',
60
+ message: '请选择 index.vue 的 Vue 语法风格:',
61
+ choices: VUE_TEMPLATE_STYLE_CHOICES,
62
+ default: 'setup',
30
63
  },
31
64
  {
32
65
  type: 'input',
33
66
  name: 'name',
34
- message: '请输入组件名称 (大驼峰):',
35
- // 逻辑:如果命令行传入了 --name,则使用它,否则使用 'CustomComponent'
67
+ message: '请输入组件名称(大驼峰):',
36
68
  default: (data) => data.name || 'CustomComponent',
37
69
  },
38
70
  {
39
- type: 'list', // 使用 list 提供明确的选择
71
+ type: 'list',
40
72
  name: 'collisionStrategy',
41
- message: (data) => `目录 ${data.rootPath}/${data.name} 已存在,请选择操作:`,
73
+ message: (data) => `目录 ${data.rootPath}/${data.name} 已存在,请选择处理方式:`,
42
74
  choices: [
43
75
  { name: '覆盖', value: 'overwrite' },
44
76
  { name: '重新输入名称', value: 'retry' },
45
77
  ],
46
- // 关键:只有当目录存在时才触发
47
78
  when: (data) => {
48
79
  const targetPath = path.join(process.cwd(), data.rootPath, data.name);
49
80
  return fs.existsSync(targetPath);
50
81
  },
51
82
  },
52
83
  {
53
- // 关键点:如果用户选择 'retry',我们通过 validate 拦截并强制用户回到上一步
54
84
  type: 'input',
55
85
  name: 'retryName',
56
- message: '请重新输入组件名称(按回车确认后将刷新名称):',
86
+ message: '请输入新的组件名称:',
57
87
  when: (data) => data.collisionStrategy === 'retry',
58
88
  validate: (value, data) => {
59
- // 这里利用 validate 的特性:
60
- // 我们直接修改上层 data.name,然后返回一个错误信息,
61
- // 实际上会迫使 Inquirer 停留在这里,但此时 data.name 已经被修正
62
89
  if (value && value !== data.name) {
63
- data.name = value; // 覆盖之前的 name
64
- // 检查新名字是否依然冲突
65
- const newPath = path.join(process.cwd(), data.rootPath, value);
66
- if (fs.existsSync(newPath)) {
67
- return '新名称依然存在,请再次输入或 Ctrl+C 退出';
68
- }
69
- return true;
90
+ data.name = value;
91
+ const newPath = path.join(process.cwd(), data.rootPath, value);
92
+ if (fs.existsSync(newPath)) {
93
+ return '新名称仍然已存在,请再次输入或 Ctrl+C 退出';
94
+ }
95
+ return true;
70
96
  }
71
- return '请输入一个新的名称以避开冲突';
72
- }
73
- }
97
+ return '请输入一个新的组件名称以避开冲突';
98
+ },
99
+ },
74
100
  ],
75
101
 
76
102
  actions: (data) => {
77
103
  const actions = [];
78
- // 最终确定的组件名(可能是初始输入的,也可能是 retry 后修正的)
79
- const finalName = data.name;
80
-
81
- const camelCase = changeCase.camelCase(finalName);
82
- const componentName = changeCase.pascalCase(camelCase);
83
- const className = changeCase.kebabCase(camelCase);
104
+ const finalName = data.name;
105
+ const camelName = changeCase.camelCase(finalName);
106
+ const componentName = changeCase.pascalCase(camelName);
107
+ const className = changeCase.kebabCase(camelName);
84
108
  const targetFolder = `${data.rootPath}/${componentName}`;
109
+ const businessTemplateDir = BUSINESS_TEMPLATE_DIR_MAP[data.templateType];
110
+ const vueTemplateFile = VUE_TEMPLATE_FILE_MAP[data.vueTemplateType];
85
111
 
86
- // 1. 如果选择了覆盖,执行删除动作
87
112
  if (data.collisionStrategy === 'overwrite') {
88
113
  actions.push({
89
114
  type: 'deleteTargetDir',
@@ -91,29 +116,14 @@ export default (plop) => {
91
116
  });
92
117
  }
93
118
 
94
- const vueTemplateFile = data.templateType === 'setup' ? 'index.setup.vue.hbs' : 'index.vue.hbs';
95
-
96
- // 2. 生成文件列表 (统一配置)
97
- const templates = [
98
- { file: 'index.scss', data: { class: className } },
99
- {
100
- file: 'index.vue',
101
- templateFile: vueTemplateFile,
102
- data: { class: className, componentName }
103
- },
104
- { file: 'typing.ts', data: { interfaceName: componentName } },
105
- { file: 'hook.ts', data: { componentName } },
106
- { file: 'index.ts', data: { componentName } },
107
- { file: 'data.ts' },
108
- ];
109
-
110
- templates.forEach(item => {
119
+ TEMPLATE_FILES.forEach((item) => {
120
+ const templateFile = item.type === 'vue-entry' ? vueTemplateFile : item.templateFile;
111
121
  actions.push({
112
122
  type: 'add',
113
- force: true, // 既然上面已经处理了删除逻辑,这里 force: true 确保写入成功
114
- path: item.file === 'index.ts' ? `${targetFolder}/index.ts` : `${targetFolder}/src/${item.file}`,
115
- templateFile: `${__dirname}/plop-templates/${item.templateFile || item.file + '.hbs'}`,
116
- data: item.data,
123
+ force: true,
124
+ path: `${targetFolder}/${item.output}`,
125
+ templateFile: `${__dirname}/plop-templates/${businessTemplateDir}/${templateFile}`,
126
+ data: item.getData(componentName, className),
117
127
  });
118
128
  });
119
129
 
@@ -121,7 +131,6 @@ export default (plop) => {
121
131
  },
122
132
  });
123
133
 
124
- // 注册自定义 Action:删除目录
125
134
  plop.setActionType('deleteTargetDir', (answers, config) => {
126
135
  const targetDir = path.join(process.cwd(), config.path);
127
136
  if (fs.existsSync(targetDir)) {
package/src/generate.mjs CHANGED
@@ -7,24 +7,45 @@ const __filename = fileURLToPath(import.meta.url);
7
7
  const __dirname = path.dirname(__filename);
8
8
  const TEMPLATES_DIR = path.join(__dirname, '..', 'plop-templates');
9
9
 
10
- const VALID_TEMPLATE_TYPES = ['setup', 'normal'];
10
+ const BUSINESS_TEMPLATE_MAP = {
11
+ general: {
12
+ label: '通用模板',
13
+ templateDir: 'general',
14
+ },
15
+ 'single-pick': {
16
+ label: '产品组单选模板',
17
+ templateDir: 'single-pick',
18
+ },
19
+ 'multiple-pick': {
20
+ label: '产品组多选模板',
21
+ templateDir: 'multiple-pick',
22
+ },
23
+ };
24
+
25
+ const VUE_TEMPLATE_STYLE_MAP = {
26
+ setup: {
27
+ label: 'script setup 风格',
28
+ templateFile: 'index.setup.vue.hbs',
29
+ },
30
+ normal: {
31
+ label: 'defineComponent 风格',
32
+ templateFile: 'index.normal.vue.hbs',
33
+ },
34
+ };
35
+
11
36
  const VALID_COLLISION_STRATEGIES = ['skip', 'overwrite'];
12
37
 
13
- const TEMPLATE_CONFIG = [
14
- { file: 'index.scss', dest: 'src', templateFile: 'index.scss.hbs', getData: (cn, cl) => ({ class: cl }) },
15
- { file: 'index.vue', dest: 'src', getData: (cn, cl, tt) => ({
16
- templateFile: tt === 'setup' ? 'index.setup.vue.hbs' : 'index.vue.hbs',
17
- data: { class: cl, componentName: cn },
18
- })},
19
- { file: 'typing.ts', dest: 'src', templateFile: 'typing.ts.hbs', getData: (cn) => ({ interfaceName: cn }) },
20
- { file: 'hook.ts', dest: 'src', templateFile: 'hook.ts.hbs', getData: (cn) => ({ componentName: cn }) },
21
- { file: 'data.ts', dest: 'src', templateFile: 'data.ts.hbs', getData: () => ({}) },
22
- { file: 'index.ts', dest: '', templateFile: 'index.ts.hbs', getData: (cn) => ({ componentName: cn }) },
38
+ const TEMPLATE_FILES = [
39
+ { output: 'src/index.scss', templateFile: 'index.scss.hbs', getData: (componentName, className) => ({ class: className, componentName }) },
40
+ { output: 'src/index.vue', type: 'vue-entry', getData: (componentName, className) => ({ class: className, componentName }) },
41
+ { output: 'src/typing.ts', templateFile: 'typing.ts.hbs', getData: (componentName) => ({ interfaceName: componentName, componentName }) },
42
+ { output: 'src/hook.ts', templateFile: 'hook.ts.hbs', getData: (componentName) => ({ componentName }) },
43
+ { output: 'src/data.ts', templateFile: 'data.ts.hbs', getData: (componentName) => ({ componentName }) },
44
+ { output: 'index.ts', templateFile: 'index.ts.hbs', getData: (componentName) => ({ componentName }) },
23
45
  ];
24
46
 
25
- function renderTemplate(templateName, data) {
26
- const filePath = path.join(TEMPLATES_DIR, templateName);
27
- const source = fs.readFileSync(filePath, 'utf-8');
47
+ function renderTemplate(templatePath, data) {
48
+ const source = fs.readFileSync(templatePath, 'utf-8');
28
49
  let result = source;
29
50
  for (const [key, value] of Object.entries(data)) {
30
51
  result = result.replaceAll(`{{ ${key} }}`, String(value));
@@ -33,18 +54,56 @@ function renderTemplate(templateName, data) {
33
54
  return result;
34
55
  }
35
56
 
36
- export function generateComponent({ name, rootPath, templateType = 'setup', collisionStrategy = 'skip' }) {
57
+ function resolveTemplateOptions({ templateType, vueTemplateType }) {
58
+ const businessTemplate = BUSINESS_TEMPLATE_MAP[templateType];
59
+ const vueTemplateStyle = VUE_TEMPLATE_STYLE_MAP[vueTemplateType];
60
+
61
+ if (!businessTemplate) {
62
+ return {
63
+ success: false,
64
+ error: `无效的 templateType: "${templateType}",可选值: ${Object.keys(BUSINESS_TEMPLATE_MAP).join(', ')}`,
65
+ };
66
+ }
67
+
68
+ if (!vueTemplateStyle) {
69
+ return {
70
+ success: false,
71
+ error: `无效的 vueTemplateType: "${vueTemplateType}",可选值: ${Object.keys(VUE_TEMPLATE_STYLE_MAP).join(', ')}`,
72
+ };
73
+ }
74
+
75
+ return {
76
+ success: true,
77
+ businessTemplate,
78
+ vueTemplateStyle,
79
+ };
80
+ }
81
+
82
+ export function generateComponent({
83
+ name,
84
+ rootPath,
85
+ templateType = 'general',
86
+ vueTemplateType = 'setup',
87
+ collisionStrategy = 'skip',
88
+ }) {
37
89
  if (!name || typeof name !== 'string') {
38
90
  return { success: false, error: '缺少必填参数: --name (组件名称)' };
39
91
  }
92
+
40
93
  if (!rootPath || typeof rootPath !== 'string') {
41
94
  return { success: false, error: '缺少必填参数: --rootPath (组件生成路径)' };
42
95
  }
43
- if (!VALID_TEMPLATE_TYPES.includes(templateType)) {
44
- return { success: false, error: `无效的 templateType: "${templateType}",可选值: ${VALID_TEMPLATE_TYPES.join(', ')}` };
96
+
97
+ const resolvedOptions = resolveTemplateOptions({ templateType, vueTemplateType });
98
+ if (!resolvedOptions.success) {
99
+ return resolvedOptions;
45
100
  }
101
+
46
102
  if (!VALID_COLLISION_STRATEGIES.includes(collisionStrategy)) {
47
- return { success: false, error: `无效的 collisionStrategy: "${collisionStrategy}",可选值: ${VALID_COLLISION_STRATEGIES.join(', ')}` };
103
+ return {
104
+ success: false,
105
+ error: `无效的 collisionStrategy: "${collisionStrategy}",可选值: ${VALID_COLLISION_STRATEGIES.join(', ')}`,
106
+ };
48
107
  }
49
108
 
50
109
  const camelName = changeCase.camelCase(name);
@@ -55,31 +114,42 @@ export function generateComponent({ name, rootPath, templateType = 'setup', coll
55
114
 
56
115
  if (fs.existsSync(fullTargetPath)) {
57
116
  if (collisionStrategy === 'skip') {
58
- return { success: false, error: `目录已存在: ${targetFolder}(使用 --collisionStrategy=overwrite 覆盖)` };
117
+ return {
118
+ success: false,
119
+ error: `目录已存在: ${targetFolder}(使用 --collisionStrategy=overwrite 覆盖)`,
120
+ };
59
121
  }
122
+
60
123
  if (collisionStrategy === 'overwrite') {
61
124
  fs.rmSync(fullTargetPath, { recursive: true, force: true });
62
125
  }
63
126
  }
64
127
 
128
+ const { businessTemplate, vueTemplateStyle } = resolvedOptions;
65
129
  const generatedFiles = [];
66
130
 
67
- for (const item of TEMPLATE_CONFIG) {
68
- const itemResult = item.getData(componentName, className, templateType);
69
- const templateFile = itemResult.templateFile || item.templateFile;
70
- const templateData = itemResult.data || itemResult;
71
-
72
- const destDir = item.dest === ''
73
- ? fullTargetPath
74
- : path.join(fullTargetPath, item.dest);
75
- const destFile = path.join(destDir, item.file);
131
+ for (const item of TEMPLATE_FILES) {
132
+ const templateFile = item.type === 'vue-entry' ? vueTemplateStyle.templateFile : item.templateFile;
133
+ const templatePath = path.join(TEMPLATES_DIR, businessTemplate.templateDir, templateFile);
134
+ const templateData = item.getData(componentName, className);
135
+ const destFile = path.join(fullTargetPath, item.output);
136
+ const destDir = path.dirname(destFile);
76
137
 
77
138
  fs.mkdirSync(destDir, { recursive: true });
78
139
 
79
- const content = renderTemplate(templateFile, templateData);
140
+ const content = renderTemplate(templatePath, templateData);
80
141
  fs.writeFileSync(destFile, content, 'utf-8');
81
- generatedFiles.push(path.join(targetFolder, item.dest, item.file).replace(/\\/g, '/'));
142
+ generatedFiles.push(path.join(targetFolder, item.output).replace(/\\/g, '/'));
82
143
  }
83
144
 
84
- return { success: true, files: generatedFiles, componentName, targetFolder };
145
+ return {
146
+ success: true,
147
+ files: generatedFiles,
148
+ componentName,
149
+ targetFolder,
150
+ templateType,
151
+ vueTemplateType,
152
+ templateLabel: businessTemplate.label,
153
+ vueTemplateLabel: vueTemplateStyle.label,
154
+ };
85
155
  }
@@ -0,0 +1,4 @@
1
+ import GeneralNormalDemo from './src/index.vue'
2
+
3
+ export default GeneralNormalDemo
4
+ export * from './src/typing'
@@ -0,0 +1,16 @@
1
+ import type { DataType } from './typing'
2
+
3
+ const data: Array<DataType> = [
4
+ {
5
+ label: '示例项1',
6
+ value: 'option-1',
7
+ },
8
+ {
9
+ label: '示例项2',
10
+ value: 'option-2',
11
+ },
12
+ ]
13
+
14
+ export {
15
+ data,
16
+ }
@@ -0,0 +1,11 @@
1
+ import { ref } from 'vue'
2
+ import type { GeneralNormalDemoProps } from './typing'
3
+
4
+ export const useGeneralNormalDemo = (props: GeneralNormalDemoProps) => {
5
+ const visible = ref(true)
6
+
7
+ return {
8
+ props,
9
+ visible,
10
+ }
11
+ }
@@ -0,0 +1,5 @@
1
+ .general-normal-demo {
2
+ position: absolute;
3
+ top: 0;
4
+ left: 0;
5
+ }
@@ -0,0 +1,28 @@
1
+ <template>
2
+ <div class="general-normal-demo"></div>
3
+ </template>
4
+
5
+ <script lang="ts">
6
+ import { defineComponent, PropType } from 'vue'
7
+ import { GeneralNormalDemoProps } from './typing'
8
+ import { useGeneralNormalDemo } from './hook'
9
+
10
+ export default defineComponent({
11
+ name: 'GeneralNormalDemo',
12
+ props: {
13
+ user: {
14
+ type: Object as PropType<GeneralNormalDemoProps['user']>,
15
+ required: false,
16
+ },
17
+ },
18
+ setup(props) {
19
+ const {} = useGeneralNormalDemo(props as GeneralNormalDemoProps)
20
+
21
+ return {}
22
+ },
23
+ })
24
+ </script>
25
+
26
+ <style lang="scss" scoped>
27
+ @import './index.scss';
28
+ </style>
@@ -0,0 +1,13 @@
1
+ export enum GeneralNormalDemoEnum {
2
+ ENUM_KEY1 = 'ENUM_KEY1',
3
+ ENUM_KEY2 = 'ENUM_KEY2',
4
+ }
5
+
6
+ export interface GeneralNormalDemoProps {
7
+ user?: Record<string, any>
8
+ }
9
+
10
+ export interface DataType {
11
+ label: string
12
+ value: string
13
+ }
@@ -0,0 +1,4 @@
1
+ import GeneralSetupDemo from './src/index.vue'
2
+
3
+ export default GeneralSetupDemo
4
+ export * from './src/typing'
@@ -0,0 +1,16 @@
1
+ import type { DataType } from './typing'
2
+
3
+ const data: Array<DataType> = [
4
+ {
5
+ label: '示例项1',
6
+ value: 'option-1',
7
+ },
8
+ {
9
+ label: '示例项2',
10
+ value: 'option-2',
11
+ },
12
+ ]
13
+
14
+ export {
15
+ data,
16
+ }
@@ -0,0 +1,11 @@
1
+ import { ref } from 'vue'
2
+ import type { GeneralSetupDemoProps } from './typing'
3
+
4
+ export const useGeneralSetupDemo = (props: GeneralSetupDemoProps) => {
5
+ const visible = ref(true)
6
+
7
+ return {
8
+ props,
9
+ visible,
10
+ }
11
+ }
@@ -0,0 +1,5 @@
1
+ .general-setup-demo {
2
+ position: absolute;
3
+ top: 0;
4
+ left: 0;
5
+ }
@@ -0,0 +1,20 @@
1
+ <template>
2
+ <div class="general-setup-demo"></div>
3
+ </template>
4
+
5
+ <script setup lang="ts">
6
+ import { GeneralSetupDemoProps } from './typing'
7
+ import { useGeneralSetupDemo } from './hook'
8
+
9
+ defineOptions({
10
+ name: 'GeneralSetupDemo',
11
+ })
12
+
13
+ const props = defineProps<GeneralSetupDemoProps>()
14
+
15
+ const {} = useGeneralSetupDemo(props)
16
+ </script>
17
+
18
+ <style lang="scss" scoped>
19
+ @import './index.scss';
20
+ </style>
@@ -0,0 +1,13 @@
1
+ export enum GeneralSetupDemoEnum {
2
+ ENUM_KEY1 = 'ENUM_KEY1',
3
+ ENUM_KEY2 = 'ENUM_KEY2',
4
+ }
5
+
6
+ export interface GeneralSetupDemoProps {
7
+ user?: Record<string, any>
8
+ }
9
+
10
+ export interface DataType {
11
+ label: string
12
+ value: string
13
+ }