create-halo-plugin-template 1.0.0 → 1.0.2

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 (109) hide show
  1. package/.editorconfig +520 -0
  2. package/.github/workflows/cd.yaml +20 -0
  3. package/.github/workflows/ci.yaml +32 -0
  4. package/.github/workflows/publish-npm.yaml +46 -0
  5. package/.gitignore +83 -0
  6. package/LICENSE +674 -0
  7. package/README.md +191 -0
  8. package/bin/create-halo-plugin-template.js +3 -0
  9. package/build.gradle +103 -0
  10. package/docs/first-npm-release-checklist.md +58 -0
  11. package/docs/publish-template.md +148 -0
  12. package/docs/rsbuild-switch.md +90 -0
  13. package/docs/template-pruning.md +43 -0
  14. package/gradle/wrapper/gradle-wrapper.jar +0 -0
  15. package/gradle/wrapper/gradle-wrapper.properties +7 -0
  16. package/gradle.properties +1 -0
  17. package/gradlew +248 -0
  18. package/gradlew.bat +93 -0
  19. package/package.json +67 -7
  20. package/scripts/create-project.mjs +399 -0
  21. package/scripts/init-template.mjs +281 -0
  22. package/scripts/publish-check.mjs +97 -0
  23. package/scripts/release.mjs +278 -0
  24. package/scripts/verify-template.mjs +407 -0
  25. package/settings.gradle +7 -0
  26. package/src/main/java/run/halo/plugintemplate/PluginTemplatePlugin.java +43 -0
  27. package/src/main/java/run/halo/plugintemplate/config/PluginTemplateConfig.java +14 -0
  28. package/src/main/java/run/halo/plugintemplate/dto/PluginTemplateChecklistItem.java +30 -0
  29. package/src/main/java/run/halo/plugintemplate/dto/PluginTemplateFeatureItem.java +30 -0
  30. package/src/main/java/run/halo/plugintemplate/dto/PluginTemplateOverview.java +73 -0
  31. package/src/main/java/run/halo/plugintemplate/dto/PluginTemplateStatItem.java +30 -0
  32. package/src/main/java/run/halo/plugintemplate/endpoint/PluginTemplateConsoleEndpoint.java +33 -0
  33. package/src/main/java/run/halo/plugintemplate/endpoint/PluginTemplatePublicEndpoint.java +26 -0
  34. package/src/main/java/run/halo/plugintemplate/endpoint/PluginTemplateUcEndpoint.java +33 -0
  35. package/src/main/java/run/halo/plugintemplate/endpoint/routes/PluginTemplateOverviewRoutes.java +60 -0
  36. package/src/main/java/run/halo/plugintemplate/model/PluginTemplateAudience.java +23 -0
  37. package/src/main/java/run/halo/plugintemplate/query/PluginTemplateOverviewQuery.java +26 -0
  38. package/src/main/java/run/halo/plugintemplate/reconcile/PluginTemplateSettingsReconciler.java +17 -0
  39. package/src/main/java/run/halo/plugintemplate/scheme/PluginTemplateRecord.java +43 -0
  40. package/src/main/java/run/halo/plugintemplate/service/PluginTemplateOverviewService.java +10 -0
  41. package/src/main/java/run/halo/plugintemplate/service/impl/PluginTemplateOverviewServiceImpl.java +74 -0
  42. package/src/main/java/run/halo/plugintemplate/setting/PluginTemplateGeneralSetting.java +25 -0
  43. package/src/main/java/run/halo/plugintemplate/setting/PluginTemplateSettingKeys.java +24 -0
  44. package/src/main/java/run/halo/plugintemplate/setting/PluginTemplateUiSetting.java +13 -0
  45. package/src/main/java/run/halo/plugintemplate/utils/PluginTemplateSeeds.java +197 -0
  46. package/src/main/resources/extensions/roleTemplate-console.yaml +39 -0
  47. package/src/main/resources/extensions/roleTemplate-uc.yaml +19 -0
  48. package/src/main/resources/extensions/settings.yaml +47 -0
  49. package/src/main/resources/logo.png +0 -0
  50. package/src/main/resources/plugin.yaml +24 -0
  51. package/src/test/java/run/halo/plugintemplate/PluginTemplatePluginTest.java +34 -0
  52. package/src/test/java/run/halo/plugintemplate/service/impl/PluginTemplateOverviewServiceImplTest.java +97 -0
  53. package/ui/build.gradle +41 -0
  54. package/ui/env.d.ts +2 -0
  55. package/ui/eslint.config.ts +30 -0
  56. package/ui/package.json +57 -0
  57. package/ui/pnpm-lock.yaml +5250 -0
  58. package/ui/src/api/__tests__/normalizers.spec.ts +65 -0
  59. package/ui/src/api/generated/.openapi-generator/FILES +23 -0
  60. package/ui/src/api/generated/.openapi-generator/VERSION +1 -0
  61. package/ui/src/api/generated/.openapi-generator-ignore +23 -0
  62. package/ui/src/api/generated/api/plugin-template-console-api.ts +128 -0
  63. package/ui/src/api/generated/api/plugin-template-uc-api.ts +128 -0
  64. package/ui/src/api/generated/api.ts +19 -0
  65. package/ui/src/api/generated/base.ts +86 -0
  66. package/ui/src/api/generated/common.ts +150 -0
  67. package/ui/src/api/generated/configuration.ts +110 -0
  68. package/ui/src/api/generated/git_push.sh +57 -0
  69. package/ui/src/api/generated/index.ts +18 -0
  70. package/ui/src/api/generated/models/add-operation.ts +49 -0
  71. package/ui/src/api/generated/models/copy-operation.ts +49 -0
  72. package/ui/src/api/generated/models/index.ts +11 -0
  73. package/ui/src/api/generated/models/json-patch-inner.ts +41 -0
  74. package/ui/src/api/generated/models/move-operation.ts +49 -0
  75. package/ui/src/api/generated/models/plugin-template-checklist-item.ts +54 -0
  76. package/ui/src/api/generated/models/plugin-template-feature-item.ts +54 -0
  77. package/ui/src/api/generated/models/plugin-template-overview.ts +147 -0
  78. package/ui/src/api/generated/models/plugin-template-stat-item.ts +54 -0
  79. package/ui/src/api/generated/models/remove-operation.ts +43 -0
  80. package/ui/src/api/generated/models/replace-operation.ts +49 -0
  81. package/ui/src/api/generated/models/test-operation.ts +49 -0
  82. package/ui/src/api/index.ts +42 -0
  83. package/ui/src/api/normalizers.ts +65 -0
  84. package/ui/src/assets/element.scss +24 -0
  85. package/ui/src/assets/index.css +361 -0
  86. package/ui/src/assets/logo.svg +1 -0
  87. package/ui/src/components/PluginTemplateAttachmentTab.vue +69 -0
  88. package/ui/src/components/PluginTemplateCommonTable.vue +69 -0
  89. package/ui/src/components/PluginTemplateDashboardWidget.vue +62 -0
  90. package/ui/src/components/PluginTemplateOverviewPage.vue +254 -0
  91. package/ui/src/components/ui/PluginUiProvider.vue +40 -0
  92. package/ui/src/components/ui/UiMetricCard.vue +21 -0
  93. package/ui/src/components/ui/UiSectionCard.vue +25 -0
  94. package/ui/src/components/ui/UiStatusPill.vue +18 -0
  95. package/ui/src/composables/useTemplateOverview.ts +38 -0
  96. package/ui/src/index.ts +88 -0
  97. package/ui/src/lib/__tests__/plugin-ui.spec.ts +19 -0
  98. package/ui/src/lib/plugin-ui.ts +19 -0
  99. package/ui/src/lib/template.spec.ts +24 -0
  100. package/ui/src/lib/template.ts +52 -0
  101. package/ui/src/lib/theme.ts +31 -0
  102. package/ui/src/types/index.ts +59 -0
  103. package/ui/src/views/console/ConsoleDashboardView.vue +7 -0
  104. package/ui/src/views/uc/UcDashboardView.vue +7 -0
  105. package/ui/tsconfig.app.json +12 -0
  106. package/ui/tsconfig.json +14 -0
  107. package/ui/tsconfig.node.json +15 -0
  108. package/ui/tsconfig.vitest.json +11 -0
  109. package/ui/vite.config.ts +25 -0
