From c7aab0f6abe11f03b4c68da21702b4ba251610da Mon Sep 17 00:00:00 2001 From: Old_cpu <1471913775@qq.com> Date: Wed, 30 Sep 2026 22:39:21 +0800 Subject: [PATCH 1/5] =?UTF-8?q?feat:=20=E9=A6=96=E9=A1=B5=E5=85=AC?= =?UTF-8?q?=E5=91=8A=E5=8D=A1=E7=89=87=E5=8C=BA=E4=B8=8E=E5=88=9B=E5=BB=BA?= =?UTF-8?q?=E5=85=AC=E5=91=8A=E8=A1=A8=E5=8D=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 scripts/gen-announcements.mjs:从公告页 md 自动提取数据生成 JSON,predev/prebuild 钩子自动同步 - 新增 AnnouncementCards 组件:首页展示最新 6 条公告卡片,滚动交错浮入动画,点击跳转原文 - theme/index.tsx 覆写 HomeLayout,经 afterFeatures 插槽挂载(仅中文站) - 新增 /announcement/create 表单页:填写链接+标题实时预览卡片,生成 Markdown 片段,一键复制并跳转 GitHub 编辑页提交 PR - 首页公告区新增「创建公告」入口按钮 - 全部样式使用 Rspress 主题变量,深浅色自适应,prefers-reduced-motion 下关闭动画 --- docs/components/AnnouncementCards.css | 256 +++++++++++++++++++++++++ docs/components/AnnouncementCards.tsx | 101 ++++++++++ docs/components/CreateAnnouncement.css | 180 +++++++++++++++++ docs/components/CreateAnnouncement.tsx | 175 +++++++++++++++++ docs/components/announcements.json | 194 +++++++++++++++++++ docs/zh/announcement/create.mdx | 7 + package.json | 2 + scripts/gen-announcements.mjs | 55 ++++++ theme/index.tsx | 15 +- 9 files changed, 982 insertions(+), 3 deletions(-) create mode 100644 docs/components/AnnouncementCards.css create mode 100644 docs/components/AnnouncementCards.tsx create mode 100644 docs/components/CreateAnnouncement.css create mode 100644 docs/components/CreateAnnouncement.tsx create mode 100644 docs/components/announcements.json create mode 100644 docs/zh/announcement/create.mdx create mode 100644 scripts/gen-announcements.mjs diff --git a/docs/components/AnnouncementCards.css b/docs/components/AnnouncementCards.css new file mode 100644 index 0000000..ef0db2e --- /dev/null +++ b/docs/components/AnnouncementCards.css @@ -0,0 +1,256 @@ +/* 首页公告卡片区 —— 全部使用 Rspress 主题变量,浅色/深色自动适配 */ + +.ann-section { + max-width: 1152px; + margin: 0 auto; + padding: 48px 24px 64px; +} + +/* ---------- 头部 ---------- */ +.ann-header { + display: flex; + align-items: flex-end; + justify-content: space-between; + gap: 16px; + margin-bottom: 28px; +} + +.ann-eyebrow { + display: block; + font-size: 12px; + font-weight: 600; + letter-spacing: 0.14em; + color: var(--rp-c-brand); + margin-bottom: 6px; +} + +.ann-title { + margin: 0; + font-size: 28px; + font-weight: 700; + line-height: 1.3; + color: var(--rp-c-text-1); +} + +.ann-subtitle { + margin: 6px 0 0; + font-size: 14px; + color: var(--rp-c-text-2); +} + +.ann-more { + flex-shrink: 0; + font-size: 14px; + font-weight: 500; + color: var(--rp-c-brand); + text-decoration: none; + padding: 8px 16px; + border: 1px solid var(--rp-c-divider); + border-radius: 999px; + transition: border-color 0.25s ease, background-color 0.25s ease; +} + +.ann-more:hover { + border-color: var(--rp-c-brand); + background-color: var(--rp-c-bg-soft); +} + +.ann-more-arrow { + display: inline-block; + margin-left: 6px; + transition: transform 0.25s ease; +} + +.ann-more:hover .ann-more-arrow { + transform: translateX(3px); +} + +.ann-actions { + display: flex; + align-items: center; + gap: 12px; + flex-shrink: 0; +} + +.ann-create { + display: inline-flex; + align-items: center; + gap: 6px; + font-size: 14px; + font-weight: 600; + color: var(--rp-c-bg); + background-color: var(--rp-c-brand); + text-decoration: none; + padding: 8px 16px; + border-radius: 999px; + transition: opacity 0.25s ease, transform 0.25s ease; +} + +.ann-create:hover { + opacity: 0.88; + transform: translateY(-1px); +} + +/* ---------- 卡片网格 ---------- */ +.ann-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); + gap: 16px; +} + +.ann-card { + --ann-lift: 0px; + display: flex; + flex-direction: column; + gap: 12px; + padding: 20px; + min-height: 148px; + border: 1px solid var(--rp-c-divider); + border-radius: 12px; + background-color: var(--rp-c-bg-soft); + text-decoration: none; + transform: translateY(var(--ann-lift)); + transition: + transform 0.3s cubic-bezier(0.22, 1, 0.36, 1), + border-color 0.3s ease, + box-shadow 0.3s ease; +} + +.ann-card:hover { + --ann-lift: -4px; + border-color: var(--rp-c-brand); + box-shadow: 0 8px 24px rgba(0, 0, 0, 0.08); +} + +:root.dark .ann-card:hover { + box-shadow: 0 8px 24px rgba(0, 0, 0, 0.35); +} + +.ann-card-top { + display: flex; + align-items: center; + justify-content: space-between; + gap: 8px; +} + +.ann-date { + font-size: 13px; + font-weight: 600; + font-variant-numeric: tabular-nums; + color: var(--rp-c-brand); +} + +.ann-badge { + font-size: 12px; + font-weight: 500; + line-height: 1; + padding: 5px 10px; + border-radius: 999px; + white-space: nowrap; +} + +.ann-badge-wechat { + color: #07c160; + background-color: rgba(7, 193, 96, 0.1); +} + +.ann-badge-gitee { + color: #c71d23; + background-color: rgba(199, 29, 35, 0.08); +} + +.ann-badge-other { + color: var(--rp-c-text-2); + background-color: var(--rp-c-bg-mute); +} + +.ann-card-title { + flex: 1; + margin: 0; + font-size: 16px; + font-weight: 600; + line-height: 1.5; + color: var(--rp-c-text-1); + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + overflow: hidden; + transition: color 0.25s ease; +} + +.ann-card:hover .ann-card-title { + color: var(--rp-c-brand); +} + +.ann-card-foot { + display: flex; + align-items: center; + justify-content: space-between; + font-size: 13px; + color: var(--rp-c-text-2); +} + +.ann-external { + transition: transform 0.25s ease; +} + +.ann-card:hover .ann-external { + transform: translate(2px, -2px); + color: var(--rp-c-brand); +} + +/* ---------- 滚动进入动画:卡片依次浮入 ---------- */ +.ann-card { + opacity: 0; + translate: 0 28px; +} + +.ann-revealed .ann-card { + opacity: 1; + translate: 0 0; + transition: + opacity 0.55s cubic-bezier(0.22, 1, 0.36, 1), + translate 0.55s cubic-bezier(0.22, 1, 0.36, 1), + transform 0.3s cubic-bezier(0.22, 1, 0.36, 1), + border-color 0.3s ease, + box-shadow 0.3s ease; + transition-delay: + calc(var(--ann-i) * 70ms), + calc(var(--ann-i) * 70ms), + 0ms, + 0ms, + 0ms; +} + +/* 无障碍:减少动态偏好下直接呈现 */ +@media (prefers-reduced-motion: reduce) { + .ann-card { + opacity: 1; + translate: none; + transition: none; + } + + .ann-revealed .ann-card { + transition-delay: 0ms; + } +} + +/* ---------- 窄屏 ---------- */ +@media (max-width: 640px) { + .ann-section { + padding: 32px 20px 48px; + } + + .ann-header { + flex-direction: column; + align-items: flex-start; + } + + .ann-title { + font-size: 24px; + } + + .ann-grid { + grid-template-columns: 1fr; + } +} diff --git a/docs/components/AnnouncementCards.tsx b/docs/components/AnnouncementCards.tsx new file mode 100644 index 0000000..650e913 --- /dev/null +++ b/docs/components/AnnouncementCards.tsx @@ -0,0 +1,101 @@ +import { useEffect, useRef, useState } from 'react'; +import items from './announcements.json'; +import './AnnouncementCards.css'; + +interface Announcement { + title: string; + date: string; + url: string; + source: 'wechat' | 'gitee' | 'other'; +} + +const SOURCE_LABEL: Record = { + wechat: '微信推文', + gitee: 'Gitee', + other: '外部链接', +}; + +/** + * 首页公告卡片区。 + * - 数据来自 scripts/gen-announcements.mjs 生成的 announcements.json + * (自动同步 announcement/index.md,无需手工维护) + * - 滚动进入视口时卡片依次浮入(IntersectionObserver + 交错延迟) + * - prefers-reduced-motion 下直接呈现,无动画 + */ +export default function AnnouncementCards({ count = 6 }: { count?: number }) { + const sectionRef = useRef(null); + const [revealed, setRevealed] = useState(false); + + useEffect(() => { + const el = sectionRef.current; + if (!el) return; + // 不支持 IO 的老浏览器直接显示 + if (typeof IntersectionObserver === 'undefined') { + setRevealed(true); + return; + } + const io = new IntersectionObserver( + (entries) => { + if (entries.some((e) => e.isIntersecting)) { + setRevealed(true); + io.disconnect(); + } + }, + { threshold: 0.15 } + ); + io.observe(el); + return () => io.disconnect(); + }, []); + + const list = (items as Announcement[]).slice(0, count); + + return ( +
+
+
+ ANNOUNCEMENTS +

社团公告

+

社团动态与活动纪实,点击卡片查看原文

+
+
+ + 创建公告 + + + 查看全部 + + +
+
+ +
+ {list.map((item, i) => ( + +
+ {item.date && {item.date}} + + {SOURCE_LABEL[item.source]} + +
+

{item.title}

+
+ 阅读原文 + +
+
+ ))} +
+
+ ); +} diff --git a/docs/components/CreateAnnouncement.css b/docs/components/CreateAnnouncement.css new file mode 100644 index 0000000..fe21555 --- /dev/null +++ b/docs/components/CreateAnnouncement.css @@ -0,0 +1,180 @@ +/* 创建公告表单页 —— 复用 ann-card 卡片样式,同样全部走主题变量 */ + +.ca-wrap { + max-width: 960px; + margin: 0 auto; + padding: 16px 0 48px; +} + +.ca-body { + display: grid; + grid-template-columns: 1fr 340px; + gap: 24px; + align-items: start; +} + +/* ---------- 表单 ---------- */ +.ca-form { + display: flex; + flex-direction: column; + gap: 20px; + padding: 28px; + border: 1px solid var(--rp-c-divider); + border-radius: 12px; + background-color: var(--rp-c-bg-soft); +} + +.ca-field { + display: flex; + flex-direction: column; + gap: 8px; +} + +.ca-label { + font-size: 14px; + font-weight: 600; + color: var(--rp-c-text-1); +} + +.ca-req { + font-size: 12px; + font-weight: 500; + color: var(--rp-c-brand); + margin-left: 4px; +} + +.ca-input { + font-size: 14px; + line-height: 1.5; + padding: 10px 14px; + border: 1px solid var(--rp-c-divider); + border-radius: 8px; + background-color: var(--rp-c-bg); + color: var(--rp-c-text-1); + outline: none; + transition: border-color 0.2s ease, box-shadow 0.2s ease; +} + +.ca-input:focus { + border-color: var(--rp-c-brand); + box-shadow: 0 0 0 3px var(--rp-c-brand-lighter, rgba(52, 81, 178, 0.15)); +} + +.ca-input::placeholder { + color: var(--rp-c-text-3, var(--rp-c-text-2)); + opacity: 0.6; +} + +.ca-help { + margin: 0; + font-size: 12px; + color: var(--rp-c-text-2); +} + +.ca-error { + margin: 0; + font-size: 12px; + color: #e5484d; +} + +/* ---------- 操作按钮 ---------- */ +.ca-actions { + display: flex; + gap: 12px; + align-items: center; + flex-wrap: wrap; +} + +.ca-btn-primary { + font-size: 14px; + font-weight: 600; + padding: 10px 22px; + border: none; + border-radius: 999px; + background-color: var(--rp-c-brand); + color: var(--rp-c-bg); + cursor: pointer; + transition: opacity 0.2s ease, transform 0.2s ease; +} + +.ca-btn-primary:hover:not(:disabled) { + opacity: 0.9; + transform: translateY(-1px); +} + +.ca-btn-primary:disabled { + opacity: 0.45; + cursor: not-allowed; +} + +.ca-btn-secondary { + font-size: 14px; + font-weight: 500; + padding: 10px 20px; + border: 1px solid var(--rp-c-divider); + border-radius: 999px; + color: var(--rp-c-text-1); + text-decoration: none; + transition: border-color 0.2s ease; +} + +.ca-btn-secondary:hover { + border-color: var(--rp-c-brand); + color: var(--rp-c-brand); +} + +.ca-steps { + margin: 4px 0 0; + padding-left: 20px; + font-size: 13px; + line-height: 2; + color: var(--rp-c-text-2); +} + +/* ---------- 右侧预览 ---------- */ +.ca-side { + display: flex; + flex-direction: column; + gap: 12px; + position: sticky; + top: 80px; +} + +.ca-side-label { + font-size: 12px; + font-weight: 600; + letter-spacing: 0.1em; + color: var(--rp-c-text-2); +} + +.ca-preview { + opacity: 1; + translate: none; +} + +.ca-preview-empty { + border-style: dashed; +} + +.ca-snippet { + margin: 0; + padding: 16px; + border-radius: 8px; + background-color: var(--rp-c-bg-mute); + font-size: 13px; + line-height: 1.6; + white-space: pre-wrap; + word-break: break-all; + color: var(--rp-c-text-1); +} + +/* ---------- 窄屏 ---------- */ +@media (max-width: 768px) { + .ca-body { + grid-template-columns: 1fr; + } + + .ca-side { + position: static; + } +} diff --git a/docs/components/CreateAnnouncement.tsx b/docs/components/CreateAnnouncement.tsx new file mode 100644 index 0000000..31c1c77 --- /dev/null +++ b/docs/components/CreateAnnouncement.tsx @@ -0,0 +1,175 @@ +import { useState } from 'react'; +import './AnnouncementCards.css'; +import './CreateAnnouncement.css'; + +type Source = 'wechat' | 'gitee' | 'other'; + +const SOURCE_LABEL: Record = { + wechat: '微信推文', + gitee: 'Gitee', + other: '外部链接', +}; + +const GITHUB_EDIT_URL = + 'https://github.com/TSGU-OSC/website/edit/master/docs/zh/announcement/index.md'; + +function detectSource(url: string): Source { + if (url.includes('mp.weixin.qq.com')) return 'wechat'; + if (url.includes('gitee.com')) return 'gitee'; + return 'other'; +} + +function todayStr(): string { + const d = new Date(); + return `${d.getFullYear()}.${d.getMonth() + 1}.${d.getDate()}`; +} + +/** + * 创建公告卡片表单页(/announcement/create)。 + * + * 静态站没有后端,新公告必须回流到仓库: + * 表单生成标准 Markdown 片段,用户复制后粘贴到 + * docs/zh/announcement/index.md 末尾提交 PR; + * 合并后 prebuild 钩子会自动把它同步进首页卡片。 + */ +export default function CreateAnnouncement() { + const [url, setUrl] = useState(''); + const [title, setTitle] = useState(''); + const [date, setDate] = useState(todayStr()); + const [copied, setCopied] = useState(false); + + const urlValid = /^https?:\/\/\S+$/.test(url.trim()); + const ready = urlValid && title.trim().length > 0 && date.trim().length > 0; + const source = detectSource(url.trim()); + const snippet = `### [${title.trim() || '卡片标题'}:${date.trim()}](${url.trim() || 'https://…'})`; + + const copySnippet = async () => { + if (!ready) return; + try { + await navigator.clipboard.writeText(snippet); + } catch { + // 剪贴板 API 不可用时的兜底 + const ta = document.createElement('textarea'); + ta.value = snippet; + document.body.appendChild(ta); + ta.select(); + document.execCommand('copy'); + document.body.removeChild(ta); + } + setCopied(true); + setTimeout(() => setCopied(false), 2000); + }; + + return ( +
+
+ {/* 左:表单 */} +
+
+ + setUrl(e.target.value)} + /> +

