diff --git a/public/logos/devops-platform.svg b/public/logos/devops-platform.svg new file mode 100644 index 0000000..642ef93 --- /dev/null +++ b/public/logos/devops-platform.svg @@ -0,0 +1,15 @@ + + DevOps 运维平台 + + + + + + + + + + + + + diff --git a/src/render.js b/src/render.js index c5452a6..8346b48 100644 --- a/src/render.js +++ b/src/render.js @@ -1,3 +1,6 @@ +/** + * 将简历数据渲染为对话式作品集页面,负责不同回合的 DOM 结构装配。 + */ import { experiences, focusAreas, @@ -119,15 +122,28 @@ function skillsContent(answer) { answer.append(el("div", "a-title gen", "Token Stream / Skills")); const HOT = new Set([ "SSE 流式通信", - "Next.js", - "React", + "Next.js 16", + "React 19", "React Native", "TypeScript", + "NestJS", + "MySQL", + "Redis", + "Jenkins", + "Gitea", + "BPMN", "Qiankun", ]); + const grid = el("div", "skills-grid"); skills.forEach((group) => { - const wrap = el("div", "skill-group"); - wrap.append(el("div", "g-name", group.group)); + const wrap = el("div", "skill-group gen"); + const head = el("div", "g-head"); + head.append( + el("div", "g-name", group.group), + el("span", "g-count", `${group.items.length} 项`), + ); + wrap.append(head); + if (group.summary) wrap.append(el("p", "g-summary", group.summary)); const row = el("div", "tag-row"); group.items.forEach((item) => { row.append( @@ -135,8 +151,9 @@ function skillsContent(answer) { ); }); wrap.append(row); - answer.append(wrap); + grid.append(wrap); }); + answer.append(grid); } function experienceContent(answer) { @@ -200,6 +217,9 @@ const CONTENT = { /* ---------- 装配 ---------- */ +/** + * 根据对话回合配置生成主内容区,保留滚动叙事所需的 section 结构。 + */ export function renderDialogue(mount) { rounds.forEach((round) => { const section = el("section", "round"); @@ -228,6 +248,9 @@ export function renderDialogue(mount) { }); } +/** + * 生成右侧回合导航节点,并把点击行为交给滚动编排模块处理。 + */ export function renderRail(mount, onJump) { rounds.forEach((round) => { const node = el("button", "rail-node"); diff --git a/src/resume-data.js b/src/resume-data.js index 6675005..4cb5aaa 100644 --- a/src/resume-data.js +++ b/src/resume-data.js @@ -1,6 +1,10 @@ +/** + * 维护作品集页面的简历指标、能力标签、项目经历和职业路径文案。 + */ export const metrics = [ { value: "7 年", label: "Web 与跨端前端经验" }, { value: "2 条", label: "近一年参与的产品线" }, + { value: "6 个", label: "纳管项目发布闭环" }, { value: "SSE", label: "对话流、思考态、消息重试" }, { value: "12 个", label: "微前端拆分基础项目" }, { value: "30%", label: "消息渲染链路优化结果" }, @@ -25,50 +29,80 @@ export const focusAreas = [ }, { title: "工程习惯", - summary: "经历过微前端拆分、跨项目公共包、i18n 协作、监控接入和代码审查,能把模块交付和团队维护放在一起考虑。", - points: ["Qiankun", "Monorepo", "i18n", "Code Review"], + summary: "经历过微前端拆分、跨项目公共包、i18n 协作、监控接入、发布平台和代码审查,能把模块交付和团队维护放在一起考虑。", + points: ["Qiankun", "Monorepo", "DevOps", "Code Review"], }, ]; +/** + * 首页自我介绍回合的关键叙事点,突出近一年方向与个人项目信号。 + */ export const resumeSignals = [ "前端经验覆盖 PC 管理后台、小程序、H5、React Native 和微前端,最近一年主要在 AI 产品团队做业务前端。", "SeaBuzz 侧更偏用户体验:对话、内容流、新闻详情、游客数据、分享和反馈链路。", "SeaCloud / Vtrix 侧更偏控制台:Pricing、Billing、API Keys、组织权限、交易筛选和分销配置。", - "技术栈以 React / Next.js / React Native / TypeScript 为主,也有 Vue、UniApp 和 Qiankun 项目经验。", + "个人工程实践里搭建了 DevOps 运维平台,把 Gitea、Jenkins、BPMN、通知、审计、Agent 诊断和 Jenkins 直构导入串成发布闭环。", + "技术栈以 React / Next.js / React Native / TypeScript 为主,能覆盖 Vue、UniApp、Qiankun、NestJS、MySQL、Redis 和 Jenkins/Gitea 发布链路。", ]; +/** + * 技能栏按招聘方扫读路径分组,避免基础设施与数据层能力被埋在项目详情里。 + */ export const skills = [ { - group: "对话与内容", - items: ["SSE 流式通信", "打字机响应", "Markdown/LaTeX", "来源引用", "对话历史", "新闻详情"], + group: "前端主栈", + summary: "主力交付栈,覆盖 AI 产品、控制台、移动端和多端内容流。", + items: ["React 19", "Next.js 16", "React Native", "TypeScript", "Vue 3", "Element Plus", "Tailwind CSS", "Zustand", "Alova"], }, { - group: "控制台", - items: ["Pricing", "Billing", "API Keys", "组织权限", "额度限制", "交易筛选", "Excel 导出"], + group: "数据与后端协作", + summary: "能理解接口、数据模型和状态一致性,不只停留在页面拼装。", + items: ["NestJS", "OpenAPI", "DTO 校验", "Prisma", "MySQL", "Redis", "BullMQ", "幂等键", "乐观锁"], }, { - group: "跨端体验", - items: ["React Native", "Expo Router", "NativeWind", "游客转登录", "只读分享", "Smart Image"], + group: "DevOps 与发布", + summary: "从个人项目实践中补齐发布闭环、可观测性和运维诊断意识。", + items: ["DevOps 平台", "Jenkins", "Jenkins 直构导入", "Gitea", "BPMN", "Outbox", "审计日志", "构建日志脱敏", "Runbook"], }, { - group: "Web 主栈", - items: ["Next.js", "React", "Vue 3", "TypeScript", "Tailwind CSS", "Zustand", "Alova"], + group: "AI 对话与内容", + summary: "最近一年重点投入的方向,关注流式体验、上下文恢复和内容可读性。", + items: ["SSE 流式通信", "打字机响应", "Markdown/LaTeX", "来源引用", "思考态", "对话历史", "新闻详情"], }, { - group: "小程序与 H5", - items: ["UniApp", "微信小程序", "飞书小程序", "H5", "摄像头扫码", "低功耗蓝牙"], + group: "控制台业务", + summary: "长期处理权限、账务、额度、筛选导出等后台业务边界。", + items: ["Pricing", "Billing", "API Keys", "组织权限", "额度限制", "交易筛选", "Excel 导出", "成员权限"], }, { - group: "工程协作", - items: ["pnpm Monorepo", "Git Submodules", "Qiankun", "Lerna", "i18n 同步", "Code Review"], + group: "跨端与小程序", + summary: "覆盖 App、H5、小程序和门店设备链路,能处理端侧能力差异。", + items: ["Expo Router", "NativeWind", "UniApp", "微信小程序", "飞书小程序", "游客转登录", "Smart Image", "摄像头扫码", "低功耗蓝牙"], }, { - group: "工程化与质量", - items: ["ARMS 监控", "CDN 优化", "Playwright", "Git Flow", "分支规范", "新人培训"], + group: "工程协作与质量", + summary: "偏团队长期维护视角,关注复用、规范、监控和评审质量。", + items: ["pnpm Monorepo", "Git Submodules", "Qiankun", "Lerna", "i18n 同步", "ARMS 监控", "Playwright", "Git Flow", "Code Review"], }, ]; export const projects = [ + { + id: "devops-platform", + name: "DevOps 运维平台", + logo: "/logos/devops-platform.svg", + period: "2026.06 - 至今", + subtitle: "个人项目发布与可观测控制台", + summary: + "从零搭建一套自用 DevOps 运维平台,把本地与线上项目的发布、Jenkins 构建与直构导入、Gitea refs、BPMN 流程、通知 outbox、审计日志和 Agent 诊断收敛到一个控制台。", + modules: [ + "前端采用 Vue 3、Vite、TypeScript、Element Plus 和 Pinia,提供登录、成员权限、项目诊断、发布中心、运行记录、系统配置和全局 Agent 抽屉。", + "后端采用 NestJS、Prisma、MySQL、Redis/BullMQ,接入统一 envelope、DTO 校验、结构化日志、审计记录、幂等键和发布单乐观锁。", + "打通 Gitea 分支/tag/commit/PR 摘要、平台触发 Jenkins queue/build 同步、Jenkins 直接构建反向导入、构建日志脱敏读取、取消/重试发布和 BPMN 节点高亮。", + "将通知投递改为 outbox 持久化与可重试模型,并把 LLM Agent 限定在发布风险、失败诊断、Runbook、发布说明和事故复盘场景。", + ], + tech: ["Vue 3", "NestJS", "TypeScript", "Prisma", "MySQL", "Redis", "BullMQ", "Jenkins", "Gitea", "BPMN"], + }, { id: "vtrix", name: "SeaCloud / Vtrix", diff --git a/src/styles.css b/src/styles.css index b13fe77..f41c198 100644 --- a/src/styles.css +++ b/src/styles.css @@ -581,19 +581,53 @@ main { } /* ============ Round 4: 技能 ============ */ +.skills-grid { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 14px; +} + .skill-group { - margin-bottom: 20px; + min-width: 0; + padding: 14px; + border: 1px solid rgba(139, 151, 173, 0.18); + border-radius: 8px; + background: + linear-gradient(180deg, rgba(255, 255, 255, 0.035), rgba(255, 255, 255, 0.012)), + rgba(11, 17, 32, 0.54); +} + +.g-head { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; + margin-bottom: 8px; } .skill-group .g-name { font-family: var(--mono); font-size: 11px; - letter-spacing: 0.2em; - color: var(--ink-faint); - margin-bottom: 8px; + letter-spacing: 0; + color: var(--ink); text-transform: uppercase; } +.g-count { + flex: 0 0 auto; + font-family: var(--mono); + font-size: 10px; + color: var(--ink-faint); +} + +.g-summary { + min-height: 42px; + margin-bottom: 12px; + font-size: 12px; + line-height: 1.65; + color: var(--ink-dim); +} + .skill-token { font-family: var(--mono); font-size: 12px; @@ -607,6 +641,7 @@ main { .skill-token.hot { border-color: rgba(34, 211, 238, 0.5); + background: rgba(34, 211, 238, 0.08); color: var(--cyan); } @@ -768,6 +803,10 @@ main { grid-template-columns: 1fr; } + .skills-grid { + grid-template-columns: 1fr; + } + #session-rail { right: auto; top: 54px;