diff --git a/app/components/content/GithubCard.vue b/app/components/content/GithubCard.vue index b254305..ae24fc9 100644 --- a/app/components/content/GithubCard.vue +++ b/app/components/content/GithubCard.vue @@ -1,70 +1,32 @@ + v-if="!pending && repoData" + class="github-card block rounded-xl p-3.5 md:p-4 bg-linear-to-b from-white/2 to-white/1 text-white/90 border border-white/4 shadow-lg hover:shadow-2xl hover:-translate-y-1.5 transition-all duration-180"> + class="flex flex-col gap-0 no-underline hover:no-underline text-inherit cursor-pointer"> {{ repoData.full_name }}{{ repoData.fullName }} @@ -75,21 +37,21 @@ const repoData = computed(() => data.value); >{{ repoData.language }} ★ {{ repoData.stargazers_count }}★ {{ repoData.convertStars }} ⑂ {{ repoData.forks_count }}⑂ {{ repoData.convertForks }} + class="shrink-0 flex items-center justify-center p-1 no-underline hover:no-underline text-inherit cursor-pointer hover:opacity-100 transition-all duration-180"> data.value); {{ repoData.license.name }} - · 更新于 {{ new Date(repoData.updated_at).toLocaleDateString() }}· 更新于 {{ new Date(repoData.updatedAt).toLocaleDateString() }} @@ -127,3 +89,13 @@ const repoData = computed(() => data.value); + + diff --git a/app/composables/useGithubRepo.ts b/app/composables/useGithubRepo.ts new file mode 100644 index 0000000..961e539 --- /dev/null +++ b/app/composables/useGithubRepo.ts @@ -0,0 +1,154 @@ +import type { MaybeRef, Ref } from "vue"; +import { useLocalStorage } from "@vueuse/core"; +import { computed, ref, toValue, watch } from "vue"; + +interface RepoLicense { + name: string; + url?: string; +} + +export interface RepoInfo { + name: string; + fullName: string; + description: string; + url: string; + stars: number; + forks: number; + convertStars: number | string; + convertForks: number | string; + watchers: number; + language: string; + languageColor: string; + archived: boolean; + visibility: "Private" | "Public"; + template: boolean; + ownerType: "User" | "Organization"; + license: RepoLicense | null; + updatedAt?: string; +} + +interface UseGithubRepoResult { + data: Ref; + loaded: Ref; +} + +const CACHE_KEY = "__CLOUDBLOG_GITHUB_REPO__"; +const CACHE_TTL = 6 * 60 * 60 * 1000; + +const storage = useLocalStorage( + CACHE_KEY, + {} as Record< + string, + { + info: RepoInfo; + updatedAt: number; + } + >, +); + +export function useGithubRepo( + repo: MaybeRef, + provider: MaybeRef<"github" | "gitee" | undefined> = "github", +): UseGithubRepoResult { + const repoRef = computed(() => { + const info = toValue(repo); + const [owner = "", name = ""] = info.split("/"); + return { owner: owner.trim(), name: name.trim() }; + }); + + const providerRef = computed(() => toValue(provider) ?? "github"); + + const data = ref(null); + const loaded = ref(false); + + const fetchData = async () => { + const { owner, name } = toValue(repoRef); + if (import.meta.server || !owner || !name) { + return; + } + + const key = `${providerRef.value === "github" ? "" : `${providerRef.value}:`}${owner}/${name}`; + const cached = storage.value[key]; + if (cached?.info?.name && Date.now() - cached.updatedAt <= CACHE_TTL) { + data.value = cached.info; + loaded.value = true; + return; + } + + loaded.value = false; + try { + const raw = await $fetch>( + `https://api.pengzhanbo.cn/${providerRef.value}/repo/${owner}/${name}`, + ); + + const res = normalizeRepoInfo(raw, `${owner}/${name}`); + res.convertStars = convertThousand(res.stars ?? 0); + res.convertForks = convertThousand(res.forks ?? 0); + + data.value = res; + loaded.value = true; + + storage.value[key] = { + info: res, + updatedAt: Date.now(), + }; + } catch (e) { + loaded.value = true; + console.error("github repo error:", e); + } + }; + + if (import.meta.client) { + watch([repoRef, providerRef], fetchData, { immediate: true }); + } + + return { data, loaded }; +} + +function convertThousand(num: number): number | string { + if (num < 1000) return num; + return `${(num / 1000).toFixed(1)}k`; +} + +function normalizeRepoInfo( + raw: RepoInfo | Record, + fallbackFullName: string, +): RepoInfo { + const r = raw as Record; + const fullName = (r.fullName ?? r.full_name ?? fallbackFullName) as string; + const name = (r.name ?? (typeof fullName === "string" ? fullName.split("/").at(1) : "")) as + | string + | undefined; + const stars = (r.stars ?? r.stargazers_count ?? 0) as number; + const forks = (r.forks ?? r.forks_count ?? 0) as number; + const watchers = (r.watchers ?? r.subscribers_count ?? 0) as number; + const url = (r.url ?? r.html_url ?? "") as string; + const description = (r.description ?? "") as string; + const language = (r.language ?? "") as string; + const licenseRaw = r.license as Record | null | undefined; + const licenseName = (licenseRaw?.name ?? "") as string; + const updatedAt = (r.updatedAt ?? r.updated_at) as string | undefined; + + const owner = r.owner as Record | null | undefined; + const ownerTypeValue = typeof owner?.type === "string" ? owner.type : undefined; + + return { + name: name ?? "", + fullName: fullName ?? "", + description, + url, + stars, + forks, + convertStars: convertThousand(stars), + convertForks: convertThousand(forks), + watchers, + language, + languageColor: (r.languageColor ?? "") as string, + archived: Boolean(r.archived ?? false), + visibility: (r.visibility ?? "Public") as "Private" | "Public", + template: Boolean(r.template ?? false), + ownerType: (r.ownerType ?? ownerTypeValue ?? "User") as "User" | "Organization", + license: licenseName ? { name: licenseName } : null, + updatedAt, + }; +} diff --git a/content/about.md b/content/about.md index db859cd..bd0deca 100644 --- a/content/about.md +++ b/content/about.md @@ -80,6 +80,7 @@ - [Vercel](https://vercel.com) - 部署平台 - [Cloudflare](https://cloudflare.com) - CDN 服务 / 部署平台 - [Tencent EdgeOne](https://edgeone.ai) - CDN 服务 / 部署平台 +- [Aliyun ESA](https://www.aliyun.com/product/esa) - CDN 服务 / 部署平台 - [Twikoo](https://twikoo.js.org) - 评论系统 - [Alpine-Starter](https://github.com/nuxt-themes/alpine-starter) - 主题模板 - [Clarity](https://github.com/L33Z22L11/blog-v3) - 博客部分灵感来源 diff --git a/content/posts/showcase/index.md b/content/posts/showcase/index.md index 18bd6fb..7782b17 100644 --- a/content/posts/showcase/index.md +++ b/content/posts/showcase/index.md @@ -31,7 +31,7 @@ categories: ["Development"] 下面使用新增的 `GithubCard` 组件来渲染一个公开仓库的信息(服务器端获取 GitHub API): - + 你也可以把它嵌入到任意文章中:``。 @@ -63,11 +63,7 @@ categories: ["Development"] ## 代码块与文件名 -支持带文件名的代码块: - -```bash [install.sh] -echo "示例安装脚本" -``` +支持带文件名的代码块 以及高亮语言: @@ -101,7 +97,3 @@ hello() | ---------- | ------------- | | GithubCard | 渲染仓库信息 | | Warning | 显示警告/提示 | - -## 结语 - -这篇示例文章覆盖了:组件嵌入(GithubCard、Warning、Alert)、命名槽与默认槽、代码块、图片、列表与表格。若需我把这篇文章在本地 dev 环境中打开并截图验证渲染,请告诉我运行命令(`pnpm dev` / `npm run dev` / `yarn dev`)。 diff --git a/eslint.config.mjs b/eslint.config.mjs index 3721f44..b7189ec 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -6,15 +6,6 @@ export default withNuxt([eslintPluginPrettierRecommended], { files: ["src/**/*.ts", "src/**/*.vue"], language: "vue", - "prettier/prettier": [ - "error", - { - fileInfoOptions: { - usePrettierrc: false, - }, - }, - ], - // rules: { // 'vue/html-self-closing': 'off', // },
{{ repoData.full_name }}{{ repoData.fullName }}