支持微信公众号 / Gitee 链接,粘贴后自动识别来源

+ {url.trim() !== '' && !urlValid && ( +

链接格式不正确,需要以 http:// 或 https:// 开头

+ )} +
+ +
+ + setTitle(e.target.value)} + /> +
+ +
+ + setDate(e.target.value)} + /> +

默认今天,格式如 2026.9.30,时间范围可写 2026.9.30 - 2026.10.2

+
+ +
+ + + 打开 GitHub 编辑页 → + +
+ +
    +
  1. 点击「复制 Markdown 片段」
  2. +
  3. 打开 GitHub 编辑页,把片段粘贴到文件末尾
  4. +
  5. 提交 PR,合并后自动出现在官网首页与公告页
  6. +
+
+ + {/* 右:实时预览 */} + +
+
+ ); +} diff --git a/docs/components/announcements.json b/docs/components/announcements.json new file mode 100644 index 0000000..f3b1151 --- /dev/null +++ b/docs/components/announcements.json @@ -0,0 +1,194 @@ +[ + { + "title": "开源鸿蒙社2025年度总结", + "date": "2026.2.16", + "url": "https://mp.weixin.qq.com/s/P129Q8HNEw4sA6T_uY95yQ", + "source": "wechat" + }, + { + "title": "COSCon'25 第十届中国开源年会", + "date": "2025.12.31", + "url": "https://mp.weixin.qq.com/s/A3LOU-Fuee5SzHlo5-SmdA", + "source": "wechat" + }, + { + "title": "开源鸿蒙社", + "date": "2025.12.21", + "url": "https://mp.weixin.qq.com/s/-hXizFPHLnVHKgunq002Yg", + "source": "wechat" + }, + { + "title": "赴约京城——共赴鸿蒙技术之约", + "date": "2025.11.13", + "url": "https://mp.weixin.qq.com/s/za3RjPYW7-fFUtnDJM1ZIQ", + "source": "wechat" + }, + { + "title": "鸿蒙新视野,中文代码共话未来——我校软通学院携手政企高层共访中文代码智慧工坊", + "date": "2025.10.30", + "url": "https://mp.weixin.qq.com/s/kz49P5srlHA8tAczIUzbCA", + "source": "wechat" + }, + { + "title": "注入新活力,共码新篇章|开源鸿蒙社纳新圆满落幕", + "date": "2025.10.8", + "url": "https://mp.weixin.qq.com/s/GM03aRxXesQjBaIDcjpNtw", + "source": "wechat" + }, + { + "title": "智创未来展锋芒,慧通世界启新程—软件通信学院携数智渤海人工智能产学协同论坛圆满落幕", + "date": "2025.6.20", + "url": "https://mp.weixin.qq.com/s/zonI_So9IHQo9GGgpw3tZQ", + "source": "wechat" + }, + { + "title": "艺术节-开源鸿蒙游园圆满落幕", + "date": "2025.5.25", + "url": "https://mp.weixin.qq.com/s/FXx7AlPE7ZtFK9qOr9neXA", + "source": "wechat" + }, + { + "title": "2025 CANN训练营线下实操圆满收官:Ascend C开发实操赋能AI人才", + "date": "2025.5.17", + "url": "https://mp.weixin.qq.com/s/Q6CGXFdyK9kcyh7QWyYOxw", + "source": "wechat" + }, + { + "title": "成就表彰|开源鸿蒙社团蓝桥杯省赛喜报", + "date": "2025.5.14", + "url": "https://mp.weixin.qq.com/s/tDwE0W4u5AnsYXPRIPVCWw", + "source": "wechat" + }, + { + "title": "开源鸿蒙社成员圆满结束鑫谷园面试", + "date": "2025.3.17", + "url": "https://mp.weixin.qq.com/s/m5MyFi1SySCbUC3R9rg4Rg", + "source": "wechat" + }, + { + "title": "开源鸿蒙社2024年度总结——沉淀过往,迎来新光", + "date": "2025.1.28", + "url": "https://mp.weixin.qq.com/s/c0JS6S83tA94rPr1bI_EXQ", + "source": "wechat" + }, + { + "title": "第十四届deepin开发者与用户大会", + "date": "2024.12.21", + "url": "https://mp.weixin.qq.com/s/taldILx_10v0IzH72BQZyQ", + "source": "wechat" + }, + { + "title": "2024开放原子开发者大会暨首届开源技术学术大会", + "date": "2024.12.20 - 2024.12.21", + "url": "https://mp.weixin.qq.com/s/FWJbjCUBKZJgZNIgpsfS7Q", + "source": "wechat" + }, + { + "title": "天津华为鲲鹏生态创新中心参观纪实:深度探索技术与应用的无缝对接", + "date": "2024.12.12", + "url": "https://mp.weixin.qq.com/s/n-Q2aYiDVdpGaG6Ys_fLpQ", + "source": "wechat" + }, + { + "title": "开放原子“校园行”Meetup——天津中德站", + "date": "2024.11.16", + "url": "https://mp.weixin.qq.com/s/ajZoU4L4n2RteRLIDI7ErQ", + "source": "wechat" + }, + { + "title": "第九届中国开源年会(COSCon'24)", + "date": "2024.11.2 - 2024.11.3", + "url": "https://mp.weixin.qq.com/s/7GoY-etFRVsZ2FLEKW3rzg?poc_token=HOt_mGejpYvlfV4wmwxDyEaPP3p9fAHmRcUbgdlv", + "source": "wechat" + }, + { + "title": "项目经验分享|BMF字节跳动多媒体框架 刘奇翰:拥抱开源是程序员的浪漫主义", + "date": "2024.10.22", + "url": "https://mp.weixin.qq.com/s/prQk-RFvSsa1fVTDuXu_2g", + "source": "wechat" + }, + { + "title": "开源鸿蒙社与天津科技大学人工智能学院交流:开源—程序员的浪漫主义", + "date": "2024.10.17", + "url": "https://mp.weixin.qq.com/s/L_F4VzPfvBK22IsGVzlu-A", + "source": "wechat" + }, + { + "title": "开源鸿蒙社AI SIG组荣获华为昇腾AI创新大赛铜奖", + "date": "2024.10.10", + "url": "https://mp.weixin.qq.com/s/V2ljOXZpgHUZHMT0-Id2mw", + "source": "wechat" + }, + { + "title": "开源鸿蒙社2024迎新晚会", + "date": "2024.9.27", + "url": "https://mp.weixin.qq.com/s/Fc2I0YecKWqeZht-kF8ufA", + "source": "wechat" + }, + { + "title": "开源鸿蒙社2024纳新活动", + "date": "2024.9.21 - 2024.9.22", + "url": "https://mp.weixin.qq.com/s/aAVoXFOFfJcUa1ZkVr8qzA", + "source": "wechat" + }, + { + "title": "世界智能产业博览会", + "date": "2024.6.20", + "url": "https://gitee.com/TSGU-OSC/OSC_main/blob/master/event/active11/README.md", + "source": "gitee" + }, + { + "title": "华为昇腾AI创享日", + "date": "2024.3.26", + "url": "https://gitee.com/TSGU-OSC/OSC_main/blob/master/event/active10/README.md", + "source": "gitee" + }, + { + "title": "开放原子校源行(北理工站)", + "date": "2024.1.6", + "url": "https://gitee.com/TSGU-OSC/OSC_main/blob/master/event/active9/README.md", + "source": "gitee" + }, + { + "title": "如何有效利用服务器资源", + "date": "2023.12.15", + "url": "https://gitee.com/TSGU-OSC/OSC_main/blob/master/event/active7/README.md", + "source": "gitee" + }, + { + "title": "华为昇腾AI全国行", + "date": "2023.12.9", + "url": "https://gitee.com/TSGU-OSC/OSC_main/blob/master/event/active8/README.md", + "source": "gitee" + }, + { + "title": "Linux入门培训", + "date": "2023.12.8", + "url": "https://gitee.com/TSGU-OSC/OSC_main/blob/master/event/active6/README.md", + "source": "gitee" + }, + { + "title": "如何使用开源项目", + "date": "2023.12.5", + "url": "https://gitee.com/TSGU-OSC/OSC_main/blob/master/event/active5/README.md", + "source": "gitee" + }, + { + "title": "首次技术讲座", + "date": "2023.10.16", + "url": "https://gitee.com/TSGU-OSC/OSC_main/blob/master/event/active3/README.md", + "source": "gitee" + }, + { + "title": "开源鸿蒙社2023迎新晚会", + "date": "2023.9.27", + "url": "https://gitee.com/TSGU-OSC/OSC_main/blob/master/event/active2/README.md", + "source": "gitee" + }, + { + "title": "Gitee建立时间", + "date": "2022.12.4", + "url": "https://gitee.com/TSGU-OSC/OSC_main/blob/master/event/active1/README.md", + "source": "gitee" + } +] diff --git a/docs/zh/announcement/create.mdx b/docs/zh/announcement/create.mdx new file mode 100644 index 0000000..cc2aaa4 --- /dev/null +++ b/docs/zh/announcement/create.mdx @@ -0,0 +1,7 @@ +import CreateAnnouncement from '../../components/CreateAnnouncement' + +# 创建社团公告 + +填写原文链接与卡片标题即可生成公告卡片。复制生成的 Markdown 片段,粘贴到公告页文件末尾并提交 PR,合并后卡片会自动同步到官网首页与本页。 + + diff --git a/package.json b/package.json index 8b76c27..2864695 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,9 @@ "version": "1.0.0", "private": true, "scripts": { + "predev": "node scripts/gen-announcements.mjs", "dev": "rspress dev", + "prebuild": "node scripts/gen-announcements.mjs", "build": "rspress build", "preview": "rspress preview" }, diff --git a/scripts/gen-announcements.mjs b/scripts/gen-announcements.mjs new file mode 100644 index 0000000..47ddcd0 --- /dev/null +++ b/scripts/gen-announcements.mjs @@ -0,0 +1,55 @@ +/** + * 从 docs/zh/announcement/index.md 提取公告列表,生成 + * docs/components/announcements.json 供首页 AnnouncementCards 组件使用。 + * + * 维护方式:只改 announcement/index.md,本脚本随 predev/prebuild 自动执行。 + * + * 支持的行格式: + * ### [标题:2024.9.27](https://...) + * ### [标题:2024.9.21 - 2024.9.22](https://...) + * 无链接的纯文本条目(如里程碑)会被跳过——首页卡片只放可跳转的文章。 + */ +import { mkdirSync, readFileSync, writeFileSync } from 'node:fs'; +import { fileURLToPath } from 'node:url'; +import { dirname, join } from 'node:path'; + +const root = join(dirname(fileURLToPath(import.meta.url)), '..'); +const src = join(root, 'docs/zh/announcement/index.md'); +const out = join(root, 'docs/components/announcements.json'); + +const md = readFileSync(src, 'utf-8'); + +// ### [标题](链接) +const entryRe = /^###\s+\[([^\]]+)\]\((https?:\/\/[^)\s]+)\)\s*$/gm; +// 尾部日期:2024.9.27 / 2024.9.21 - 2024.9.22 / 2023年6月25日 等 +const dateRe = /^(.*?)[::]\s*((?:\d{4}[./\-年]\s*\d{1,2}.*))$/; + +const items = []; +let m; +while ((m = entryRe.exec(md)) !== null) { + const rawTitle = m[1].trim(); + const url = m[2].trim(); + + let title = rawTitle; + let date = ''; + const dm = rawTitle.match(dateRe); + if (dm) { + title = dm[1].trim(); + date = dm[2].trim().replace(/\s+/g, ' '); + } + + const source = url.includes('mp.weixin.qq.com') + ? 'wechat' + : url.includes('gitee.com') + ? 'gitee' + : 'other'; + + items.push({ title, date, url, source }); +} + +// 文件中时间从旧到新排列,首页要最新的在前 +items.reverse(); + +mkdirSync(dirname(out), { recursive: true }); +writeFileSync(out, JSON.stringify(items, null, 2) + '\n', 'utf-8'); +console.log(`[gen-announcements] ${items.length} 条 -> ${out}`); diff --git a/theme/index.tsx b/theme/index.tsx index 04c8be5..7ab1214 100644 --- a/theme/index.tsx +++ b/theme/index.tsx @@ -1,11 +1,20 @@ import Theme from 'rspress/theme'; +import { useLang } from 'rspress/runtime'; +import AnnouncementCards from '../docs/components/AnnouncementCards'; - - +// 在首页 features 区块之后插入公告卡片区(仅中文站点) +const HomeLayout = () => { + const lang = useLang(); + return ( + : null} + /> + ); +}; export default { ...Theme, + HomeLayout, }; export * from 'rspress/theme'; - From aa6553d8e8f543152346e6287421ffbec9670899 Mon Sep 17 00:00:00 2001 From: Old_cpu <1471913775@qq.com> Date: Sat, 10 Oct 2026 11:25:55 +0800 Subject: [PATCH 2/5] =?UTF-8?q?feat:=20=E5=85=AC=E5=91=8A=E9=A1=B5?= =?UTF-8?q?=E5=85=A8=E9=87=8F=E5=8D=A1=E7=89=87=E5=A2=99=20+=20=E5=88=9B?= =?UTF-8?q?=E5=BB=BA=E6=B5=81=E7=A8=8B=E6=94=B9=E8=B5=B0=20Fork?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 公告数据独立为仓库根目录 announcements.md(从 docs/zh/announcement/index.md 迁出) - 公告页改为全量卡片墙:39 条全展示、按年份分组、blur-fade 滚动入场动画(纯 CSS,无新依赖) - 提取脚本支持无链接里程碑条目,渲染为不可点击的静态卡片 - 创建公告表单改走 Fork 流程:主按钮变为「Fork 仓库并编辑」,步骤说明更新;社团成员保留直链编辑入口 - GitHub 编辑链接指向新的 announcements.md --- .../announcement/index.md => announcements.md | 0 docs/components/AnnouncementCards.css | 21 +++ docs/components/AnnouncementCards.tsx | 66 ++++++---- docs/components/AnnouncementList.css | 97 ++++++++++++++ docs/components/AnnouncementList.tsx | 122 ++++++++++++++++++ docs/components/CreateAnnouncement.css | 24 ++++ docs/components/CreateAnnouncement.tsx | 32 +++-- docs/components/announcements.json | 42 ++++++ docs/zh/announcement/create.mdx | 2 +- docs/zh/announcement/index.mdx | 7 + scripts/gen-announcements.mjs | 35 ++--- 11 files changed, 400 insertions(+), 48 deletions(-) rename docs/zh/announcement/index.md => announcements.md (100%) create mode 100644 docs/components/AnnouncementList.css create mode 100644 docs/components/AnnouncementList.tsx create mode 100644 docs/zh/announcement/index.mdx diff --git a/docs/zh/announcement/index.md b/announcements.md similarity index 100% rename from docs/zh/announcement/index.md rename to announcements.md diff --git a/docs/components/AnnouncementCards.css b/docs/components/AnnouncementCards.css index ef0db2e..b4be559 100644 --- a/docs/components/AnnouncementCards.css +++ b/docs/components/AnnouncementCards.css @@ -164,6 +164,27 @@ background-color: var(--rp-c-bg-mute); } +.ann-badge-milestone { + color: var(--rp-c-text-2); + background-color: var(--rp-c-bg-mute); +} + +/* 无链接的里程碑:静态卡片,不可点击 */ +.ann-card-static { + cursor: default; + background-color: var(--rp-c-bg); +} + +.ann-card-static:hover { + --ann-lift: 0px; + border-color: var(--rp-c-divider); + box-shadow: none; +} + +.ann-card-static:hover .ann-card-title { + color: var(--rp-c-text-1); +} + .ann-card-title { flex: 1; margin: 0; diff --git a/docs/components/AnnouncementCards.tsx b/docs/components/AnnouncementCards.tsx index 650e913..b0292d1 100644 --- a/docs/components/AnnouncementCards.tsx +++ b/docs/components/AnnouncementCards.tsx @@ -5,14 +5,15 @@ import './AnnouncementCards.css'; interface Announcement { title: string; date: string; - url: string; - source: 'wechat' | 'gitee' | 'other'; + url: string | null; + source: 'wechat' | 'gitee' | 'other' | 'milestone'; } const SOURCE_LABEL: Record = { wechat: '微信推文', gitee: 'Gitee', other: '外部链接', + milestone: '里程碑', }; /** @@ -73,28 +74,47 @@ export default function AnnouncementCards({ count = 6 }: { count?: number }) {
- {list.map((item, i) => ( - -
- {item.date && {item.date}} - - {SOURCE_LABEL[item.source]} - + {list.map((item, i) => + item.url ? ( + +
+ {item.date && {item.date}} + + {SOURCE_LABEL[item.source]} + +
+

{item.title}

+
+ 阅读原文 + +
+
+ ) : ( +
+
+ {item.date && {item.date}} + + {SOURCE_LABEL[item.source]} + +
+

{item.title}

+
+ 社团里程碑 +
-

{item.title}

-
- 阅读原文 - -
- - ))} + ) + )}
); diff --git a/docs/components/AnnouncementList.css b/docs/components/AnnouncementList.css new file mode 100644 index 0000000..ca18fd0 --- /dev/null +++ b/docs/components/AnnouncementList.css @@ -0,0 +1,97 @@ +/* 公告页全量卡片墙 —— 年份分组 + blur-fade 滚动入场(animate-ui 风格的纯 CSS 实现) */ + +.al-list { + display: flex; + flex-direction: column; + gap: 40px; + padding-bottom: 48px; +} + +/* ---------- 年份标题 ---------- */ +.al-year { + display: flex; + align-items: baseline; + gap: 12px; + margin: 0 0 20px; + padding-bottom: 12px; + border-bottom: 1px solid var(--rp-c-divider); +} + +.al-year-num { + font-size: 26px; + font-weight: 700; + font-variant-numeric: tabular-nums; + color: var(--rp-c-text-1); +} + +.al-year-count { + font-size: 13px; + color: var(--rp-c-text-2); +} + +/* ---------- blur-fade 入场动画(按年份分组触发,组内交错) ---------- */ +.al-year-group .al-year { + opacity: 0; + translate: 0 12px; +} + +.al-year-group .ann-card { + opacity: 0; + translate: 0 20px; + filter: blur(6px); +} + +.al-year-group.al-in .al-year { + opacity: 1; + translate: 0 0; + transition: + opacity 0.5s cubic-bezier(0.22, 1, 0.36, 1), + translate 0.5s cubic-bezier(0.22, 1, 0.36, 1); +} + +.al-year-group.al-in .ann-card { + opacity: 1; + translate: 0 0; + filter: blur(0); + transition: + opacity 0.55s cubic-bezier(0.22, 1, 0.36, 1), + translate 0.55s cubic-bezier(0.22, 1, 0.36, 1), + filter 0.55s cubic-bezier(0.22, 1, 0.36, 1), + transform 0.3s cubic-bezier(0.22, 1, 0.36, 1), + border-color 0.3s ease, + box-shadow 0.3s ease; + transition-delay: + calc(80ms + var(--ann-i) * 60ms), + calc(80ms + var(--ann-i) * 60ms), + calc(80ms + var(--ann-i) * 60ms), + 0ms, + 0ms, + 0ms; +} + +/* 无障碍:减少动态偏好下直接呈现 */ +@media (prefers-reduced-motion: reduce) { + .al-year-group .al-year, + .al-year-group .ann-card { + opacity: 1; + translate: none; + filter: none; + } + + .al-year-group.al-in .al-year, + .al-year-group.al-in .ann-card { + transition: none; + transition-delay: 0ms; + } +} + +/* ---------- 窄屏 ---------- */ +@media (max-width: 640px) { + .al-list { + gap: 32px; + } + + .al-year-num { + font-size: 22px; + } +} diff --git a/docs/components/AnnouncementList.tsx b/docs/components/AnnouncementList.tsx new file mode 100644 index 0000000..227c997 --- /dev/null +++ b/docs/components/AnnouncementList.tsx @@ -0,0 +1,122 @@ +import { useEffect, useRef } from 'react'; +import items from './announcements.json'; +import './AnnouncementCards.css'; +import './AnnouncementList.css'; + +interface Announcement { + title: string; + date: string; + url: string | null; + source: 'wechat' | 'gitee' | 'other' | 'milestone'; +} + +const SOURCE_LABEL: Record = { + wechat: '微信推文', + gitee: 'Gitee', + other: '外部链接', + milestone: '里程碑', +}; + +function yearOf(date: string): string { + const m = date.match(/^(\d{4})/); + return m ? m[1] : '更早'; +} + +function Card({ item, order }: { item: Announcement; order: number }) { + const inner = ( + <> +
+ {item.date && {item.date}} + + {SOURCE_LABEL[item.source]} + +
+

{item.title}

+
+ + {item.url ? '阅读原文' : '社团里程碑'} + + {item.url && ( + + )} +
+ + ); + + const style = { ['--ann-i' as string]: order }; + + return item.url ? ( + + {inner} + + ) : ( +
+ {inner} +
+ ); +} + +/** + * 公告页全量卡片墙。 + * - 展示 announcements.json 全部条目,按年份分组(新→旧) + * - 有链接的渲染为可跳转卡片,无链接的里程碑渲染为静态卡片 + * - 每个年份分组滚动进入视口时,卡片依次 blur-fade 浮入 + * (animate-ui 同款效果,纯 CSS 实现,无额外依赖) + */ +export default function AnnouncementList() { + const rootRef = useRef(null); + + useEffect(() => { + const root = rootRef.current; + if (!root || typeof IntersectionObserver === 'undefined') { + root?.querySelectorAll('.al-year-group').forEach((g) => g.classList.add('al-in')); + return; + } + const io = new IntersectionObserver( + (entries) => { + entries.forEach((e) => { + if (e.isIntersecting) { + e.target.classList.add('al-in'); + io.unobserve(e.target); + } + }); + }, + { threshold: 0.1 } + ); + root.querySelectorAll('.al-year-group').forEach((g) => io.observe(g)); + return () => io.disconnect(); + }, []); + + // 按年份分组,保持新→旧顺序;组内序号用于动画交错(封顶 12 张) + const groups: { year: string; list: Announcement[] }[] = []; + (items as Announcement[]).forEach((it) => { + const y = yearOf(it.date); + const g = groups.find((g) => g.year === y); + if (g) g.list.push(it); + else groups.push({ year: y, list: [it] }); + }); + + return ( +
+ {groups.map((g) => ( +
+

+ {g.year} + {g.list.length} 条 +

+
+ {g.list.map((item, i) => ( + + ))} +
+
+ ))} +
+ ); +} diff --git a/docs/components/CreateAnnouncement.css b/docs/components/CreateAnnouncement.css index fe21555..1195942 100644 --- a/docs/components/CreateAnnouncement.css +++ b/docs/components/CreateAnnouncement.css @@ -131,6 +131,30 @@ color: var(--rp-c-text-2); } +.ca-steps code { + font-size: 12px; + padding: 2px 6px; + border-radius: 4px; + background-color: var(--rp-c-bg-mute); + color: var(--rp-c-text-1); +} + +.ca-member-link { + margin: 0; + font-size: 13px; + color: var(--rp-c-text-2); +} + +.ca-member-link a { + color: var(--rp-c-brand); + text-decoration: none; + margin-left: 4px; +} + +.ca-member-link a:hover { + text-decoration: underline; +} + /* ---------- 右侧预览 ---------- */ .ca-side { display: flex; diff --git a/docs/components/CreateAnnouncement.tsx b/docs/components/CreateAnnouncement.tsx index 31c1c77..84a7119 100644 --- a/docs/components/CreateAnnouncement.tsx +++ b/docs/components/CreateAnnouncement.tsx @@ -10,8 +10,10 @@ const SOURCE_LABEL: Record = { other: '外部链接', }; +// 社团成员(有仓库写权限)可直链编辑;外部提交者走 Fork 流程 const GITHUB_EDIT_URL = - 'https://github.com/TSGU-OSC/website/edit/master/docs/zh/announcement/index.md'; + 'https://github.com/TSGU-OSC/website/edit/master/announcements.md'; +const GITHUB_FORK_URL = 'https://github.com/TSGU-OSC/website/fork'; function detectSource(url: string): Source { if (url.includes('mp.weixin.qq.com')) return 'wechat'; @@ -28,9 +30,9 @@ function todayStr(): string { * 创建公告卡片表单页(/announcement/create)。 * * 静态站没有后端,新公告必须回流到仓库: - * 表单生成标准 Markdown 片段,用户复制后粘贴到 - * docs/zh/announcement/index.md 末尾提交 PR; - * 合并后 prebuild 钩子会自动把它同步进首页卡片。 + * 表单生成标准 Markdown 片段,提交者 Fork 仓库后粘贴到 + * 根目录 announcements.md 末尾,再向官方仓库发起 PR; + * 合并后 prebuild 钩子会自动把它同步进首页卡片与公告页。 */ export default function CreateAnnouncement() { const [url, setUrl] = useState(''); @@ -122,19 +124,33 @@ export default function CreateAnnouncement() { - 打开 GitHub 编辑页 → + Fork 仓库并编辑 →
  1. 点击「复制 Markdown 片段」
  2. -
  3. 打开 GitHub 编辑页,把片段粘贴到文件末尾
  4. -
  5. 提交 PR,合并后自动出现在官网首页与公告页
  6. +
  7. + 点击「Fork 仓库并编辑」(已 Fork 过会直接进入你的 Fork), + 在你自己的 Fork 中打开根目录 announcements.md +
  8. +
  9. 把片段粘贴到文件末尾,提交更改
  10. +
  11. + 向 TSGU-OSC/website 发起 Pull Request, + 合并后自动同步到官网首页与公告页 +
+ +

+ 是社团仓库成员? + + 也可以直接打开编辑页 → + +

{/* 右:实时预览 */} diff --git a/docs/components/announcements.json b/docs/components/announcements.json index f3b1151..beb455c 100644 --- a/docs/components/announcements.json +++ b/docs/components/announcements.json @@ -113,6 +113,12 @@ "url": "https://mp.weixin.qq.com/s/L_F4VzPfvBK22IsGVzlu-A", "source": "wechat" }, + { + "title": "产教融合信息系统试运行", + "date": "2024.10.12", + "url": null, + "source": "milestone" + }, { "title": "开源鸿蒙社AI SIG组荣获华为昇腾AI创新大赛铜奖", "date": "2024.10.10", @@ -137,12 +143,36 @@ "url": "https://gitee.com/TSGU-OSC/OSC_main/blob/master/event/active11/README.md", "source": "gitee" }, + { + "title": "开源鸿蒙社门户网站正式上线", + "date": "2024.4.20", + "url": null, + "source": "milestone" + }, { "title": "华为昇腾AI创享日", "date": "2024.3.26", "url": "https://gitee.com/TSGU-OSC/OSC_main/blob/master/event/active10/README.md", "source": "gitee" }, + { + "title": "中科院2024开源之夏启动会", + "date": "2024.3.10", + "url": null, + "source": "milestone" + }, + { + "title": "HDD•她力量", + "date": "2024.3.8", + "url": null, + "source": "milestone" + }, + { + "title": "社团管理系统正式上线", + "date": "2024.3.4", + "url": null, + "source": "milestone" + }, { "title": "开放原子校源行(北理工站)", "date": "2024.1.6", @@ -173,6 +203,12 @@ "url": "https://gitee.com/TSGU-OSC/OSC_main/blob/master/event/active5/README.md", "source": "gitee" }, + { + "title": "开源鸿蒙社首届程序员节", + "date": "2023.10.24", + "url": null, + "source": "milestone" + }, { "title": "首次技术讲座", "date": "2023.10.16", @@ -185,6 +221,12 @@ "url": "https://gitee.com/TSGU-OSC/OSC_main/blob/master/event/active2/README.md", "source": "gitee" }, + { + "title": "开源鸿蒙社正式成立", + "date": "2023.6.25", + "url": null, + "source": "milestone" + }, { "title": "Gitee建立时间", "date": "2022.12.4", diff --git a/docs/zh/announcement/create.mdx b/docs/zh/announcement/create.mdx index cc2aaa4..fd331d7 100644 --- a/docs/zh/announcement/create.mdx +++ b/docs/zh/announcement/create.mdx @@ -2,6 +2,6 @@ import CreateAnnouncement from '../../components/CreateAnnouncement' # 创建社团公告 -填写原文链接与卡片标题即可生成公告卡片。复制生成的 Markdown 片段,粘贴到公告页文件末尾并提交 PR,合并后卡片会自动同步到官网首页与本页。 +填写原文链接与卡片标题即可生成公告卡片。复制生成的 Markdown 片段,Fork 仓库后粘贴到根目录 `announcements.md` 末尾并提交 PR,合并后卡片会自动同步到官网首页与公告页。 diff --git a/docs/zh/announcement/index.mdx b/docs/zh/announcement/index.mdx new file mode 100644 index 0000000..f67a3d4 --- /dev/null +++ b/docs/zh/announcement/index.mdx @@ -0,0 +1,7 @@ +import AnnouncementList from '../../components/AnnouncementList' + +# 社团公告 + +社团动态与活动纪实,点击卡片查看原文。想发布新公告?前往 [创建公告](/announcement/create)。 + + diff --git a/scripts/gen-announcements.mjs b/scripts/gen-announcements.mjs index 47ddcd0..6e98abc 100644 --- a/scripts/gen-announcements.mjs +++ b/scripts/gen-announcements.mjs @@ -1,34 +1,35 @@ /** - * 从 docs/zh/announcement/index.md 提取公告列表,生成 - * docs/components/announcements.json 供首页 AnnouncementCards 组件使用。 + * 从仓库根目录 announcements.md 提取公告列表,生成 + * docs/components/announcements.json 供首页 AnnouncementCards 与 + * 公告页 AnnouncementList 组件使用。 * - * 维护方式:只改 announcement/index.md,本脚本随 predev/prebuild 自动执行。 + * 维护方式:只改 announcements.md,本脚本随 predev/prebuild 自动执行。 * * 支持的行格式: - * ### [标题:2024.9.27](https://...) + * ### [标题:2024.9.27](https://...) —— 可跳转的公告文章 * ### [标题:2024.9.21 - 2024.9.22](https://...) - * 无链接的纯文本条目(如里程碑)会被跳过——首页卡片只放可跳转的文章。 + * ### 标题:2023.6.25 —— 无链接的里程碑(生成静态卡片) */ import { mkdirSync, readFileSync, writeFileSync } from 'node:fs'; import { fileURLToPath } from 'node:url'; import { dirname, join } from 'node:path'; const root = join(dirname(fileURLToPath(import.meta.url)), '..'); -const src = join(root, 'docs/zh/announcement/index.md'); +const src = join(root, 'announcements.md'); const out = join(root, 'docs/components/announcements.json'); const md = readFileSync(src, 'utf-8'); -// ### [标题](链接) -const entryRe = /^###\s+\[([^\]]+)\]\((https?:\/\/[^)\s]+)\)\s*$/gm; +// ### [标题](链接) 或 ### 标题 +const entryRe = /^###\s+(?:\[([^\]]+)\]\((https?:\/\/[^)\s]+)\)|([^\n]+?))\s*$/gm; // 尾部日期:2024.9.27 / 2024.9.21 - 2024.9.22 / 2023年6月25日 等 const dateRe = /^(.*?)[::]\s*((?:\d{4}[./\-年]\s*\d{1,2}.*))$/; const items = []; let m; while ((m = entryRe.exec(md)) !== null) { - const rawTitle = m[1].trim(); - const url = m[2].trim(); + const rawTitle = (m[1] ?? m[3]).trim(); + const url = m[2] ? m[2].trim() : null; let title = rawTitle; let date = ''; @@ -38,16 +39,18 @@ while ((m = entryRe.exec(md)) !== null) { date = dm[2].trim().replace(/\s+/g, ' '); } - const source = url.includes('mp.weixin.qq.com') - ? 'wechat' - : url.includes('gitee.com') - ? 'gitee' - : 'other'; + const source = !url + ? 'milestone' + : url.includes('mp.weixin.qq.com') + ? 'wechat' + : url.includes('gitee.com') + ? 'gitee' + : 'other'; items.push({ title, date, url, source }); } -// 文件中时间从旧到新排列,首页要最新的在前 +// 文件中时间从旧到新排列,展示要最新的在前 items.reverse(); mkdirSync(dirname(out), { recursive: true }); From 6ac981ff35684ec23b07b690d05e91bc6c70fefe Mon Sep 17 00:00:00 2001 From: Old_cpu <1471913775@qq.com> Date: Sat, 10 Oct 2026 11:42:00 +0800 Subject: [PATCH 3/5] =?UTF-8?q?fix:=20=E6=8F=90=E4=BA=A4=E6=B5=81=E7=A8=8B?= =?UTF-8?q?=E6=94=B9=E7=94=A8=20GitHub=20=E7=BC=96=E8=BE=91=E7=9B=B4?= =?UTF-8?q?=E9=93=BE=E7=9A=84=E8=BA=AB=E4=BB=BD=E8=87=AA=E5=8A=A8=E5=88=86?= =?UTF-8?q?=E6=B5=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 撤销「Fork 仓库并编辑」按钮:已 Fork 的提交者点 /fork 会碰壁 - 改为编辑直链:未 Fork 自动引导创建,已 Fork 自动在自己的 Fork 中编辑,仓库成员提示新建分支 - 步骤文案按三种身份说明,成员提示并入步骤 --- docs/components/CreateAnnouncement.tsx | 27 +++++++++++++------------- docs/zh/announcement/create.mdx | 2 +- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/docs/components/CreateAnnouncement.tsx b/docs/components/CreateAnnouncement.tsx index 84a7119..ceba7cd 100644 --- a/docs/components/CreateAnnouncement.tsx +++ b/docs/components/CreateAnnouncement.tsx @@ -10,10 +10,10 @@ const SOURCE_LABEL: Record = { other: '外部链接', }; -// 社团成员(有仓库写权限)可直链编辑;外部提交者走 Fork 流程 +// GitHub 编辑链接自带身份分流:未 Fork 引导创建 Fork,已 Fork 自动在自己的 +// Fork 中编辑,仓库成员则提示新建分支发 PR —— 一条链接覆盖所有提交者 const GITHUB_EDIT_URL = 'https://github.com/TSGU-OSC/website/edit/master/announcements.md'; -const GITHUB_FORK_URL = 'https://github.com/TSGU-OSC/website/fork'; function detectSource(url: string): Source { if (url.includes('mp.weixin.qq.com')) return 'wechat'; @@ -124,32 +124,33 @@ export default function CreateAnnouncement() { - Fork 仓库并编辑 → + 去 GitHub 提交 →
  1. 点击「复制 Markdown 片段」
  2. - 点击「Fork 仓库并编辑」(已 Fork 过会直接进入你的 Fork), - 在你自己的 Fork 中打开根目录 announcements.md + 点击「去 GitHub 提交」,GitHub 会自动处理你的身份: + 没 Fork 过会引导你一键 Fork; + 已 Fork 过会直接在你自己的 Fork 中打开编辑器
  3. -
  4. 把片段粘贴到文件末尾,提交更改
  5. - 向 TSGU-OSC/website 发起 Pull Request, - 合并后自动同步到官网首页与公告页 + 在编辑器里把片段粘贴到文件末尾,点击 + Propose changes / Commit changes +
  6. +
  7. + GitHub 会自动跳转到 PR 页面,确认目标是{' '} + TSGU-OSC/website 的 master,提交即可

- 是社团仓库成员? - - 也可以直接打开编辑页 → - + 是社团仓库成员?页面会提示你新建分支提交 PR,同样符合规范。

diff --git a/docs/zh/announcement/create.mdx b/docs/zh/announcement/create.mdx index fd331d7..9081f9a 100644 --- a/docs/zh/announcement/create.mdx +++ b/docs/zh/announcement/create.mdx @@ -2,6 +2,6 @@ import CreateAnnouncement from '../../components/CreateAnnouncement' # 创建社团公告 -填写原文链接与卡片标题即可生成公告卡片。复制生成的 Markdown 片段,Fork 仓库后粘贴到根目录 `announcements.md` 末尾并提交 PR,合并后卡片会自动同步到官网首页与公告页。 +填写原文链接与卡片标题即可生成公告卡片。复制生成的 Markdown 片段后前往 GitHub 提交——没 Fork 过会自动引导你 Fork,已 Fork 过会直接在你自己的 Fork 中编辑。合并后卡片会自动同步到官网首页与公告页。 From a65adfc83d5ed4b3f73502b2df2455bc19407940 Mon Sep 17 00:00:00 2001 From: Old_cpu <1471913775@qq.com> Date: Sat, 10 Oct 2026 12:19:57 +0800 Subject: [PATCH 4/5] =?UTF-8?q?fix:=20=E6=9C=AC=E5=9C=B0=E9=A2=84=E8=A7=88?= =?UTF-8?q?=E6=97=B6=E6=8F=90=E4=BA=A4=E9=93=BE=E6=8E=A5=E6=8C=87=E5=90=91?= =?UTF-8?q?=E5=8A=9F=E8=83=BD=E5=88=86=E6=94=AF=EF=BC=8C=E9=81=BF=E5=85=8D?= =?UTF-8?q?=20404?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit announcements.md 随本 PR 才合入 master,合并前本地预览点击 「去 GitHub 提交」会因 master 上无此文件而 404。 现检测 localhost 自动切换到 new_page 分支链接,并显示提示; 生产环境(非 localhost)仍指向 master,行为不变。 --- docs/components/CreateAnnouncement.css | 18 ++++++++++++++++++ docs/components/CreateAnnouncement.tsx | 25 +++++++++++++++++++++++-- 2 files changed, 41 insertions(+), 2 deletions(-) diff --git a/docs/components/CreateAnnouncement.css b/docs/components/CreateAnnouncement.css index 1195942..8465d7a 100644 --- a/docs/components/CreateAnnouncement.css +++ b/docs/components/CreateAnnouncement.css @@ -155,6 +155,24 @@ text-decoration: underline; } +.ca-preview-note { + margin: 0; + font-size: 12px; + line-height: 1.7; + padding: 10px 14px; + border-radius: 8px; + color: var(--rp-c-text-2); + background-color: var(--rp-c-bg-mute); +} + +.ca-preview-note code { + font-size: 12px; + padding: 1px 5px; + border-radius: 4px; + background-color: var(--rp-c-bg); + color: var(--rp-c-text-1); +} + /* ---------- 右侧预览 ---------- */ .ca-side { display: flex; diff --git a/docs/components/CreateAnnouncement.tsx b/docs/components/CreateAnnouncement.tsx index ceba7cd..2e3f030 100644 --- a/docs/components/CreateAnnouncement.tsx +++ b/docs/components/CreateAnnouncement.tsx @@ -1,4 +1,4 @@ -import { useState } from 'react'; +import { useEffect, useState } from 'react'; import './AnnouncementCards.css'; import './CreateAnnouncement.css'; @@ -14,6 +14,10 @@ const SOURCE_LABEL: Record = { // Fork 中编辑,仓库成员则提示新建分支发 PR —— 一条链接覆盖所有提交者 const GITHUB_EDIT_URL = 'https://github.com/TSGU-OSC/website/edit/master/announcements.md'; +// 本地预览时 master 上还没有 announcements.md(要随本功能 PR 合入), +// 链接切到功能分支,否则 404 +const GITHUB_EDIT_URL_PREVIEW = + 'https://github.com/TSGU-OSC/website/edit/new_page/announcements.md'; function detectSource(url: string): Source { if (url.includes('mp.weixin.qq.com')) return 'wechat'; @@ -39,6 +43,16 @@ export default function CreateAnnouncement() { const [title, setTitle] = useState(''); const [date, setDate] = useState(todayStr()); const [copied, setCopied] = useState(false); + const [editUrl, setEditUrl] = useState(GITHUB_EDIT_URL); + const [isLocalPreview, setIsLocalPreview] = useState(false); + + useEffect(() => { + const host = window.location.hostname; + if (host === 'localhost' || host === '127.0.0.1') { + setEditUrl(GITHUB_EDIT_URL_PREVIEW); + setIsLocalPreview(true); + } + }, []); const urlValid = /^https?:\/\/\S+$/.test(url.trim()); const ready = urlValid && title.trim().length > 0 && date.trim().length > 0; @@ -124,7 +138,7 @@ export default function CreateAnnouncement() { @@ -132,6 +146,13 @@ export default function CreateAnnouncement() { + {isLocalPreview && ( +

+ 本地预览模式:提交链接已指向 new_page 分支 + (announcements.md 还未合入 master,正式上线后自动指回 master) +

+ )} +
  1. 点击「复制 Markdown 片段」
  2. From 52b8e69e57f9465ce212cecb62b38c5c2336e3a0 Mon Sep 17 00:00:00 2001 From: Old_cpu <1471913775@qq.com> Date: Sat, 10 Oct 2026 12:32:58 +0800 Subject: [PATCH 5/5] =?UTF-8?q?feat:=20=E5=85=A8=E5=91=98=20Fork=20?= =?UTF-8?q?=E6=8F=90=E4=BA=A4=E6=B5=81=E7=A8=8B=20+=20GitHub=20API=20?= =?UTF-8?q?=E6=A3=80=E6=B5=8B=20Fork?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 GitHub 用户名字段与「检测我的 Fork」按钮: 调用免鉴权 API GET /repos/{user}/website 判断 Fork 是否存在 - 已 Fork(含仓库管理者)→ 提交按钮直达本人 Fork 编辑页; 未 Fork → 上游编辑链接,GitHub 自动引导创建 - 提示 Sync fork 同步要求;步骤与说明文案同步更新 --- docs/components/CreateAnnouncement.css | 49 +++++++++++++ docs/components/CreateAnnouncement.tsx | 95 ++++++++++++++++++++++++-- docs/zh/announcement/create.mdx | 2 +- 3 files changed, 138 insertions(+), 8 deletions(-) diff --git a/docs/components/CreateAnnouncement.css b/docs/components/CreateAnnouncement.css index 8465d7a..d8d8720 100644 --- a/docs/components/CreateAnnouncement.css +++ b/docs/components/CreateAnnouncement.css @@ -173,6 +173,55 @@ color: var(--rp-c-text-1); } +/* ---------- Fork 检测 ---------- */ +.ca-ghuser-row { + display: flex; + gap: 10px; + align-items: stretch; +} + +.ca-ghuser-row .ca-input { + flex: 1; + min-width: 0; +} + +.ca-btn-check { + flex-shrink: 0; + font-size: 13px; + font-weight: 500; + padding: 0 16px; + border: 1px solid var(--rp-c-divider); + border-radius: 8px; + background-color: var(--rp-c-bg); + color: var(--rp-c-text-1); + cursor: pointer; + transition: border-color 0.2s ease, color 0.2s ease; +} + +.ca-btn-check:hover:not(:disabled) { + border-color: var(--rp-c-brand); + color: var(--rp-c-brand); +} + +.ca-btn-check:disabled { + opacity: 0.45; + cursor: not-allowed; +} + +.ca-fork-ok { + margin: 0; + font-size: 12px; + line-height: 1.7; + color: #07c160; +} + +.ca-fork-none { + margin: 0; + font-size: 12px; + line-height: 1.7; + color: var(--rp-c-text-2); +} + /* ---------- 右侧预览 ---------- */ .ca-side { display: flex; diff --git a/docs/components/CreateAnnouncement.tsx b/docs/components/CreateAnnouncement.tsx index 2e3f030..2d481a8 100644 --- a/docs/components/CreateAnnouncement.tsx +++ b/docs/components/CreateAnnouncement.tsx @@ -45,6 +45,10 @@ export default function CreateAnnouncement() { const [copied, setCopied] = useState(false); const [editUrl, setEditUrl] = useState(GITHUB_EDIT_URL); const [isLocalPreview, setIsLocalPreview] = useState(false); + const [ghUser, setGhUser] = useState(''); + const [forkState, setForkState] = useState< + 'idle' | 'checking' | 'exists' | 'none' | 'error' + >('idle'); useEffect(() => { const host = window.location.hostname; @@ -54,6 +58,35 @@ export default function CreateAnnouncement() { } }, []); + // 免鉴权 GitHub API(60 次/小时/IP):检测提交者是否已有 Fork + const checkFork = async () => { + const u = ghUser.trim(); + if (!u || forkState === 'checking') return; + setForkState('checking'); + try { + const res = await fetch( + `https://api.github.com/repos/${encodeURIComponent(u)}/website` + ); + if (res.ok) { + const data = await res.json(); + setForkState(data.fork ? 'exists' : 'none'); + } else if (res.status === 404) { + setForkState('none'); + } else { + setForkState('error'); + } + } catch { + setForkState('error'); + } + }; + + const userForkEditUrl = `https://github.com/${ghUser.trim()}/website/edit/master/announcements.md`; + // 已检测到 Fork → 直达本人 Fork 编辑页(仓库成员也走这里); + // 未检测/无 Fork → 上游编辑链接(GitHub 自动引导建 Fork) + const submitUrl = forkState === 'exists' ? userForkEditUrl : editUrl; + const submitLabel = + forkState === 'exists' ? '在我的 Fork 中编辑 →' : '去 GitHub 提交 →'; + const urlValid = /^https?:\/\/\S+$/.test(url.trim()); const ready = urlValid && title.trim().length > 0 && date.trim().length > 0; const source = detectSource(url.trim()); @@ -128,6 +161,55 @@ export default function CreateAnnouncement() {

    默认今天,格式如 2026.9.30,时间范围可写 2026.9.30 - 2026.10.2

    +
    + +
    + { + setGhUser(e.target.value); + setForkState('idle'); + }} + onKeyDown={(e) => { + if (e.key === 'Enter') { + e.preventDefault(); + checkFork(); + } + }} + /> + +
    +

    + 所有人(包括仓库管理者)都通过自己的 Fork 提交,保持官方仓库历史干净 +

    + {forkState === 'exists' && ( +

    + ✓ 已找到你的 Fork:{ghUser.trim()}/website。若编辑时提示文件不存在, + 请先在你的 Fork 页面点 Sync fork 同步最新代码 +

    + )} + {forkState === 'none' && ( +

    + 未找到你的 Fork——点下方「去 GitHub 提交」,GitHub 会自动引导你一键创建 +

    + )} + {forkState === 'error' && ( +

    检测失败(网络或 API 限流),不影响正常提交

    + )} +
    + @@ -156,13 +238,12 @@ export default function CreateAnnouncement() {
    1. 点击「复制 Markdown 片段」
    2. - 点击「去 GitHub 提交」,GitHub 会自动处理你的身份: - 没 Fork 过会引导你一键 Fork; - 已 Fork 过会直接在你自己的 Fork 中打开编辑器 + 填写 GitHub 用户名并「检测我的 Fork」:已 Fork 过会直达 + 你自己 Fork 的编辑页;没 Fork 过会自动引导你一键创建
    3. 在编辑器里把片段粘贴到文件末尾,点击 - Propose changes / Commit changes + Commit changes
    4. GitHub 会自动跳转到 PR 页面,确认目标是{' '} @@ -171,7 +252,7 @@ export default function CreateAnnouncement() {

    - 是社团仓库成员?页面会提示你新建分支提交 PR,同样符合规范。 + 包括仓库管理者在内,所有人都通过自己的 Fork 提交 PR,官方仓库历史保持干净。

    diff --git a/docs/zh/announcement/create.mdx b/docs/zh/announcement/create.mdx index 9081f9a..0d77884 100644 --- a/docs/zh/announcement/create.mdx +++ b/docs/zh/announcement/create.mdx @@ -2,6 +2,6 @@ import CreateAnnouncement from '../../components/CreateAnnouncement' # 创建社团公告 -填写原文链接与卡片标题即可生成公告卡片。复制生成的 Markdown 片段后前往 GitHub 提交——没 Fork 过会自动引导你 Fork,已 Fork 过会直接在你自己的 Fork 中编辑。合并后卡片会自动同步到官网首页与公告页。 +填写原文链接与卡片标题即可生成公告卡片。复制生成的 Markdown 片段后,填写你的 GitHub 用户名检测 Fork——已 Fork 过会直接在你自己的 Fork 中编辑,没 Fork 过会自动引导你创建。包括仓库管理者在内,所有人都通过 Fork + PR 提交。合并后卡片会自动同步到官网首页与公告页。