Skip to content

Commit c29d8cd

Browse files
authored
feat: add contributors section to documentation and scripts for automatic updates (#424)
* feat(contributors): add contributors section to documentation and scripts for automatic updates - Added a new `DocContributors` component to display contributors in documentation. - Updated multiple component documentation files to include a contributors section. - Introduced scripts for collecting and updating contributor data automatically. - Added a new entry in `package.json` for updating contributors. * chore(contributors): update contributor data and documentation paths - Refactored component paths in contributors data to include documentation and example directories. - Enhanced the script for collecting contributors to accommodate new documentation structure. * feat(contributors): enhance contributor aggregation in DocContributors component - Updated the logic to find contributors for a component by matching both exact names and partial names. - Implemented a deduplication and aggregation mechanism to combine contributions from multiple components. - Sorted contributors by contribution count before returning the list. * feat(contributors): increase max contributor count in DocContributors component - Updated the maximum contributor count displayed in the DocContributors component from 6 to 20 to enhance visibility of contributions.
1 parent 49a0ed5 commit c29d8cd

21 files changed

+8561
-0
lines changed

contributors/contributors.json

Lines changed: 3773 additions & 0 deletions
Large diffs are not rendered by default.

contributors/data.ts

Lines changed: 3824 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 379 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,379 @@
1+
<template>
2+
<div class="doc-contributors">
3+
<div class="contributors-header">
4+
<h3 class="contributors-title">
5+
<svg class="title-icon" viewBox="0 0 16 16" width="18" height="18">
6+
<path fill="currentColor" d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z"/>
7+
</svg>
8+
贡献者
9+
</h3>
10+
</div>
11+
12+
<div class="contributors-list">
13+
<div
14+
v-for="contributor in contributorsList"
15+
:key="contributor.id"
16+
class="contributor-wrapper"
17+
@click="openProfile(contributor.html_url)"
18+
@mouseenter="showTooltip = contributor.id"
19+
@mouseleave="showTooltip = null"
20+
>
21+
<img
22+
:src="contributor.avatar_url"
23+
:alt="contributor.login"
24+
class="contributor-avatar"
25+
@error="handleImageError"
26+
/>
27+
<div
28+
v-if="showTooltip === contributor.id"
29+
class="contributor-tooltip"
30+
>
31+
{{ contributor.login }}
32+
</div>
33+
</div>
34+
35+
<!-- 加载状态 -->
36+
<div v-if="loading" class="loading-contributors">
37+
<div class="loading-text">
38+
正在加载贡献者信息...
39+
</div>
40+
</div>
41+
42+
<!-- 如果没有找到贡献者,显示默认信息 -->
43+
<div v-else-if="contributorsList.length === 0" class="no-contributors">
44+
<div class="no-contributors-text">
45+
暂无贡献者信息
46+
</div>
47+
</div>
48+
</div>
49+
</div>
50+
</template>
51+
52+
<script setup lang="ts">
53+
import { computed, onMounted, ref } from 'vue'
54+
55+
interface Contributor {
56+
login: string
57+
id: number
58+
avatar_url: string
59+
html_url: string
60+
contributions: number
61+
type: string
62+
}
63+
64+
interface FileContributor {
65+
file: string
66+
contributors: Contributor[]
67+
}
68+
69+
interface Props {
70+
componentName?: string
71+
maxCount?: number
72+
}
73+
74+
const props = withDefaults(defineProps<Props>(), {
75+
componentName: '',
76+
maxCount: 50
77+
})
78+
79+
// 加载实际的贡献者数据
80+
let contributorsData: any = null
81+
82+
// 动态导入贡献者数据
83+
const loadContributorsData = async () => {
84+
if (contributorsData) return contributorsData
85+
86+
try {
87+
// 在客户端环境下动态导入
88+
if (typeof window !== 'undefined') {
89+
const module = await import('../../../../contributors/data.ts')
90+
contributorsData = module.contributorsData
91+
return contributorsData
92+
} else {
93+
// 服务端渲染时返回空数据
94+
return {
95+
contributors: [],
96+
componentContributors: [],
97+
fileContributors: []
98+
}
99+
}
100+
} catch (error) {
101+
console.warn('Failed to load contributors data:', error)
102+
// 返回模拟数据作为后备
103+
return {
104+
contributors: [
105+
{
106+
login: 'wzc520pyfm',
107+
id: 69044080,
108+
avatar_url: 'https://avatars.githubusercontent.com/u/69044080?v=4',
109+
html_url: 'https://github.com/wzc520pyfm',
110+
contributions: 127,
111+
type: 'User'
112+
}
113+
],
114+
componentContributors: [],
115+
fileContributors: []
116+
}
117+
}
118+
}
119+
120+
// 响应式数据
121+
const allContributorsData = ref<any>(null)
122+
const loading = ref(true)
123+
const showTooltip = ref<number | null>(null)
124+
125+
// 过滤当前组件的贡献者
126+
const contributorsList = computed(() => {
127+
if (!allContributorsData.value || loading.value) {
128+
return []
129+
}
130+
131+
if (!props.componentName) {
132+
return allContributorsData.value.contributors?.slice(0, props.maxCount) || []
133+
}
134+
135+
// 优先使用新的 componentContributors 结构
136+
if (allContributorsData.value.componentContributors) {
137+
// 查找所有包含当前组件名的组件(包括完全匹配和包含关系)
138+
const matchingComponents = allContributorsData.value.componentContributors.filter((comp: any) => {
139+
const compName = comp.component.toLowerCase()
140+
const targetName = props.componentName.toLowerCase()
141+
142+
// 完全匹配或者组件名包含目标名称(如 prompts-docs 包含 prompts)
143+
return compName === targetName || compName.includes(targetName)
144+
})
145+
146+
if (matchingComponents.length > 0) {
147+
// 按贡献者 login 去重并合并贡献数
148+
const contributorsMap = new Map()
149+
150+
matchingComponents.forEach((componentData: any) => {
151+
componentData.contributors.forEach((contributor: Contributor) => {
152+
const existing = contributorsMap.get(contributor.login)
153+
if (existing) {
154+
// 累加贡献数
155+
existing.contributions += contributor.contributions
156+
} else {
157+
// 新增贡献者
158+
contributorsMap.set(contributor.login, { ...contributor })
159+
}
160+
})
161+
})
162+
163+
// 按贡献度排序并返回
164+
return Array.from(contributorsMap.values())
165+
.sort((a, b) => b.contributions - a.contributions)
166+
.slice(0, props.maxCount)
167+
}
168+
}
169+
170+
// 回退到 fileContributors 结构(向后兼容)
171+
if (allContributorsData.value.fileContributors) {
172+
const componentFiles = allContributorsData.value.fileContributors.filter((fc: FileContributor) => {
173+
const fileName = fc.file.toLowerCase()
174+
const componentName = props.componentName.toLowerCase()
175+
176+
return fileName.includes(`src/${componentName}/`) ||
177+
fileName.includes(`/${componentName}.vue`) ||
178+
fileName.includes(`/${componentName}header.vue`) ||
179+
fileName.endsWith(`${componentName}/index.vue`)
180+
})
181+
182+
// 合并所有相关文件的贡献者
183+
const contributorsMap = new Map()
184+
185+
componentFiles.forEach((fileContrib: FileContributor) => {
186+
fileContrib.contributors.forEach((contributor: Contributor) => {
187+
const existing = contributorsMap.get(contributor.login)
188+
if (existing) {
189+
existing.contributions += contributor.contributions
190+
} else {
191+
contributorsMap.set(contributor.login, { ...contributor })
192+
}
193+
})
194+
})
195+
196+
if (contributorsMap.size > 0) {
197+
return Array.from(contributorsMap.values())
198+
.sort((a, b) => b.contributions - a.contributions)
199+
.slice(0, props.maxCount)
200+
}
201+
}
202+
203+
// 最后回退到总体贡献者
204+
return allContributorsData.value.contributors?.slice(0, props.maxCount) || []
205+
})
206+
207+
// 方法
208+
const openProfile = (url: string) => {
209+
window.open(url, '_blank')
210+
}
211+
212+
const handleImageError = (event: Event) => {
213+
const img = event.target as HTMLImageElement
214+
img.src = 'https://github.com/identicons/github.png'
215+
}
216+
217+
// 加载数据
218+
const loadData = async () => {
219+
try {
220+
loading.value = true
221+
const data = await loadContributorsData()
222+
allContributorsData.value = data
223+
console.log(`Contributors data loaded for component: ${props.componentName}`, data)
224+
} catch (error) {
225+
console.error('Failed to load contributors data:', error)
226+
} finally {
227+
loading.value = false
228+
}
229+
}
230+
231+
onMounted(async () => {
232+
console.log(`DocContributors mounted for component: ${props.componentName}`)
233+
await loadData()
234+
})
235+
</script>
236+
237+
<style scoped>
238+
.doc-contributors {
239+
margin-top: 48px;
240+
padding-top: 24px;
241+
border-top: 1px solid var(--vp-c-divider);
242+
}
243+
244+
.contributors-header {
245+
display: flex;
246+
align-items: center;
247+
justify-content: space-between;
248+
margin-bottom: 16px;
249+
}
250+
251+
.contributors-title {
252+
display: flex;
253+
align-items: center;
254+
margin: 0;
255+
font-size: 18px;
256+
font-weight: 600;
257+
color: var(--vp-c-text-1);
258+
}
259+
260+
.title-icon {
261+
margin-right: 8px;
262+
color: var(--vp-c-brand);
263+
flex-shrink: 0;
264+
}
265+
266+
.contributors-list {
267+
display: flex;
268+
flex-wrap: wrap;
269+
gap: 6px; /* Reduced gap */
270+
min-height: 60px;
271+
align-items: flex-start;
272+
}
273+
274+
.contributor-wrapper {
275+
position: relative;
276+
cursor: pointer;
277+
}
278+
279+
.contributor-wrapper:hover .contributor-avatar {
280+
border-color: var(--vp-c-brand);
281+
transform: translateY(-1px);
282+
}
283+
284+
.contributor-avatar {
285+
width: 32px;
286+
height: 32px;
287+
border-radius: 50%;
288+
border: 2px solid var(--vp-c-bg);
289+
transition: all 0.2s ease;
290+
flex-shrink: 0;
291+
display: block;
292+
}
293+
294+
.contributor-tooltip {
295+
position: absolute;
296+
top: -36px;
297+
left: 50%;
298+
transform: translateX(-50%);
299+
background-color: var(--vp-c-bg-alt);
300+
color: var(--vp-c-text-1);
301+
padding: 6px 10px;
302+
border-radius: 6px;
303+
font-size: 13px;
304+
font-weight: 500;
305+
white-space: nowrap;
306+
z-index: 1000;
307+
opacity: 1;
308+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
309+
border: 1px solid var(--vp-c-divider);
310+
pointer-events: none;
311+
animation: tooltip-fade-in 0.2s ease-out;
312+
}
313+
314+
.contributor-tooltip::after {
315+
content: '';
316+
position: absolute;
317+
top: 100%;
318+
left: 50%;
319+
transform: translateX(-50%);
320+
border: 5px solid transparent;
321+
border-top-color: var(--vp-c-bg-alt);
322+
}
323+
324+
@keyframes tooltip-fade-in {
325+
from {
326+
opacity: 0;
327+
transform: translateX(-50%) translateY(-4px);
328+
}
329+
to {
330+
opacity: 1;
331+
transform: translateX(-50%) translateY(0);
332+
}
333+
}
334+
335+
.loading-contributors {
336+
display: flex;
337+
align-items: center;
338+
justify-content: center;
339+
width: 100%;
340+
padding: 20px;
341+
color: var(--vp-c-text-2);
342+
font-size: 14px;
343+
}
344+
345+
.loading-text {
346+
opacity: 0.7;
347+
}
348+
349+
.no-contributors {
350+
display: flex;
351+
align-items: center;
352+
justify-content: center;
353+
width: 100%;
354+
padding: 20px;
355+
color: var(--vp-c-text-2);
356+
font-size: 14px;
357+
}
358+
359+
.no-contributors-text {
360+
opacity: 0.7;
361+
}
362+
363+
@media (max-width: 768px) {
364+
.contributors-header {
365+
flex-direction: column;
366+
align-items: flex-start;
367+
gap: 8px;
368+
}
369+
370+
.contributors-list {
371+
justify-content: center;
372+
}
373+
374+
.contributor-avatar {
375+
width: 28px;
376+
height: 28px;
377+
}
378+
}
379+
</style>

docs/.vitepress/vitepress/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
import type { Component } from 'vue'
22
import VPDemo from './components/vp-demo.vue'
33
import VPSemantic from './components/vp-semantic.vue'
4+
import DocContributors from './components/DocContributors.vue'
45

56
export const globals: [string, Component][] = [
67
['Demo', VPDemo],
78
['VpSemantic', VPSemantic],
9+
['DocContributors', DocContributors],
810
]

docs/component/attachments.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,3 +135,6 @@ type PresetIcons = 'default' | 'excel' | 'image' | 'markdown' | 'pdf' | 'ppt' |
135135
## 主题变量(Design Token)
136136

137137
<!-- <ComponentTokenTable component="Prompts"></ComponentTokenTable> -->
138+
## 贡献者
139+
140+
<doc-contributors component-name="attachments" :max-count="50" :show-view-all="true" />

0 commit comments

Comments
 (0)