@@ -0,0 +1,69 @@
1
+ <script setup lang="ts">
2
+ import { ElButton } from 'element-plus'
3
+ import logoUrl from '@/assets/logo.svg'
4
+
5
+ const props = withDefaults(
6
+ defineProps<{
7
+ selected?: unknown[]
8
+ }>(),
9
+ {
10
+ selected: () => [],
11
+ },
12
+ )
13
+
14
+ const emit = defineEmits<{
15
+ (event: 'update:selected', attachments: unknown[]): void
16
+ }>()
17
+
18
+ const assets = [
19
+ {
20
+ id: 'logo',
21
+ title: '模板 Logo',
22
+ description: '用作后台菜单图标、空状态或品牌占位。',
23
+ value: logoUrl,
24
+ },
25
+ {
26
+ id: 'docs',
27
+ title: '开发文档',
28
+ description: '示例外链资源,也可以替换成素材库或 CDN 地址。',
29
+ value: 'https://docs.halo.run/developer-guide/plugin/introduction',
30
+ },
31
+ ]
32
+
33
+ const selectAsset = (value: string) => {
34
+ emit('update:selected', [value])
35
+ }
36
+
37
+ const isSelected = (value: string) => props.selected.some((item) => item === value)
38
+ </script>
39
+
40
+ <template>
41
+ <div class="halo-plugin-template-admin-attachment-list">
42
+ <article
43
+ v-for="asset in assets"
44
+ :key="asset.id"
45
+ class="halo-plugin-template-admin-attachment-item"
46
+ >
47
+ <div class="halo-plugin-template-admin-attachment-preview">
48
+ <img v-if="asset.id === 'logo'" :src="asset.value" alt="Template asset" />
49
+ <span v-else>DOC</span>
50
+ </div>
51
+ <div>
52
+ <h3 class="halo-plugin-template-admin-card-title" style="font-size: 15px">
53
+ {{ asset.title }}
54
+ </h3>
55
+ <p class="halo-plugin-template-admin-card-description">
56
+ {{ asset.description }}
57
+ </p>
58
+ </div>
59
+ <div>
60
+ <ElButton
61
+ :type="isSelected(asset.value) ? 'success' : 'primary'"
62
+ @click="selectAsset(asset.value)"
63
+ >
64
+ {{ isSelected(asset.value) ? '已选择' : '选择资源' }}
65
+ </ElButton>
66
+ </div>
67
+ </article>
68
+ </div>
69
+ </template>
@@ -0,0 +1,69 @@
1
+ <script setup lang="ts">
2
+ import { ElEmpty, ElTable, ElTableColumn } from 'element-plus'
3
+ import type { ResponsiveColumn } from '@/types'
4
+ import { formatCellValue } from '@/lib/template'
5
+
6
+ const props = withDefaults(
7
+ defineProps<{
8
+ rows: Record<string, unknown>[]
9
+ columns: ResponsiveColumn[]
10
+ rowKey?: string
11
+ emptyDescription?: string
12
+ }>(),
13
+ {
14
+ rowKey: 'key',
15
+ emptyDescription: '暂无数据',
16
+ },
17
+ )
18
+
19
+ const resolveRowKey = (row: Record<string, unknown>) =>
20
+ String(row[props.rowKey] ?? row.id ?? row.title ?? Math.random())
21
+ </script>
22
+
23
+ <template>
24
+ <div>
25
+ <div class="halo-plugin-template-admin-table-desktop">
26
+ <ElTable :data="rows" style="width: 100%" :row-key="resolveRowKey">
27
+ <ElTableColumn
28
+ v-for="column in columns"
29
+ :key="column.key"
30
+ :prop="column.key"
31
+ :label="column.label"
32
+ :min-width="column.minWidth"
33
+ :align="column.align"
34
+ >
35
+ <template #default="{ row }">
36
+ <slot :name="`cell-${column.key}`" :row="row" :value="row[column.key]">
37
+ {{ formatCellValue(row[column.key]) }}
38
+ </slot>
39
+ </template>
40
+ </ElTableColumn>
41
+ </ElTable>
42
+ </div>
43
+
44
+ <div v-if="rows.length" class="halo-plugin-template-admin-table-mobile">
45
+ <article
46
+ v-for="row in rows"
47
+ :key="resolveRowKey(row)"
48
+ class="halo-plugin-template-admin-table-card"
49
+ >
50
+ <div
51
+ v-for="column in columns"
52
+ :key="column.key"
53
+ class="halo-plugin-template-admin-table-card-row"
54
+ >
55
+ <span class="halo-plugin-template-admin-table-card-label">{{ column.label }}</span>
56
+ <div class="halo-plugin-template-admin-table-card-value">
57
+ <slot :name="`cell-${column.key}`" :row="row" :value="row[column.key]">
58
+ {{ formatCellValue(row[column.key]) }}
59
+ </slot>
60
+ </div>
61
+ </div>
62
+ </article>
63
+ </div>
64
+
65
+ <div v-else class="halo-plugin-template-admin-table-mobile">
66
+ <ElEmpty :description="emptyDescription" />
67
+ </div>
68
+ </div>
69
+ </template>
@@ -0,0 +1,62 @@
1
+ <script setup lang="ts">
2
+ import { ElButton, ElSkeleton } from 'element-plus'
3
+ import { computed, onMounted, ref } from 'vue'
4
+ import { templateConsoleApi } from '@/api'
5
+ import type { PluginTemplateOverview } from '@/types'
6
+
7
+ const loading = ref(true)
8
+ const overview = ref<PluginTemplateOverview | null>(null)
9
+
10
+ const load = async () => {
11
+ loading.value = true
12
+ try {
13
+ overview.value = await templateConsoleApi.getOverview()
14
+ } finally {
15
+ loading.value = false
16
+ }
17
+ }
18
+
19
+ const stats = computed(() => overview.value?.stats?.slice(0, 2) ?? [])
20
+
21
+ const openConsole = () => {
22
+ window.location.assign(`/console${overview.value?.consolePath || '/halo-plugin-template'}`)
23
+ }
24
+
25
+ onMounted(load)
26
+ </script>
27
+
28
+ <template>
29
+ <div class="halo-plugin-template-admin-widget">
30
+ <div class="halo-plugin-template-admin-widget-header">
31
+ <div>
32
+ <h3 class="halo-plugin-template-admin-widget-title">模板工作台</h3>
33
+ <p class="halo-plugin-template-admin-widget-description">
34
+ 直接复用模板默认 API、路由和扩展点,作为新插件的第一块控制台仪表盘。
35
+ </p>
36
+ </div>
37
+ <ElButton type="primary" plain @click="openConsole">
38
+ 打开页面
39
+ </ElButton>
40
+ </div>
41
+
42
+ <ElSkeleton :loading="loading" animated :rows="3">
43
+ <template #default>
44
+ <div class="halo-plugin-template-admin-widget-stats">
45
+ <div
46
+ v-for="item in stats"
47
+ :key="item.key"
48
+ class="halo-plugin-template-admin-widget-stat"
49
+ >
50
+ <p class="halo-plugin-template-admin-stat-label">{{ item.label }}</p>
51
+ <p class="halo-plugin-template-admin-card-title" style="margin-top: 6px">
52
+ {{ item.value }}
53
+ </p>
54
+ <p class="halo-plugin-template-admin-card-description" style="margin-top: 8px">
55
+ {{ item.helper }}
56
+ </p>
57
+ </div>
58
+ </div>
59
+ </template>
60
+ </ElSkeleton>
61
+ </div>
62
+ </template>
@@ -0,0 +1,254 @@
1
+ <script setup lang="ts">
2
+ import { stores, utils } from '@halo-dev/ui-shared'
3
+ import { ElAlert, ElButton, ElLink, ElSkeleton } from 'element-plus'
4
+ import { storeToRefs } from 'pinia'
5
+ import { computed, onMounted } from 'vue'
6
+ import { buildAudienceLabel, checklistColumns, featureColumns } from '@/lib/template'
7
+ import { useTemplateOverview } from '@/composables/useTemplateOverview'
8
+ import PluginTemplateCommonTable from './PluginTemplateCommonTable.vue'
9
+ import PluginUiProvider from './ui/PluginUiProvider.vue'
10
+ import UiMetricCard from './ui/UiMetricCard.vue'
11
+ import UiSectionCard from './ui/UiSectionCard.vue'
12
+ import UiStatusPill from './ui/UiStatusPill.vue'
13
+
14
+ const props = defineProps<{
15
+ audience: 'console' | 'uc'
16
+ }>()
17
+
18
+ const { overview, loading, errorMessage, stats, features, checklist, load } =
19
+ useTemplateOverview(props.audience)
20
+
21
+ const { currentUser } = storeToRefs(stores.currentUser())
22
+ const { globalInfo } = storeToRefs(stores.globalInfo())
23
+
24
+ const viewerName = computed(
25
+ () =>
26
+ currentUser.value?.user?.spec?.displayName ||
27
+ currentUser.value?.user?.metadata?.name ||
28
+ 'Halo Developer',
29
+ )
30
+
31
+ const siteTitle = computed(() => globalInfo.value?.siteTitle || 'Halo')
32
+ const buildTime = computed(() => utils.date.format(new Date(), 'YYYY-MM-DD HH:mm'))
33
+ const audienceLabel = computed(() => buildAudienceLabel(props.audience))
34
+ const rsbuildGuideLink = 'https://docs.halo.run/developer-guide/plugin/basics/ui/build'
35
+ const currentPath = computed(() =>
36
+ props.audience === 'console' ? overview.value?.consolePath || '/halo-plugin-template' : overview.value?.ucPath || '/halo-plugin-template',
37
+ )
38
+ const supportLink = computed(
39
+ () => overview.value?.supportLink || 'https://docs.halo.run/developer-guide/plugin/introduction',
40
+ )
41
+ const densityLabel = computed(() => {
42
+ if (overview.value?.density === 'compact') {
43
+ return '紧凑'
44
+ }
45
+ if (overview.value?.density === 'relaxed') {
46
+ return '宽松'
47
+ }
48
+ return '平衡'
49
+ })
50
+
51
+ const openExternal = (url: string) => {
52
+ window.open(url, '_blank')
53
+ }
54
+
55
+ const densityTone = computed(() => overview.value?.density || 'balanced')
56
+ const attachmentState = computed(() =>
57
+ overview.value?.enableAttachmentProvider ? '附件已启用' : '附件已关闭',
58
+ )
59
+ const attachmentTone = computed(() =>
60
+ overview.value?.enableAttachmentProvider ? 'success' : 'warning',
61
+ )
62
+
63
+ onMounted(load)
64
+ </script>
65
+
66
+ <template>
67
+ <PluginUiProvider
68
+ :audience="audience"
69
+ :density="overview?.density"
70
+ :accent-color="overview?.accentColor"
71
+ >
72
+ <section class="halo-plugin-template-admin-page">
73
+ <header class="halo-plugin-template-admin-hero">
74
+ <div>
75
+ <p class="halo-plugin-template-admin-eyebrow">{{ siteTitle }} · {{ audienceLabel }}</p>
76
+ <h1 class="halo-plugin-template-admin-title">
77
+ {{ overview?.displayName || 'Halo Plugin Template' }}
78
+ </h1>
79
+ <p class="halo-plugin-template-admin-description">
80
+ 这不是单纯的欢迎页,而是一份可重复初始化的 Halo 插件 starter:
81
+ 默认接好 Console / UC / Element Plus / 扩展点 / OpenAPI。
82
+ </p>
83
+ <div class="halo-plugin-template-admin-meta">
84
+ <span class="halo-plugin-template-admin-meta-item">
85
+ 当前查看者:{{ viewerName }}
86
+ </span>
87
+ <span class="halo-plugin-template-admin-meta-item">
88
+ 构建时间:{{ buildTime }}
89
+ </span>
90
+ <span class="halo-plugin-template-admin-meta-item">
91
+ 路由前缀:{{ currentPath }}
92
+ </span>
93
+ </div>
94
+ </div>
95
+
96
+ <div class="halo-plugin-template-admin-hero-actions">
97
+ <ElButton type="primary" @click="openExternal(supportLink)">
98
+ Halo 插件文档
99
+ </ElButton>
100
+ <ElButton plain @click="openExternal(rsbuildGuideLink)">
101
+ UI 构建说明
102
+ </ElButton>
103
+ </div>
104
+ </header>
105
+
106
+ <ElAlert
107
+ v-if="errorMessage"
108
+ title="模板概览接口加载失败"
109
+ :description="errorMessage"
110
+ type="warning"
111
+ show-icon
112
+ :closable="false"
113
+ />
114
+
115
+ <ElSkeleton :loading="loading" animated :rows="6">
116
+ <template #default>
117
+ <div class="halo-plugin-template-admin-stats">
118
+ <UiMetricCard
119
+ v-for="item in stats"
120
+ :key="item.key"
121
+ :label="item.label"
122
+ :value="item.value"
123
+ :helper="item.helper"
124
+ :tone="item.tone"
125
+ />
126
+ </div>
127
+
128
+ <section class="halo-plugin-template-admin-grid">
129
+ <UiSectionCard
130
+ title="初始化建议"
131
+ description="把模板里的默认值替换干净,再继续堆业务代码。"
132
+ >
133
+ <div class="halo-plugin-template-admin-checklist">
134
+ <article
135
+ v-for="item in checklist.slice(0, 3)"
136
+ :key="item.key"
137
+ class="halo-plugin-template-admin-checklist-item"
138
+ >
139
+ <div class="halo-plugin-template-admin-checklist-copy">
140
+ <h3 class="halo-plugin-template-admin-checklist-title">{{ item.title }}</h3>
141
+ <p class="halo-plugin-template-admin-checklist-description">
142
+ {{ item.description }}
143
+ </p>
144
+ </div>
145
+ <UiStatusPill :label="item.status" :tone="item.status" />
146
+ </article>
147
+ </div>
148
+ </UiSectionCard>
149
+
150
+ <UiSectionCard
151
+ title="当前上下文"
152
+ description="这些值可以直接帮助你确认模板是否已经被成功初始化。"
153
+ >
154
+ <div class="halo-plugin-template-admin-kpis">
155
+ <div class="halo-plugin-template-admin-checklist-item">
156
+ <div class="halo-plugin-template-admin-checklist-copy">
157
+ <h3 class="halo-plugin-template-admin-checklist-title">Setting 资源</h3>
158
+ <p class="halo-plugin-template-admin-checklist-description">
159
+ {{ overview?.settingName }}
160
+ </p>
161
+ </div>
162
+ <UiStatusPill label="已接线" tone="success" />
163
+ </div>
164
+ <div class="halo-plugin-template-admin-checklist-item">
165
+ <div class="halo-plugin-template-admin-checklist-copy">
166
+ <h3 class="halo-plugin-template-admin-checklist-title">ConfigMap 资源</h3>
167
+ <p class="halo-plugin-template-admin-checklist-description">
168
+ {{ overview?.configMapName }}
169
+ </p>
170
+ </div>
171
+ <UiStatusPill label="已接线" tone="success" />
172
+ </div>
173
+ <div class="halo-plugin-template-admin-checklist-item">
174
+ <div class="halo-plugin-template-admin-checklist-copy">
175
+ <h3 class="halo-plugin-template-admin-checklist-title">API 输出目录</h3>
176
+ <p class="halo-plugin-template-admin-checklist-description">
177
+ {{ overview?.generatedClientPath }}
178
+ </p>
179
+ </div>
180
+ <UiStatusPill label="已接线" tone="success" />
181
+ </div>
182
+ <div class="halo-plugin-template-admin-checklist-item">
183
+ <div class="halo-plugin-template-admin-checklist-copy">
184
+ <h3 class="halo-plugin-template-admin-checklist-title">页面密度</h3>
185
+ <p class="halo-plugin-template-admin-checklist-description">
186
+ {{ densityLabel }}
187
+ </p>
188
+ </div>
189
+ <UiStatusPill :label="densityTone" tone="info" />
190
+ </div>
191
+ <div class="halo-plugin-template-admin-checklist-item">
192
+ <div class="halo-plugin-template-admin-checklist-copy">
193
+ <h3 class="halo-plugin-template-admin-checklist-title">主色</h3>
194
+ <p class="halo-plugin-template-admin-checklist-description">
195
+ {{ overview?.accentColor || '#2457F5' }}
196
+ </p>
197
+ </div>
198
+ <UiStatusPill :label="attachmentState" :tone="attachmentTone" />
199
+ </div>
200
+ </div>
201
+ </UiSectionCard>
202
+ </section>
203
+
204
+ <UiSectionCard
205
+ title="功能矩阵"
206
+ description="这张表列出模板默认预置的前后端能力,你可以在初始化后按需删减。"
207
+ >
208
+ <PluginTemplateCommonTable
209
+ :rows="features"
210
+ :columns="featureColumns"
211
+ empty-description="暂无功能矩阵"
212
+ >
213
+ <template #cell-enabled="{ value }">
214
+ <UiStatusPill :label="value ? '已启用' : '按需接入'" :tone="value ? 'success' : 'info'" />
215
+ </template>
216
+ </PluginTemplateCommonTable>
217
+ </UiSectionCard>
218
+
219
+ <UiSectionCard
220
+ title="初始化清单"
221
+ description="前端和后端都通过统一的检查清单推进,避免漏掉 settings、角色模板和 API 包装层。"
222
+ >
223
+ <PluginTemplateCommonTable
224
+ :rows="checklist"
225
+ :columns="checklistColumns"
226
+ empty-description="暂无初始化清单"
227
+ >
228
+ <template #cell-status="{ value }">
229
+ <UiStatusPill :label="String(value)" :tone="String(value)" />
230
+ </template>
231
+ </PluginTemplateCommonTable>
232
+ </UiSectionCard>
233
+
234
+ <UiSectionCard
235
+ title="下一步"
236
+ description="模板的真实价值在于后续多个插件项目都沿用同一套工程边界。"
237
+ >
238
+ <ul style="margin: 0; padding-left: 18px; color: var(--halo-plugin-template-text-secondary); line-height: 1.8">
239
+ <li>先运行初始化脚本,再修改 `plugin.yaml`、`settings.yaml`、角色模板和 Java 包名。</li>
240
+ <li>初始化后执行 `node scripts/verify-template.mjs`,确认插件名、包名、权限前缀和文档入口已经全部收敛。</li>
241
+ <li>新接口补好 Springdoc 后,执行 `./gradlew generateApiClient`,再在 `ui/src/api/index.ts` 暴露新增能力。</li>
242
+ <li>按插件实际范围裁剪 UC、附件扩展和工作台部件,避免模板示例残留到正式项目。</li>
243
+ <li>
244
+ 如需切换到 Rsbuild,可参考
245
+ <ElLink :href="rsbuildGuideLink" target="_blank">Halo 官方 UI 构建说明</ElLink>
246
+ 和仓库内的 `docs/rsbuild-switch.md`。
247
+ </li>
248
+ </ul>
249
+ </UiSectionCard>
250
+ </template>
251
+ </ElSkeleton>
252
+ </section>
253
+ </PluginUiProvider>
254
+ </template>
@@ -0,0 +1,40 @@
1
+ <script setup lang="ts">
2
+ import zhCn from 'element-plus/es/locale/lang/zh-cn'
3
+ import { ElConfigProvider } from 'element-plus'
4
+ import { computed } from 'vue'
5
+ import { buildShellStyles, resolveElementSize } from '@/lib/plugin-ui'
6
+ import { useDocumentTheme } from '@/lib/theme'
7
+
8
+ const props = withDefaults(
9
+ defineProps<{
10
+ audience?: 'console' | 'uc'
11
+ density?: string
12
+ accentColor?: string
13
+ }>(),
14
+ {
15
+ audience: 'console',
16
+ density: 'balanced',
17
+ accentColor: '',
18
+ },
19
+ )
20
+
21
+ const { isDark } = useDocumentTheme()
22
+
23
+ const elementSize = computed(() => resolveElementSize(props.density))
24
+
25
+ const classes = computed(() => [
26
+ 'halo-plugin-template-admin-shell',
27
+ `halo-plugin-template-admin-shell--${props.audience}`,
28
+ { dark: isDark.value },
29
+ ])
30
+
31
+ const styles = computed(() => buildShellStyles(props.accentColor))
32
+ </script>
33
+
34
+ <template>
35
+ <ElConfigProvider :locale="zhCn" :size="elementSize" :z-index="2100">
36
+ <div :class="classes" :style="styles">
37
+ <slot />
38
+ </div>
39
+ </ElConfigProvider>
40
+ </template>
@@ -0,0 +1,21 @@
1
+ <script setup lang="ts">
2
+ import UiStatusPill from './UiStatusPill.vue'
3
+
4
+ defineProps<{
5
+ label: string
6
+ value: string
7
+ helper: string
8
+ tone?: string
9
+ }>()
10
+ </script>
11
+
12
+ <template>
13
+ <article class="halo-plugin-template-admin-stat">
14
+ <p class="halo-plugin-template-admin-stat-label">{{ label }}</p>
15
+ <p class="halo-plugin-template-admin-stat-value">{{ value }}</p>
16
+ <p class="halo-plugin-template-admin-stat-helper">{{ helper }}</p>
17
+ <div style="margin-top: 14px">
18
+ <UiStatusPill :label="label" :tone="tone" />
19
+ </div>
20
+ </article>
21
+ </template>
@@ -0,0 +1,25 @@
1
+ <script setup lang="ts">
2
+ import { ElCard } from 'element-plus'
3
+
4
+ defineProps<{
5
+ title: string
6
+ description?: string
7
+ }>()
8
+ </script>
9
+
10
+ <template>
11
+ <ElCard class="halo-plugin-template-admin-surface" shadow="never">
12
+ <div class="halo-plugin-template-admin-card-header">
13
+ <div>
14
+ <h2 class="halo-plugin-template-admin-card-title">{{ title }}</h2>
15
+ <p v-if="description" class="halo-plugin-template-admin-card-description">
16
+ {{ description }}
17
+ </p>
18
+ </div>
19
+ <slot name="actions" />
20
+ </div>
21
+ <div style="margin-top: 20px">
22
+ <slot />
23
+ </div>
24
+ </ElCard>
25
+ </template>
@@ -0,0 +1,18 @@
1
+ <script setup lang="ts">
2
+ import { ElTag } from 'element-plus'
3
+ import { computed } from 'vue'
4
+ import { toTagType } from '@/lib/template'
5
+
6
+ const props = defineProps<{
7
+ label: string
8
+ tone?: string
9
+ }>()
10
+
11
+ const tagType = computed(() => toTagType(props.tone))
12
+ </script>
13
+
14
+ <template>
15
+ <ElTag :type="tagType" round effect="light">
16
+ {{ label }}
17
+ </ElTag>
18
+ </template>
@@ -0,0 +1,38 @@
1
+ import { computed, ref } from 'vue'
2
+ import { templateConsoleApi, templateUcApi } from '@/api'
3
+ import type { PluginTemplateOverview } from '@/types'
4
+
5
+ type Audience = 'console' | 'uc'
6
+
7
+ export const useTemplateOverview = (audience: Audience) => {
8
+ const overview = ref<PluginTemplateOverview | null>(null)
9
+ const loading = ref(false)
10
+ const errorMessage = ref('')
11
+
12
+ const load = async () => {
13
+ loading.value = true
14
+ errorMessage.value = ''
15
+
16
+ try {
17
+ const response =
18
+ audience === 'console'
19
+ ? await templateConsoleApi.getOverview()
20
+ : await templateUcApi.getOverview()
21
+ overview.value = response
22
+ } catch (error) {
23
+ errorMessage.value = error instanceof Error ? error.message : '模板概览加载失败'
24
+ } finally {
25
+ loading.value = false
26
+ }
27
+ }
28
+
29
+ return {
30
+ overview,
31
+ loading,
32
+ errorMessage,
33
+ stats: computed(() => overview.value?.stats ?? []),
34
+ features: computed(() => overview.value?.features ?? []),
35
+ checklist: computed(() => overview.value?.checklist ?? []),
36
+ load,
37
+ }
38
+ }