From ab8cc9a073e76dd3bdf84a10886646266e6bd340 Mon Sep 17 00:00:00 2001 From: xueweihan <595666367@qq.com> Date: Tue, 5 Nov 2024 00:45:30 +0800 Subject: [PATCH] feat: add contribution ranking --- public/locales/en/rank.json | 13 ++ public/locales/zh/rank.json | 13 ++ src/components/rankTable/RankTable.tsx | 4 +- src/components/report/Report.tsx | 54 ++++++++ src/pages/report/contribution.tsx | 179 +++++++++++++++++++++++++ src/pages/report/db-engines.tsx | 3 + src/services/rank.ts | 20 +++ src/types/rank.ts | 2 + 8 files changed, 287 insertions(+), 1 deletion(-) create mode 100644 src/pages/report/contribution.tsx diff --git a/public/locales/en/rank.json b/public/locales/en/rank.json index fe973ab0..a103d8bb 100644 --- a/public/locales/en/rank.json +++ b/public/locales/en/rank.json @@ -44,5 +44,18 @@ "nginx": "A free, open-source, lightweight, high-performance web server developed by Igor Sysoev for Rambler.ru, the second most visited site in Russia.", "openresty": "A web platform based on Nginx that can run Lua scripts using its LuaJIT engine, created by Yichun Zhang." } + }, + "contribution": { + "title": "Contribution Ranking", + "nav": "{{month}} {{year}} Contribution Ranking", + "p_text": "HelloGitHub Contribution Ranking is a ranking based on the contribution value of users in the HelloGitHub open source community, updated with the current month's data on the 15th of each month. Users can earn contribution points in the following ways: sharing an open source project earns 5 points, posting a comment earns 2 points, and if a comment is selected as a hot comment, an additional 10 contribution points will be awarded.", + "thead": { + "position": "Rank", + "name": "User", + "rating": "Level", + "change": "Change", + "total": "Total", + "md_change": "📊" + } } } diff --git a/public/locales/zh/rank.json b/public/locales/zh/rank.json index 67ae8e74..b8c9c8f2 100644 --- a/public/locales/zh/rank.json +++ b/public/locales/zh/rank.json @@ -44,5 +44,18 @@ "nginx": "免费开源、轻量级、高性能 Web 服务器,由伊戈尔·赛索耶夫为俄罗斯访问量第二的 Rambler.ru 站点开发。", "openresty": "一个基于 Nginx 的 Web 平台,可以使用其 LuaJIT 引擎运行 Lua 脚本,由章亦春创建。" } + }, + "contribution": { + "title": "用户贡献排名", + "nav": "{{year}} 年 {{month}} 月用户贡献排行榜", + "p_text": "「HelloGitHub 贡献排名」是根据用户在 HelloGitHub 开源社区中的贡献值进行排名,每月 15 日更新当月的数据。用户可通过以下方式获得贡献值:分享开源项目可获得 5 点,发表评论获得 2 点,若评论被选为热评,则可额外获得 10 点贡献值。", + "thead": { + "position": "排名", + "name": "用户", + "rating": "等级", + "change": "对比上月", + "total": "总贡献", + "md_change": "本月" + } } } diff --git a/src/components/rankTable/RankTable.tsx b/src/components/rankTable/RankTable.tsx index 5c1c6557..3953735b 100644 --- a/src/components/rankTable/RankTable.tsx +++ b/src/components/rankTable/RankTable.tsx @@ -66,7 +66,7 @@ export const RankTable = ({ columns, list, i18n_lang }: TableProps) => { return ( {content} @@ -114,11 +114,13 @@ export const RankSearchBar = ({ return i18n_lang == 'en' ? [ { key: '/report/tiobe', value: 'Language' }, + { key: '/report/contribution', value: 'Contribution' }, { key: '/report/netcraft', value: 'Server' }, { key: '/report/db-engines', value: 'Database' }, ] : [ { key: '/report/tiobe', value: '编程语言' }, + { key: '/report/contribution', value: '用户贡献' }, { key: '/report/netcraft', value: '服务器' }, { key: '/report/db-engines', value: '数据库' }, ]; diff --git a/src/components/report/Report.tsx b/src/components/report/Report.tsx index 5cd2573d..5932803f 100644 --- a/src/components/report/Report.tsx +++ b/src/components/report/Report.tsx @@ -1,6 +1,8 @@ import { AiFillCaretDown, AiFillCaretUp } from 'react-icons/ai'; import { IoMdRemove, IoMdTrendingDown, IoMdTrendingUp } from 'react-icons/io'; +import { NoPrefetchLink } from '../links/CustomLink'; + import { RankDataItem } from '@/types/rank'; export const ChangeColumnRender = ( @@ -54,3 +56,55 @@ export const TrendColumnRender = ( } return {icon}; }; + +export const ContributionColumnRender = ( + row: RankDataItem, + i18n_lang: string +) => { + let text = '-'; + if (row.change !== null) { + text = `+${row.change}`; + } else { + return {text}; + } + + return ( +
+ {i18n_lang === 'en' ? ( + {text} + ) : ( + {text} + )} +
+ ); +}; + +export const UserColumnRender = (row: RankDataItem) => { + return ( + +
+ {`${row.name} + {row.name} +
+
+ ); +}; + +export const PositionColumnRender = (row: RankDataItem) => { + const positionList = ['🥇', '🥈', '🥉']; + return ( +
+ {row.position > 3 ? ( + {row.position} + ) : ( + {positionList[row.position - 1]} + )} +
+ ); +}; diff --git a/src/pages/report/contribution.tsx b/src/pages/report/contribution.tsx new file mode 100644 index 00000000..cdb477c5 --- /dev/null +++ b/src/pages/report/contribution.tsx @@ -0,0 +1,179 @@ +import { GetServerSideProps, NextPage } from 'next'; +import { useRouter } from 'next/router'; +import { Trans, useTranslation } from 'next-i18next'; +import { serverSideTranslations } from 'next-i18next/serverSideTranslations'; +import { useMemo } from 'react'; + +import Loading from '@/components/loading/Loading'; +import Navbar from '@/components/navbar/Navbar'; +import { + getMonthName, + RankSearchBar, + RankTable, +} from '@/components/rankTable/RankTable'; +import { + ContributionColumnRender, + PositionColumnRender, + UserColumnRender, +} from '@/components/report/Report'; +import Seo from '@/components/Seo'; + +import { getContributionRank } from '@/services/rank'; +import { getClientIP } from '@/utils/util'; + +import { RankPageProps } from '@/types/rank'; + +const ContributionPage: NextPage = ({ + year, + month, + monthList, + list, +}) => { + const { t, i18n } = useTranslation('rank'); + const router = useRouter(); + + const onSearch = (key: string, value: string) => { + if (key === 'month') { + router.push(`/report/contribution/?month=${value}`); + } + if (key === 'target') { + router.push(`${value}`); + } + }; + + // 排名 用户 等级 贡献值 对比上月 + const columns: any[] = useMemo( + () => [ + { + key: 'position', + title: t('contribution.thead.position'), + render: PositionColumnRender, + width: 80, + }, + { + key: 'name', + title: t('contribution.thead.name'), + render: UserColumnRender, + width: 180, + }, + { key: 'rating', title: t('contribution.thead.rating') }, + { + key: 'change', + title: t('contribution.thead.change'), + render: ContributionColumnRender, + }, + { + key: 'total', + title: t('contribution.thead.total'), + }, + ], + [i18n.language] + ); + + // 排名 用户 本月 贡献值 + const md_columns: any[] = useMemo( + () => + columns + .map((col) => { + if (col.key === 'position') { + return { ...col, width: 60 }; + } + if (col.key === 'name') { + return { ...col, width: 140 }; + } + if (col.key === 'change') { + return { + ...col, + title: t('contribution.thead.md_change'), + render: ContributionColumnRender, + width: 80, + }; + } + if (col.key === 'rating') { + return null; + } + return col; + }) + .filter(Boolean), + [i18n.language] + ); + + return ( + <> + + {list ? ( +
+ + +
+ +
+ +
+
+ +
+
+
+

+ +

+
+
+
+
+
+ ) : ( + + )} + + ); +}; + +export const getServerSideProps: GetServerSideProps = async ({ + query, + req, + locale, +}) => { + const ip = getClientIP(req); + const data = await getContributionRank( + ip, + query['month'] as unknown as number + ); + if (!data.success) { + return { + notFound: true, + }; + } else { + return { + props: { + year: data.year, + month: data.month, + list: data.data, + monthList: data.month_list, + ...(await serverSideTranslations(locale as string, ['common', 'rank'])), + }, + }; + } +}; + +export default ContributionPage; diff --git a/src/pages/report/db-engines.tsx b/src/pages/report/db-engines.tsx index e512a603..d26affa6 100644 --- a/src/pages/report/db-engines.tsx +++ b/src/pages/report/db-engines.tsx @@ -65,6 +65,9 @@ const DBEnginesPage: NextPage = ({ if (col.key === 'position') { return { ...col, width: 60 }; } + if (col.key === 'name') { + return { ...col, width: 160 }; + } if (col.key === 'rating') { return { ...col, width: 80 }; } diff --git a/src/services/rank.ts b/src/services/rank.ts index 9fa9f973..1fd7fb85 100644 --- a/src/services/rank.ts +++ b/src/services/rank.ts @@ -63,3 +63,23 @@ export const getDBRank = async ( return {} as RankData; } }; + +// 贡献值排名 +export const getContributionRank = async ( + ip: string, + month?: number +): Promise => { + const req: RequestInit = {}; + req.headers = { 'x-real-ip': ip, 'x-forwarded-for': ip }; + + let url = '/report/contribution/'; + if (month) { + url = `${url}?month=${month}`; + } + try { + const data = await fetcher(makeUrl(url), req); + return data; + } catch (error) { + return {} as RankData; + } +}; diff --git a/src/types/rank.ts b/src/types/rank.ts index ced16b9f..9599bc61 100644 --- a/src/types/rank.ts +++ b/src/types/rank.ts @@ -7,6 +7,8 @@ export interface RankDataItem { star?: string; percent?: boolean; total?: number; + avatar?: string; + uid?: string; } export interface RankData {