diff --git a/config/mcporter.json b/config/mcporter.json new file mode 100644 index 0000000..d75e052 --- /dev/null +++ b/config/mcporter.json @@ -0,0 +1,8 @@ +{ + "mcpServers": { + "autoglm-browser-agent": { + "command": "/Users/sanbu/.agents/skills/autoglm-browser-agent/dist/mcp_server --start_url https://www.bing.com --window_width 1456 --window_height 819 --resize_width 1456 --resize_height 819 --max_steps 100 --log_dir /Users/sanbu/.agents/skills/autoglm-browser-agent/mcp_output --if_subagent" + } + }, + "imports": [] +} diff --git a/docs/.vitepress/config.mjs b/docs/.vitepress/config.mjs index 2005123..0bb552e 100644 --- a/docs/.vitepress/config.mjs +++ b/docs/.vitepress/config.mjs @@ -1433,7 +1433,7 @@ Sitemap: ${siteUrl}/sitemap.xml link: '/zh-cn/stage-2/frontend/2.1-figma-mastergo/' }, { - text: '参考 UI 设计规范与多产品 UI 设计', + text: '参考 UI 设计规范设计页面和按钮', link: '/zh-cn/stage-2/frontend/2.3-multi-product-ui/' }, { diff --git a/docs/.vitepress/theme/index.js b/docs/.vitepress/theme/index.js index 95859a0..78a4560 100644 --- a/docs/.vitepress/theme/index.js +++ b/docs/.vitepress/theme/index.js @@ -4,7 +4,7 @@ import 'element-plus/dist/index.css' import Viewer from 'viewerjs' import 'viewerjs/dist/viewer.css' import TypeIt from 'typeit' -import { onMounted, watch, nextTick } from 'vue' +import { onMounted, onBeforeUnmount, watch, nextTick } from 'vue' import { useRoute, useData } from 'vitepress' import './style.css' import Layout from './Layout.vue' @@ -1712,12 +1712,259 @@ export default { const route = useRoute() const { frontmatter } = useData() let viewer = null + let mermaidViewer = null + let mermaidViewerWrapper = null + let mermaidViewerObjectUrl = null + let mermaidApi = null + let themeObserver = null + let currentMermaidTheme = null + const COLLAPSIBLE_CODE_MIN_LINES = 14 // Skip browser-only initialization during SSR if (import.meta.env.SSR) { return } + const getMermaidTheme = () => + document.documentElement.classList.contains('dark') ? 'dark' : 'default' + + const loadMermaid = async () => { + if (mermaidApi) return mermaidApi + const mermaidModule = await import('mermaid') + mermaidApi = mermaidModule.default ?? mermaidModule + return mermaidApi + } + + const renderMermaidDiagrams = async (force = false) => { + const mermaidBlocks = document.querySelectorAll( + '.vp-doc div.language-mermaid, .vp-doc .mermaid-diagram[data-source]' + ) + + if (!mermaidBlocks.length) return + + const mermaid = await loadMermaid() + const nextTheme = getMermaidTheme() + + if (force || currentMermaidTheme !== nextTheme) { + mermaid.initialize({ + startOnLoad: false, + securityLevel: 'loose', + theme: nextTheme + }) + currentMermaidTheme = nextTheme + } + + let index = 0 + for (const block of mermaidBlocks) { + let source = '' + let container = block + + if (block.classList.contains('language-mermaid')) { + source = block.querySelector('code')?.textContent?.trim() ?? '' + if (!source) continue + + container = document.createElement('div') + container.className = 'mermaid-diagram' + container.dataset.source = source + block.replaceWith(container) + } else { + source = block.dataset.source ?? '' + if (!source) continue + } + + try { + const diagramId = `mermaid-${route.path.replace(/\W+/g, '-')}-${Date.now()}-${index}` + const { svg, bindFunctions } = await mermaid.render(diagramId, source) + container.innerHTML = svg + container.classList.remove('mermaid-diagram-error') + container.setAttribute('role', 'button') + container.setAttribute('tabindex', '0') + container.setAttribute('aria-label', 'Open Mermaid diagram in fullscreen viewer') + container.onclick = (event) => { + if (event.target.closest?.('a')) return + openMermaidViewer(container) + } + container.onkeydown = (event) => { + if (event.key === 'Enter' || event.key === ' ') { + event.preventDefault() + openMermaidViewer(container) + } + } + bindFunctions?.(container) + } catch (error) { + console.error('Mermaid render failed:', error) + container.innerHTML = '' + container.classList.add('mermaid-diagram-error') + } + + index += 1 + } + } + + const cleanupMermaidViewer = () => { + if (mermaidViewer) { + mermaidViewer.destroy() + mermaidViewer = null + } + + if (mermaidViewerWrapper) { + mermaidViewerWrapper.remove() + mermaidViewerWrapper = null + } + + if (mermaidViewerObjectUrl) { + URL.revokeObjectURL(mermaidViewerObjectUrl) + mermaidViewerObjectUrl = null + } + + document.body.classList.remove('mermaid-viewer-open') + document.body.classList.remove('viewer-ready') + } + + const openMermaidViewer = (container) => { + const svg = container.querySelector('svg') + if (!svg) return + + cleanupMermaidViewer() + + const serializer = new XMLSerializer() + let svgMarkup = serializer.serializeToString(svg) + + if (!svgMarkup.includes('xmlns="http://www.w3.org/2000/svg"')) { + svgMarkup = svgMarkup.replace( + ' { + const imageData = mermaidViewer?.imageData + const viewerData = mermaidViewer?.viewerData + if (!imageData || !viewerData) return + + const widthScale = (viewerData.width * 0.94) / imageData.width + const heightScale = (viewerData.height * 0.94) / imageData.height + const targetScale = Math.min(widthScale, heightScale) + + if (targetScale > 1.02) { + mermaidViewer.zoomTo(imageData.ratio * targetScale, false) + } + }) + }, + hidden() { + cleanupMermaidViewer() + } + }) + + mermaidViewer.view(0) + } + + const initRenderedMermaidFeatures = async (force = false) => { + await renderMermaidDiagrams(force) + } + + const getCodeToggleLabels = () => { + const isChineseRoute = + route.path.startsWith('/zh-cn/') || route.path.startsWith('/zh-tw/') + + return isChineseRoute + ? { + expand: '展开代码', + collapse: '收起代码' + } + : { + expand: 'Expand code', + collapse: 'Collapse code' + } + } + + const getCodeLineCount = (source) => { + const normalized = source.replace(/\s+$/, '') + if (!normalized) return 0 + return normalized.split('\n').length + } + + const updateCodeToggleButton = (block, button, lineCount) => { + const labels = getCodeToggleLabels() + const isCollapsed = block.classList.contains('is-code-collapsed') + const nextLabel = isCollapsed ? labels.expand : labels.collapse + + button.textContent = `${nextLabel} (${lineCount} 行)` + button.setAttribute('aria-expanded', String(!isCollapsed)) + button.setAttribute('title', nextLabel) + } + + const initCollapsibleCodeBlocks = () => { + const codeBlocks = document.querySelectorAll( + '.vp-doc div[class*="language-"]:not(.language-mermaid)' + ) + + codeBlocks.forEach((block) => { + const pre = block.querySelector('pre') + const code = pre?.querySelector('code') + if (!pre || !code) return + + const lineCount = getCodeLineCount(code.textContent ?? '') + const existingToggle = block.querySelector('.code-collapse-toggle') + + if (lineCount < COLLAPSIBLE_CODE_MIN_LINES) { + block.classList.remove('is-collapsible-code', 'is-code-collapsed') + existingToggle?.remove() + return + } + + block.classList.add('is-collapsible-code') + + let toggle = existingToggle + if (!toggle) { + toggle = document.createElement('button') + toggle.type = 'button' + toggle.className = 'code-collapse-toggle' + toggle.addEventListener('click', () => { + block.classList.toggle('is-code-collapsed') + updateCodeToggleButton(block, toggle, lineCount) + }) + block.append(toggle) + } + + block.classList.add('is-code-collapsed') + updateCodeToggleButton(block, toggle, lineCount) + }) + } + const initViewer = () => { // 销毁旧实例 if (viewer) { @@ -1827,20 +2074,46 @@ export default { } } - onMounted(() => { + onMounted(async () => { initViewer() initTypewriter() optimizeImages() + await initRenderedMermaidFeatures(true) + initCollapsibleCodeBlocks() + + themeObserver = new MutationObserver(() => { + const nextTheme = getMermaidTheme() + if (nextTheme === currentMermaidTheme) return + nextTick(async () => { + await initRenderedMermaidFeatures(true) + }) + }) + + themeObserver.observe(document.documentElement, { + attributes: true, + attributeFilter: ['class'] + }) }) watch( () => route.path, () => - nextTick(() => { + nextTick(async () => { + cleanupMermaidViewer() initViewer() initTypewriter() optimizeImages() + await initRenderedMermaidFeatures(true) + initCollapsibleCodeBlocks() }) ) + + onBeforeUnmount(() => { + cleanupMermaidViewer() + if (themeObserver) { + themeObserver.disconnect() + themeObserver = null + } + }) } } diff --git a/docs/.vitepress/theme/style.css b/docs/.vitepress/theme/style.css index 323e91e..6a5a345 100644 --- a/docs/.vitepress/theme/style.css +++ b/docs/.vitepress/theme/style.css @@ -460,3 +460,135 @@ white-space: nowrap; flex-shrink: 0; } + +/* Mermaid diagrams */ +.vp-doc .mermaid-diagram { + margin: 18px 0 22px; + padding: 0; + overflow-x: auto; + position: relative; + border: 0; + border-radius: 0; + background: transparent; + box-shadow: none; + cursor: zoom-in; +} + +.vp-doc .mermaid-diagram svg { + display: block; + max-width: 100%; + height: auto; + margin: 0 auto; +} + +.vp-doc .mermaid-diagram-error { + min-height: 80px; +} + +.dark .vp-doc .mermaid-diagram { + border: 0; + background: transparent; +} + +.vp-doc .mermaid-diagram:focus-visible { + outline: 2px solid var(--vp-c-brand-1); + outline-offset: 3px; +} + +.mermaid-viewer-source { + position: fixed; + width: 0; + height: 0; + overflow: hidden; + opacity: 0; + pointer-events: none; +} + +body.mermaid-viewer-open .viewer-backdrop { + background: rgba(245, 247, 250, 0.96); +} + +body.mermaid-viewer-open .viewer-canvas { + background: rgba(248, 250, 252, 0.98) !important; +} + +body.mermaid-viewer-open .viewer-canvas img { + background: transparent; + border-radius: 0; + box-shadow: none; +} + +/* Long code blocks */ +.vp-doc div[class*='language-'].is-collapsible-code { + position: relative; +} + +.vp-doc div[class*='language-'].is-collapsible-code pre { + position: relative; + transition: max-height 0.24s ease; +} + +.vp-doc div[class*='language-'].is-collapsible-code.is-code-collapsed pre { + max-height: 320px; + overflow-x: auto; + overflow-y: hidden; +} + +.vp-doc + div[class*='language-'].is-collapsible-code.is-code-collapsed + pre::after { + content: ''; + position: absolute; + left: 0; + right: 0; + bottom: 0; + height: 88px; + pointer-events: none; + background: linear-gradient( + to bottom, + rgba(255, 255, 255, 0), + var(--vp-code-block-bg) 72% + ); +} + +.vp-doc .code-collapse-toggle { + display: inline-flex; + align-items: center; + justify-content: center; + min-width: 132px; + margin: 12px auto 2px; + padding: 7px 14px; + border: 1px solid rgba(0, 113, 227, 0.2); + border-radius: 999px; + background: rgba(0, 113, 227, 0.06); + color: var(--vp-c-brand-1); + font-size: 13px; + font-weight: 600; + line-height: 1; + cursor: pointer; + transition: + background-color 0.2s ease, + border-color 0.2s ease, + transform 0.2s ease; +} + +.vp-doc .code-collapse-toggle:hover { + border-color: rgba(0, 113, 227, 0.32); + background: rgba(0, 113, 227, 0.1); + transform: translateY(-1px); +} + +.vp-doc .code-collapse-toggle:focus-visible { + outline: 2px solid var(--vp-c-brand-1); + outline-offset: 2px; +} + +.dark .vp-doc .code-collapse-toggle { + border-color: rgba(96, 165, 250, 0.24); + background: rgba(96, 165, 250, 0.12); +} + +.dark .vp-doc .code-collapse-toggle:hover { + border-color: rgba(96, 165, 250, 0.38); + background: rgba(96, 165, 250, 0.18); +} diff --git a/docs/en/stage-2/assignments/2.1-fullstack-app/index.md b/docs/en/stage-2/assignments/2.1-fullstack-app/index.md index b93da08..bdbeb16 100644 --- a/docs/en/stage-2/assignments/2.1-fullstack-app/index.md +++ b/docs/en/stage-2/assignments/2.1-fullstack-app/index.md @@ -1,3 +1,207 @@ # Build Your First Modern App: Full-Stack Application -> This chapter is currently being written. Stay tuned... +::: tip Goal +Turn a prototype idea into a small but complete web application with a real frontend, backend logic, persistent data, and a public deployment link. +::: + +At this point, the goal is no longer just “make a page look right.” The goal is to make the core product loop actually work: a user opens the app, does something meaningful, triggers backend logic, stores or reads data, and gets a result that still exists the next time they come back. + +You do not need to build a huge platform. A small product with a clear scope and a fully working flow is a much better submission than a large project full of unfinished features. + +This assignment is meant to connect the Stage 2 path: + +- [UI Design](../../frontend/2.2-ui-design/) +- [From Design Prototype to Project Code](../../frontend/2.6-design-to-code/) +- [Modern Component Libraries](../../frontend/2.7-modern-component-library/) +- [From Database to Supabase](../../backend/2.2-database-supabase/) +- [Using LLMs to Write API Code](../../backend/2.3-ai-interface-code/) +- [Git and GitHub Workflow](../../backend/2.4-git-workflow/) +- [How to Deploy Web Applications](../../backend/2.5-zeabur-deployment/) + +## 1. Assignment Goal + +Build a **working, demoable, publicly accessible** full-stack web app. + +Your project should have: + +1. **A clear user and use case** + - You should be able to explain who the product is for and what problem it solves. +2. **A complete product loop** + - The key action must go beyond frontend mock data and trigger real backend or database behavior. +3. **A real engineering handoff** + - The code should live in a repo, secrets should be handled correctly, and someone else should be able to understand how the project works. + +Good project directions include: + +- upgrading a Stage 1 prototype into a real application +- extending [Hogwarts Portraits](../../frontend/2.5-hogwarts-portraits/) into a more complete product +- building a small AI utility such as a writing tool, summarizer, or research workspace +- building a lightweight vertical tool such as a CRM, scheduling tool, or learning assistant + +## 2. Minimum Requirements + +| Area | Minimum Requirement | Notes | +| --- | --- | --- | +| Product scope | One clear core workflow | Example: create and save content, upload and process input, create a character and chat | +| Frontend | At least 2 pages, or 1 complex main page plus 1 supporting page | Desktop-ready is required | +| States | Handle loading, empty, and error states | Do not build happy-path only | +| Data | At least 1 core table, ideally 2 related entities | Example: `users` + `projects`, `characters` + `messages` | +| Backend | At least 3 real API endpoints or equivalent server-side capabilities | Express, Next.js route handlers, or edge functions are all acceptable | +| Persistence | Core user actions must be saved and retrievable | Not just mock data | +| Delivery | A GitHub repo and a public online link | Vercel or Zeabur are recommended | +| Documentation | A readable `README.md` | Include purpose, stack, setup, and env vars | + +## 3. Recommended Stack + +You can use any reasonable stack, but if you want the safest path, this combination works well: + +| Layer | Recommended Option | +| --- | --- | +| Frontend | React / Next.js or Vue | +| Component library | shadcn/ui, Ant Design, HeroUI, or Element Plus | +| Data and auth | Supabase | +| Backend | Next.js route handlers, Express, or Supabase Edge Functions | +| Deployment | Vercel or Zeabur | + +For a first full-stack project, “frontend + Supabase + a small amount of server logic” is usually the fastest way to a successful submission. + +## 4. Suggested Workflow + +### 4.1 Write the core task in one sentence + +Before coding, answer: + +> What is the single most important thing a user should be able to do in this app? + +Examples: + +- upload text, generate a summary, and save it to history +- create a character profile and continue chatting with it +- add a customer lead and review its status later + +If that sentence is still vague, the scope is probably too wide. + +### 4.2 Plan the structure first + +Sketch: + +- what pages you need +- the most important module on each page +- which actions read data +- which actions write data + +This will make AI-assisted implementation much more stable. + +### 4.3 Start with mock data + +A practical order is: + +1. build the frontend structure with mock data +2. connect real backend logic +3. replace mock data with real persistence + +This keeps the debugging surface much smaller. + +### 4.4 Starter prompt for AI IDEs + +```text +Help me build a deployable full-stack web app for [your use case]. + +Do not generate everything at once. First break the project into: +1. page structure and routes +2. data model design +3. API or server-side capability design +4. frontend component breakdown +5. environment variable and deployment setup + +Requirements: +- Use [React/Next.js/Vue + your component library + Supabase/Express] +- Prioritize a working MVP over over-engineering +- Tell me which files to create +- Include loading, empty, error, and validation states +``` + +## 5. Deliverables + +Please submit: + +1. **A GitHub repository link** +2. **A public online link** +3. **A `README.md`** + - project name and one-line description + - target users and use case + - tech stack + - main features + - local setup steps + - environment variable notes +4. **A short demo asset** + - ideally a 60 to 90 second video, or a few key screenshots/GIFs + +Useful optional additions: + +- database schema or ER diagram +- API notes or endpoint list +- design/architecture explanation +- a short iteration log + +## 6. Evaluation Checklist + +Your project is in good shape if: + +- a user can complete a real end-to-end flow +- key actions create visible changes in the system +- data still exists after refresh +- backend failures do not crash the UI +- you can explain the product clearly in about one minute + +## 7. Bonus Ideas + +You can go further with: + +- authentication and multi-user support +- real AI capabilities with streaming or retries +- better UI quality with a modern component library +- monitoring, logging, or analytics +- basic tests +- payment or subscription flows +- mobile adaptation + +## 8. Common Mistakes + +### 8.1 The scope is too large + +If your first version requires auth, admin tools, payments, workflow builders, knowledge bases, and multi-user collaboration all at once, the project is probably too big. + +### 8.2 The frontend looks good, but nothing is real + +That may still be a useful frontend exercise, but not a strong full-stack assignment. This project should include real persistence and server-side behavior. + +### 8.3 Secrets are committed into the repo + +API keys and database secrets belong in environment variables, not in frontend source code. + +### 8.4 Only testing the happy path + +Manually test: + +- empty input +- duplicate actions +- network failure +- missing data +- permission errors + +That is where the most useful product fixes usually appear. + +## 9. Final Advice + +Treat this assignment as your first real product delivery exercise, not as a feature-count competition. + +What matters most is not how many tools you used, but whether you can: + +- define a clear product goal +- use AI tools to move from idea to implementation +- deliver something another person can understand, try, and reproduce + +::: tip Next +After this assignment, continue with [Assignment 2: Modern Frontend Component Library + Trae](../2.2-modern-frontend-trae/) to push the interface quality even further. +::: diff --git a/docs/en/stage-2/assignments/2.2-modern-frontend-trae/index.md b/docs/en/stage-2/assignments/2.2-modern-frontend-trae/index.md index 6d6b218..d15bf43 100644 --- a/docs/en/stage-2/assignments/2.2-modern-frontend-trae/index.md +++ b/docs/en/stage-2/assignments/2.2-modern-frontend-trae/index.md @@ -1,3 +1,282 @@ # Assignment 2: Modern Frontend Component Library + Trae Practice -> This chapter is currently being written. Stay tuned... +::: tip Goal +Use Trae or another AI IDE together with a modern component library to build a frontend that feels like a real product rather than a classroom demo. +::: + +The previous assignment focused on full-stack completeness. This one focuses on frontend quality: structure, consistency, interaction, responsiveness, and iteration. + +Many first frontend projects can “work” but still feel unfinished: + +- the layout is usable, but visually inconsistent +- buttons, forms, drawers, and tables do not feel like one system +- the desktop version is acceptable, but narrow screens fall apart +- AI can generate a first version quickly, but the result still looks generic + +This assignment is about pushing past that first draft. + +Suggested review chapters: + +- [From Design Prototype to Project Code](../../frontend/2.6-design-to-code/) +- [Upgrade Your Interface with Modern Component Libraries](../../frontend/2.7-modern-component-library/) +- [Make Interfaces Beautiful with LLMs and Skills](../../frontend/2.4-llm-skills-beautiful/) +- [Reference UI Design Specifications and Multi-Product UI Design](../../frontend/2.3-multi-product-ui/) + +## 1. Assignment Goal + +Build a frontend project with: + +- a clear use case +- a structured layout +- a consistent design language +- real interaction patterns +- code organized in reusable components + +Possible project types: + +- a landing page +- a logged-in product workspace +- a dashboard or admin interface +- an AI tool interface +- a multi-module content management page + +## 2. Required Tools + +You should use: + +- **Trae** or another AI coding IDE +- **at least one modern component library** + +Examples: + +- React: `shadcn/ui`, `Ant Design`, `HeroUI`, `Material UI` +- Vue: `Element Plus`, `Naive UI`, `Ant Design Vue` + +The important part is not just naming a library. The library should meaningfully improve the project's structure and polish. + +## 3. Minimum Requirements + +| Area | Minimum Requirement | Notes | +| --- | --- | --- | +| Page scope | At least 2 pages, or 1 complex main page plus 1 supporting page | Example: dashboard + detail page | +| Component library | Use at least 1 real component library | It should appear in the actual implementation | +| Component breakdown | At least 5 reusable components or clearly separated modules | Navigation, filters, table area, modal, summary cards, etc. | +| Interaction | At least 3 interaction types | Form validation, modal/drawer, tabs, menus, filters, pagination, chart switching, etc. | +| States | Include at least 3 of the following: loading, empty, error, success feedback | Important for product realism | +| Responsiveness | Desktop-ready, with reasonable narrow-screen handling | Full mobile support is a bonus | +| Data | Real APIs or structured mock data | Even mock data should be organized well | +| Delivery | Repo link + public deployment | GitHub + Vercel/Zeabur is recommended | + +## 4. Suggested Project Directions + +### 4.1 Product landing page + +Good for practicing visual hierarchy and presentation. + +Suggested sections: + +- hero +- feature cards +- workflow +- testimonials +- pricing +- FAQ +- footer + +Good library options: `HeroUI`, `Material UI` + +### 4.2 AI tool workspace + +Good for layout, state design, and interaction. + +Suggested sections: + +- sidebar +- top toolbar +- input area +- result area +- history panel +- settings modal + +Good library options: `shadcn/ui`, `Element Plus` + +### 4.3 Dashboard / admin interface + +Good for dense information layout and component composition. + +Suggested sections: + +- summary cards +- charts +- filters +- data table +- details drawer or modal +- pagination and status tags + +Good library options: `Ant Design`, `Element Plus` + +## 5. Suggested Workflow + +### 5.1 Decide three things first + +Before opening Trae, write down: + +1. what kind of product you are building +2. why you chose this component library +3. which 3 modules matter most on the page + +That clarity will make AI-generated output far better. + +### 5.2 Generate the first structural version + +In the first round, focus on layout, component structure, data shape, and core interactions rather than visual perfection. + +Example prompt: + +```text +Help me build a frontend project for [product type] using [component library]. + +For the first version, focus on: +1. page structure +2. component breakdown +3. mock data structure +4. core interactions +5. responsive layout foundations + +Please split the code into reasonable page and component files. +``` + +### 5.3 Use later rounds for polish + +Once the first version works, iterate on: + +- spacing and layout rhythm +- visual hierarchy +- button priority +- hover/focus/disabled states +- status colors, icons, and tags +- narrow-screen adaptation + +This is often where the project begins to feel like a real product. + +### 5.4 Add the missing product details + +Do not skip: + +- loading buttons during submission +- empty-state messaging +- fallback UI for failed requests +- confirmation and feedback for save/delete/export flows +- scroll and overflow behavior in dense layouts + +These details often create the biggest quality jump. + +## 6. Starter Prompt Template + +```text +Help me build a frontend interface for [project name] using [component library]. +The style should feel [modern / professional / lightweight / consumer / enterprise]. + +Target users: +- [who uses this] + +Page goal: +- [the most important task on this page] + +Layout: +- left side: ... +- top area: ... +- main content: ... +- right side / bottom: ... + +Interaction requirements: +- include several of: modal, drawer, filters, form validation, charts, pagination +- include loading, empty, and error states +- desktop-first, with narrow-screen handling + +Implementation requirements: +- clear component breakdown +- mock data in separate files or modules +- do not generate one giant page file +- build structure first, then improve visuals iteratively +``` + +## 7. Deliverables + +Please submit: + +1. **A GitHub repository link** +2. **A public online link** +3. **A `README.md`** + - what the page or product is + - which component library you used and why + - the main modules included + - how to run it locally +4. **Screenshots or a short demo** + - at minimum, show the main desktop view + - if you handled responsiveness, include a narrow-screen view too + +Useful optional additions: + +- before/after comparison between first draft and final result +- the prompts that helped you most +- one UI detail you are especially proud of + +## 8. Evaluation Checklist + +Your project is strong if: + +- the first screen clearly communicates what the product is +- the layout feels organized even when the page contains a lot of information +- controls feel consistent across the page +- the code is broken into understandable parts +- interactions feel intentional rather than decorative +- the result feels like a product, not a collage of screenshots + +## 9. Bonus Ideas + +You can go further with: + +- real API integration +- theme tokens or dark/light theme switching +- reusable layout primitives +- charts, skeleton loaders, or richer data states +- accessibility improvements such as focus states and semantic structure +- a short design rationale + +## 10. Common Mistakes + +### 10.1 Focusing only on “looking better” + +Modern frontend quality is not just visuals. It also includes clarity, usability, state handling, and structure. + +### 10.2 Copying default component library examples too closely + +A component library is a foundation, not the final identity of the product. + +### 10.3 Generating everything at once + +Complex interfaces are usually better when built in stages: + +1. structure +2. modules +3. polish +4. details + +## 11. Final Advice + +Do not treat AI as a one-shot page generator. Treat it as a collaborative frontend partner. + +The most useful pattern is usually: + +- you define the direction +- AI creates the first version +- you critique the result +- AI refines it +- you keep making product decisions + +That is how you move from “a page that exists” to “a frontend worth shipping.” + +::: tip Next +If this assignment goes well, revisit [the full-stack assignment](../2.1-fullstack-app/) and upgrade that product with the frontend quality you built here. +::: diff --git a/docs/public/sitemap.xml b/docs/public/sitemap.xml index 04bc100..0b76b04 100644 --- a/docs/public/sitemap.xml +++ b/docs/public/sitemap.xml @@ -788,7 +788,7 @@ https://datawhalechina.github.io/easy-vibe/zh-cn/stage-0/ - 2026-03-25T17:40:30+08:00 + 2026-03-25T23:02:33+08:00 weekly 0.9 @@ -892,7 +892,7 @@ https://datawhalechina.github.io/easy-vibe/zh-cn/stage-1/appendix-double-diamond/ - 2026-03-25T17:40:30+08:00 + 2026-03-25T23:02:33+08:00 weekly 0.9 @@ -900,7 +900,7 @@ https://datawhalechina.github.io/easy-vibe/zh-cn/stage-1/appendix-idea-sources/ - 2026-03-25T17:40:30+08:00 + 2026-03-25T23:02:33+08:00 weekly 0.9 @@ -916,7 +916,7 @@ https://datawhalechina.github.io/easy-vibe/zh-cn/stage-1/appendix-jobs-to-be-done/ - 2026-03-25T17:40:30+08:00 + 2026-03-25T23:02:33+08:00 weekly 0.9 @@ -924,7 +924,7 @@ https://datawhalechina.github.io/easy-vibe/zh-cn/stage-1/appendix-mom-test/ - 2026-03-25T17:40:30+08:00 + 2026-03-25T23:02:33+08:00 weekly 0.9 @@ -947,12 +947,11 @@ - https://datawhalechina.github.io/easy-vibe/zh-cn/stage-2/assignments/2.2-modern-frontend-trae/ - 2026-03-25T00:28:36+08:00 + https://datawhalechina.github.io/easy-vibe/zh-cn/stage-2/assignments/在线考试与管理系统/ + 2026-03-26T02:36:21.127Z weekly 0.8 - - + https://datawhalechina.github.io/easy-vibe/zh-cn/stage-2/backend/2.2-database-supabase/ @@ -1252,7 +1251,7 @@ https://datawhalechina.github.io/easy-vibe/zh-cn/stage-3/cross-platform/3.8-pwa-local-app/ - 2026-03-25T00:28:36+08:00 + 2026-03-25T20:01:19+08:00 weekly 0.8 diff --git a/docs/zh-cn/guide/introduction.md b/docs/zh-cn/guide/introduction.md index 861c2c5..86ec7ee 100644 --- a/docs/zh-cn/guide/introduction.md +++ b/docs/zh-cn/guide/introduction.md @@ -56,7 +56,7 @@ | 使用 lovart 生产素材 | 学会用 lovart 批量生成人物、场景等视觉素材,为 UI 设计和前端开发提供素材基础 | 🚧 | | Figma 与 MasterGo 入门 | 用设计工具梳理信息架构和页面结构,为前端实现打基础 | 🚧 | | 构建第一个现代应用程序-UI 设计 | 基于设计稿完成组件化界面,实现从设计到代码的第一条链路 | 🚧 | -| 参考 UI 设计规范与多产品 UI 设计 | 围绕统一主视觉扩展多产品界面,练习系统化设计能力 | 🚧 | +| 参考 UI 设计规范设计页面和按钮 | 学习用主流设计规范组织页面结构、按钮层级,并借助 AI 生成设计方案 | 🚧 | | [一起做霍格沃茨画像](/zh-cn/stage-2/frontend/2.5-hogwarts-portraits/) | 从 0 到 1 做出接入 AI 能力的前端应用,串联设计与开发 | 🚧 | #### 后端开发部分 diff --git a/docs/zh-cn/stage-2/assignments/2.1-fullstack-app/index.md b/docs/zh-cn/stage-2/assignments/2.1-fullstack-app/index.md deleted file mode 100644 index f67f0ba..0000000 --- a/docs/zh-cn/stage-2/assignments/2.1-fullstack-app/index.md +++ /dev/null @@ -1,3 +0,0 @@ -# 大作业 1:构建第一个现代应用程序 - 全栈应用 - -> 本章节正在编写中,敬请期待... diff --git a/docs/zh-cn/stage-2/assignments/2.2-modern-frontend-trae/index.md b/docs/zh-cn/stage-2/assignments/2.2-modern-frontend-trae/index.md deleted file mode 100644 index 5042a05..0000000 --- a/docs/zh-cn/stage-2/assignments/2.2-modern-frontend-trae/index.md +++ /dev/null @@ -1,3 +0,0 @@ -# 大作业 2:现代前端组件库 + Trae 实战 - -> 本章节正在编写中,敬请期待... diff --git a/docs/zh-cn/stage-2/assignments/copywriting-platform-supabase/index.md b/docs/zh-cn/stage-2/assignments/copywriting-platform-supabase/index.md new file mode 100644 index 0000000..2791972 --- /dev/null +++ b/docs/zh-cn/stage-2/assignments/copywriting-platform-supabase/index.md @@ -0,0 +1,417 @@ +# 大作业 1:第一个 SaaS 全栈应用——文案生成网站 + +第一次做全栈网站,最容易卡住的地方往往不是代码,而是**不知道该做什么**。 + +题目太大,功能太散,做到一半发现根本收不住。 + +所以这次换个思路:不给开放题,直接给你一个明确的方向——做一件完整但不复杂的事。 + +::: tip 🎯 这次做什么? +打造一个 **AI 营销文案工作台**。用户登录后填写产品信息,一键生成营销文案,自动保存历史记录。需要更多生成次数?升级套餐即可。管理员可在后台查看所有用户、生成记录和支付情况。 +::: + +
+ + + +
+ +## 为什么选这个题目? + +因为它恰到好处——**涵盖了现代网站该有的所有要素,又不会复杂到失控**。 + +- **前台有实际用途**:不是摆设,用户真的来解决问题 +- **用户系统有登录权限**:区分访客与注册用户 +- **核心功能是"生成"**:调用 AI 动态产出内容,而非展示静态页面 +- **数据持久化**:生成结果存入数据库,随时可查 +- **付费机制**:看起来像正经 SaaS,而非玩具项目 +- **管理后台**:体验管理员视角,掌控全局 + +难度适中:不会简单到只有一个表单,也不会复杂到做一周还找不着北。 + +## 1. 定主题:先搞清楚要做什么 + +网站名称:**LaunchKit** + +定位:AI 营销文案工作台 + +目标用户:独立开发者、小商家、内容运营者,以及想快速搞定 Landing Page 的人。 + +他们来这里不是为了闲聊——就是想**快速拿到能用的营销文案**。 + +### 核心功能 + +别想太复杂,核心就一件事: + +**用户输入**:产品名、一句话介绍、目标用户、3 个卖点、投放渠道 + +**系统输出**:主标题、副标题、CTA 按钮文案、3 版短文案、1 版长文案 + +生成结果自动存入账户,下次登录随时查看。 + +### 页面规划 + +按这 6 个页面来做: + +| 页面 | 路径 | 说明 | +|------|------|------| +| 首页 | `/` | 清晰传达产品价值,放置注册/登录入口 | +| 登录页 | `/login` | 简洁的登录表单 | +| 注册页 | `/register` | 简洁的注册表单 | +| 工作台 | `/dashboard` | 填写信息、生成文案、查看结果 | +| 套餐页 | `/billing` | 展示免费版与 Pro 版,跳转 Stripe 付款 | +| 管理后台 | `/admin` | 管理员查看用户、生成记录、支付状态 | + +### 数据模型 + +三张核心表足够: + +```sql +profiles ( + id uuid primary key, + email text, + role text, -- user / admin + plan text, -- free / pro + created_at timestamptz +) + +generations ( + id uuid primary key, + user_id uuid, + product_name text, + target_channel text, + input_payload jsonb, + result_payload jsonb, + created_at timestamptz +) + +subscriptions ( + id uuid primary key, + user_id uuid, + stripe_customer_id text, + stripe_subscription_id text, + plan text, + status text, + created_at timestamptz +) +``` + +到这一步,整个网站的骨架已经清晰。 + +
+ + + +
+ +## 2. 搭前台:先把页面做出来 + +这一步先别碰数据库,也别急着接支付——**先把前台骨架搭起来**。 + +### 技术栈 + +- **Next.js App Router** —— 现代 React 框架 +- **TypeScript** —— 类型安全 +- **Tailwind CSS** —— 原子化样式 +- **shadcn/ui** —— 精致组件库 +- **Supabase** —— 后端服务 +- **Stripe** —— 支付处理 + +这套组合是目前 AI IDE 最擅长的技术栈,也最符合现代 SaaS 的审美。 + +### 第一步:搭建项目骨架 + +把下面这段提示词丢给 Trae / Cursor / Claude Code: + +```text +请帮我创建一个现代 SaaS 网站,名字叫 LaunchKit。 + +技术栈要求: +- Next.js App Router +- TypeScript +- Tailwind CSS +- shadcn/ui + +页面清单: +1. 首页 / +2. 登录页 /login +3. 注册页 /register +4. 用户工作台 /dashboard +5. 套餐页 /billing +6. 管理后台 /admin + +请先只做前端结构,不接数据库。 + +要求: +- 首页要有现代 AI SaaS 落地页的气质 +- 登录和注册页保持简洁 +- Dashboard 左侧表单,右侧结果展示 +- Billing 页面展示 free 和 pro 两个套餐 +- Admin 页面先做后台框架:侧边栏、顶部栏、表格区域 +- 使用 shadcn/ui 组件 +- 页面风格要像真实产品,不要像课堂 demo +``` + +### 第二步:完善工作台细节 + +第一版页面完成后,继续补充: + +```text +请继续完善 /dashboard 页面。 + +这是一个 AI 营销文案工作台。 + +左侧表单字段: +- 产品名 +- 一句话介绍 +- 目标用户 +- 3 个卖点 +- 投放渠道(官网、朋友圈、小红书、抖音、邮件) + +右侧结果区域预留: +- 主标题 +- 副标题 +- CTA +- 3 版短文案 +- 长文案 + +先用 mock 数据跑通交互。 + +要求: +- 点击"生成文案"后有 loading 状态 +- 结果区域设计空状态 +- 响应式布局,宽屏窄屏都能正常显示 +``` + +### 遇到阻碍? + +回头看看这几篇: + +- [构建第一个现代应用程序 - UI 设计](../../frontend/2.2-ui-design/) +- [参考 UI 设计规范设计页面和按钮](../../frontend/2.3-multi-product-ui/) +- [用 LLM 和 Skills 让界面变好看](../../frontend/2.4-llm-skills-beautiful/) +- [从设计原型到项目代码](../../frontend/2.6-design-to-code/) +- [使用现代组件库更新你的界面](../../frontend/2.7-modern-component-library/) + +
+ + + +
+ +## 3. 接后端:把数据库、生成、支付串起来 + +这一步才算真正的"全栈"。 + +### 第三步:接入 Supabase 登录 + +```text +请把我当成 0 基础,一步一步带我完成 Supabase 登录接入。 + +需要你帮我完成: +1. 项目接入 Supabase +2. 实现注册、登录、退出功能 +3. 登录成功后跳转到 /dashboard +4. 未登录用户访问 /dashboard、/billing、/admin 时自动跳转 /login +5. 创建 profiles 表 +6. 用户注册成功后自动在 profiles 表创建记录 +7. profiles 表包含 email、role、plan 字段 + +实现要求: +- 每步都说明在修改哪些文件 +- 密钥不要硬编码 +- 需要在 Supabase 后台手动操作的地方请明确标注 +- 完成后说明如何验证注册和登录 +``` + +### 第四步:接入生成接口和数据库 + +```text +请把我当成 0 基础,帮我完成网站的核心功能:生成营销文案并保存。 + +目标效果: +1. 用户在 /dashboard 填写表单,点击"生成文案" +2. 后端接收:产品名、介绍、目标用户、卖点、投放渠道 +3. 后端调用模型生成结果 +4. 页面展示生成结果 +5. 输入和输出都保存到数据库 +6. 用户下次进入可查看历史记录 + +需要你完成: +- 创建生成接口 /api/generate +- 创建 generations 表 +- 设计输入和输出字段 +- Dashboard 页面读取当前用户的历史记录 + +用户体验: +- 按钮 loading 状态 +- 生成失败时的错误提示 +- 无历史记录时的空状态 + +完成后请说明: +- 前端页面文件位置 +- 后端接口文件位置 +- 数据写入数据库的逻辑位置 +- 如何测试完整生成链路 +``` + +### 第五步:接入 Stripe 付费 + +```text +请把我当成 0 基础,帮我给 LaunchKit 加上最简可用的 Stripe 付费。 + +不需要复杂系统,先跑通最基本的付费链路。 + +需要你完成: +1. /billing 页面展示 free 和 pro 两个套餐 +2. 用户点击升级后跳转 Stripe Checkout +3. 支付成功后返回网站 +4. 支付结果保存到 subscriptions 表 +5. 同步更新 profile.plan 字段 +6. free 用户每日限 3 次生成,pro 用户不限 + +实现原则: +- 先跑通主流程,暂不考虑复杂边界 +- 需要在 Stripe 后台配置的地方请写清楚 +- 完成后说明如何测试完整支付流程 +``` + +### 第六步:搭建管理后台 + +```text +请把我当成 0 基础,帮我做一个简洁可用的管理后台。 + +仅限管理员访问。 + +需要你完成: +1. 仅 role = admin 的用户可访问 /admin +2. 后台包含 3 个 Tab: + - 用户列表 + - 生成记录 + - 订阅状态 +3. 用户列表显示:email、plan、创建时间 +4. 生成记录显示:用户、产品名、渠道、创建时间 +5. 订阅状态显示:用户、套餐、支付状态 + +要求: +- 界面简洁清晰 +- 使用现有组件库的表格、Tab、Badge +- 完成后说明如何将账号设为 admin +``` + +### 遇到阻碍? + +回头看看这几篇: + +- [从数据库到 Supabase](../../backend/2.2-database-supabase/) +- [大模型辅助编写接口代码与接口文档](../../backend/2.3-ai-interface-code/) +- [如何集成 Stripe 等收费系统](../../backend/2.7-stripe-payment/) + +
+ + + +
+ +## 4. 做后台与交付:真正上线 + +网站基本成型,最后三件事: + +### 4.1 部署 + +代码推送到 GitHub,部署到公网。 + +参考: + +- [Git 和 GitHub 工作流](../../backend/2.4-git-workflow/) +- [如何部署 Web 应用](../../backend/2.5-zeabur-deployment/) + +### 第七步:部署前检查 + +```text +请把我当成 0 基础,帮我检查项目是否具备部署条件。 + +检查重点: +- 环境变量是否完整 +- 登录回调地址是否正确 +- Stripe 支付回调地址是否正确 +- 页面是否缺少 loading、空状态、错误提示 +- README 是否包含启动说明和部署说明 + +需要你: +1. 按优先级列出待修复事项 +2. 标注哪些必须先修 +3. 说明修复后的部署步骤 +``` + +### 4.2 README + +至少包含: +- 项目简介 +- 核心页面说明 +- 技术栈 +- 本地启动步骤 +- 环境变量清单 + +### 4.3 演示材料 + +至少准备: +- 首页截图 +- Dashboard 生成截图 +- Billing 页面截图 +- Admin 后台截图 +- 60 秒左右演示视频 + +## 5. 最终成果 + +按这篇指南做完,你拿到的不是"练习页",而是一个**最小但完整的 SaaS 产品**: + +- ✅ 现代组件库前端 +- ✅ Supabase 数据库与登录 +- ✅ 真实 AI 生成功能 +- ✅ Stripe 支付系统 +- ✅ 管理员后台 +- ✅ 可部署上线 + +这完全够资格作为**第一个全栈作品**。 + +## 6. 提交前最后检查 + + + + +
    +
  • +
  • +
  • +
  • +
  • +
  • +
+
+ +::: tip 🚀 下一篇 +完成这个网站后,继续阅读 [大作业 2:现代前端组件库 + Trae 实战](../2.2-modern-frontend-trae/),把界面质量再提升一层。 +::: diff --git a/docs/zh-cn/stage-2/assignments/custom-dify-agent-platform/index.md b/docs/zh-cn/stage-2/assignments/custom-dify-agent-platform/index.md new file mode 100644 index 0000000..83a262c --- /dev/null +++ b/docs/zh-cn/stage-2/assignments/custom-dify-agent-platform/index.md @@ -0,0 +1,7 @@ +# 自制 Dify 智能体平台 + +::: warning 🚧 建设中 + +本文档正在编写中,内容可能会有较大变动。请关注更新。 + +::: diff --git a/docs/zh-cn/stage-2/assignments/exam-management-express/index.md b/docs/zh-cn/stage-2/assignments/exam-management-express/index.md new file mode 100644 index 0000000..e6a9717 --- /dev/null +++ b/docs/zh-cn/stage-2/assignments/exam-management-express/index.md @@ -0,0 +1,6 @@ +# 大作业 2:在线考试与管理系统 + +自动出题答题、后台记录每次测试、有管理员和学生模式。。。支持正常登录。。 + +有管理系统、前端正常页面系统。。。。使用现代组件库。 +> 本章节正在编写中,敬请期待... diff --git a/docs/zh-cn/stage-2/assignments/modern-landing-page/index.md b/docs/zh-cn/stage-2/assignments/modern-landing-page/index.md new file mode 100644 index 0000000..804f473 --- /dev/null +++ b/docs/zh-cn/stage-2/assignments/modern-landing-page/index.md @@ -0,0 +1,7 @@ +# 现代网页落地页 + +::: warning 🚧 建设中 + +本文档正在编写中,内容可能会有较大变动。请关注更新。 + +::: diff --git a/docs/zh-cn/stage-2/assignments/movie-recommendation-springboot/index.md b/docs/zh-cn/stage-2/assignments/movie-recommendation-springboot/index.md new file mode 100644 index 0000000..be76a42 --- /dev/null +++ b/docs/zh-cn/stage-2/assignments/movie-recommendation-springboot/index.md @@ -0,0 +1,7 @@ +# 电影推荐网 (Spring Boot) + +::: warning 🚧 建设中 + +本文档正在编写中,内容可能会有较大变动。请关注更新。 + +::: diff --git a/docs/zh-cn/stage-2/assignments/simple-grocery-microservices/index.md b/docs/zh-cn/stage-2/assignments/simple-grocery-microservices/index.md new file mode 100644 index 0000000..08e2b14 --- /dev/null +++ b/docs/zh-cn/stage-2/assignments/simple-grocery-microservices/index.md @@ -0,0 +1,7 @@ +# 简单买菜微服务网站 + +::: warning 🚧 建设中 + +本文档正在编写中,内容可能会有较大变动。请关注更新。 + +::: diff --git a/docs/zh-cn/stage-2/assignments/traffic-data-visualization-go/index.md b/docs/zh-cn/stage-2/assignments/traffic-data-visualization-go/index.md new file mode 100644 index 0000000..ce9c7b4 --- /dev/null +++ b/docs/zh-cn/stage-2/assignments/traffic-data-visualization-go/index.md @@ -0,0 +1,7 @@ +# 交通大数据可视化分析 (Go项目) + +::: warning 🚧 建设中 + +本文档正在编写中,内容可能会有较大变动。请关注更新。 + +::: diff --git a/docs/zh-cn/stage-2/assignments/travel-planning-agent-platform/index.md b/docs/zh-cn/stage-2/assignments/travel-planning-agent-platform/index.md new file mode 100644 index 0000000..e96d6a2 --- /dev/null +++ b/docs/zh-cn/stage-2/assignments/travel-planning-agent-platform/index.md @@ -0,0 +1,7 @@ +# 智能旅游规划 Agent 平台 + +::: warning 🚧 建设中 + +本文档正在编写中,内容可能会有较大变动。请关注更新。 + +::: diff --git a/docs/zh-cn/stage-2/backend/2.7-stripe-payment/index.md b/docs/zh-cn/stage-2/backend/2.7-stripe-payment/index.md index ebaa6b8..15b3a94 100644 --- a/docs/zh-cn/stage-2/backend/2.7-stripe-payment/index.md +++ b/docs/zh-cn/stage-2/backend/2.7-stripe-payment/index.md @@ -1,3 +1,907 @@ # 如何集成 Stripe 等收费系统 -> 本章节正在编写中,敬请期待... +当你的产品已经有了页面、登录、数据库和基础后端之后,下一个现实问题就是:**怎么收费**。 + +很多人第一次接支付,会把注意力全放在"怎么跳转到付款页"上。但真正决定系统是否稳定的,不是按钮,而是整条收费链路:谁决定价格、谁确认支付成功、谁更新数据库、谁回收权限。 + +这篇文章我帮你拆成两部分: + +- **前半部分**只讲最实用的基础接入,目标是让你尽快把 Stripe 接进项目。 +- **后半部分**统一放到附录,包含 Webhook 细节、订阅事件、不同国家和地区的支付方案差异。 + +> 💡 建议先学完这些章节再继续 +> +> - [从数据库到 Supabase](../2.2-database-supabase/) +> - [大模型辅助编写接口代码与接口文档](../2.3-ai-interface-code/) +> - [如何部署 Web 应用](../2.5-zeabur-deployment/) + +# 你将学到 + +1. 最小可行的支付系统到底长什么样。 +2. 如何用最快的方式把 Stripe 接进你的项目。 +3. 如何写提示词,让 AI 直接帮你加支付系统。 +4. 如果不是做海外 Stripe 项目,不同地区应该优先考虑什么支付方案。 + +--- + +# 第一部分:基础上手 + +## 1. 先记住 3 个原则 + +如果你只记住三件事,就记住下面这三条: + +1. **价格必须由后端决定**,不能相信前端传来的金额。 +2. **真正让权限生效的是 Webhook**,不是 `success` 页面。 +3. **你自己的数据库必须保存支付状态**,不能只依赖 Stripe 后台。 + +这三条是支付系统最核心的边界。只要边界没错,后面换 Stripe、PayPal、支付宝、微信支付,本质上都只是"接口换了,架构不变"。 + +## 2. 如果不在后端处理,而是前端直接连 Stripe,会怎么样? + +这是很多人第一次做支付时最自然的想法: + +- 页面上已经有"购买"按钮了 +- 那我能不能让前端自己去连 Stripe +- 这样是不是就不用做后端了 + +如果你只是做一个假的演示页面,这样想当然没问题。 +但如果你是真的要收钱,**这条路通常会把事情做坏**。 + +最常见的问题有这几个: + +1. **价格容易被改** + 浏览器里的请求,是用户自己电脑上发出去的。别人是可以改请求内容的。 +2. **敏感信息容易暴露** + 真正重要的密钥、价格逻辑、会员开通逻辑,本来就不该放在前端。 +3. **你没法可靠确认"这笔钱到底算不算成功"** + 用户跳到成功页,不代表你的数据库已经同步对了。 +4. **数据库状态会乱** + 用户可能说"我明明已经付钱了",但你自己的系统里根本没记上。 + +所以更安全的分工应该是: + +- 前端负责:展示按钮、发起购买、跳转页面 +- 后端负责:决定价格、创建支付会话、接收 Webhook、更新数据库 + +::: info 这一段你可以直接记成一句话 +**前端可以负责跳转,后端必须负责定价和确认。** + +只要是真收钱,就不要把"最终价格决定权"和"支付成功后的开通逻辑"放在前端。 +::: + +## 3. 什么时候适合先用 Stripe + +如果你做的是下面这些场景,Stripe 往往是最顺手的起点: + +- 面向海外用户的 SaaS +- 订阅制会员产品 +- 数字产品、模板、AI 积分包 +- 想先快速验证商业化,而不是一开始就处理太多本地支付细节 + +如果你的主要用户在中国大陆,那通常不会把 Stripe 当第一选择,这个我放到附录里统一讲。 + +## 4. 最小可行支付链路 + +先看最小版本。只要这条链路能跑通,你的支付系统就有了骨架。 + +```mermaid +flowchart LR + user["用户"] + frontend["前端页面"] + backend["你的后端"] + checkout["Stripe Checkout"] + webhook["Stripe Webhook"] + db["Supabase / 业务数据库"] + + user -->|"点击购买"| frontend + frontend -->|"请求创建支付会话"| backend + backend -->|"按后端价格创建 Session"| checkout + frontend -->|"跳转到支付页"| checkout + checkout -->|"支付完成后发送事件"| webhook + webhook -->|"校验签名并更新状态"| backend + backend -->|"写入 orders / subscriptions"| db + db -->|"前端刷新后读取最新状态"| frontend +``` + +把它翻译成人话就是: + +1. 用户点按钮。 +2. 前端找后端要支付链接。 +3. 后端用 Stripe 密钥创建支付会话。 +4. 用户去 Stripe 页面付款。 +5. Stripe 把"付款真的成功了"这件事通过 Webhook 通知你。 +6. 你的后端再去更新数据库。 + +## 5. 发起付款的标准时序图 + +如果你习惯看更规范的系统图,可以直接看这张时序图: + +```mermaid +sequenceDiagram + autonumber + actor User as 用户 + participant Frontend as 前端页面 + participant Backend as 后端 API + participant Stripe as Stripe Checkout + + User->>Frontend: 点击"升级"或"购买" + Frontend->>Backend: POST /api/billing/create-checkout-session + Note right of Frontend: 前端传 plan / userId / email\n不传最终收费金额 + Backend->>Backend: 校验套餐并映射 priceId + Backend->>Stripe: 创建 Checkout Session + Stripe-->>Backend: 返回 session.url + Backend-->>Frontend: 返回支付链接 + Frontend-->>User: 跳转到 Stripe 支付页 + User->>Stripe: 完成付款 +``` + +## 6. 快速开始 + +如果你想最快把它接进项目,照着下面这 5 步做就够了。 + +### 6.1 第一步:在 Stripe 后台创建商品和价格 + +这一步的目的,不是"先随便配点东西",而是先把 **你到底在卖什么、打算怎么收费** 这件事在 Stripe 里定义清楚。 + +在 Stripe 的模型里: + +- **Product** 表示"你卖的是什么",比如 `Pro 会员` +- **Price** 表示"这个东西卖多少钱、按什么周期卖",比如 `月付 9.9 美元`、`年付 99 美元` + +为什么要先做这一步? +因为后面当你的后端创建 Checkout Session 时,并不是直接传一个金额给 Stripe,而是要传一个已经存在的 `price_id`。Stripe 再根据这个 `price_id` 去生成真正的支付页、金额、币种和订阅周期。 + +如果你跳过这一步,后面的"创建支付链接"其实就没法做。 + +::: info 为什么这里要先停一下 +很多新手看到 `Product`、`Price` 这两个词会有点烦,觉得像是在学 Stripe 的内部术语。 + +但实际上,这一步是在做一件很朴素的事: +- 把"卖什么"定义清楚 +- 把"卖多少钱"定义清楚 +- 让后端之后能拿一个稳定的 `price_id` 去创建支付链接 + +只要把这层想明白,后面的 Checkout Session 就不会觉得抽象。 +::: + +对于一个最小可行的订阅系统,你至少先建这两个层级: + +- 一个 `Product` +- 一个或多个 `Price` + +你可以直接打开这些页面: + +- Stripe Dashboard 登录页:[Dashboard Login](https://dashboard.stripe.com/login) +- Stripe 商品与价格管理文档:[Manage products and prices](https://docs.stripe.com/products-prices/manage-prices) +- Stripe Checkout 快速开始文档:[Build a Stripe-hosted checkout page](https://docs.stripe.com/checkout/quickstart?lang=node) +- Stripe Dashboard 商品页:[Product catalog](https://dashboard.stripe.com/test/products) + +推荐你先在 **Test mode(测试模式)** 下操作,不要一开始就在正式环境里建。 + +一个最常见的最小配置是: + +- `Product`: `Pro Plan` +- `Price 1`: `pro_monthly` +- `Price 2`: `pro_yearly` + +你在后台操作时,可以按这个顺序理解: + +1. 先创建一个商品 `Pro Plan` +2. 再在这个商品下面挂两个价格 +3. 月付和年付其实是同一个商品的两种收费方式 + +完成后,你至少要记下这些信息: + +- 月付价格的 `price_id` +- 年付价格的 `price_id` +- 你自己的套餐名,例如 `pro_monthly`、`pro_yearly` + +如果你是第一次进 Stripe 后台,建议你把这一步理解成: + +- `Product` 决定支付页里卖的是什么 +- `Price` 决定支付页里收多少钱 +- 后端之后真正会用到的,主要是 `price_id` + +::: info 真正要记下来的值 +这一页里最重要的不是商品名称,而是 `price_id`。 + +后面无论是让 AI 帮你接后端,还是你自己排查问题,真正会频繁用到的,通常都是: +- `STRIPE_PRICE_PRO_MONTHLY` +- `STRIPE_PRICE_PRO_YEARLY` +- 它们背后对应的两个 `price_id` +::: + +如果你想让 AI 先带你把后台配置做完,可以直接用这个 prompt: + +```text +我现在是第一次用 Stripe,你先不要改代码,先带我在 Stripe 后台把最基本的付费配置做好。 + +请基于这些官方文档给我一步一步的操作说明: +- https://docs.stripe.com/products-prices/manage-prices +- https://docs.stripe.com/checkout/quickstart?lang=node + +我的情况是: +- 我想做一个最简单的会员付费 +- 只有两个套餐:月付和年付 +- 我现在还不懂 Product、Price 这些词 + +请你: +1. 先用最简单的话告诉我 Product 和 Price 分别是什么。 +2. 再按"先打开哪个页面 -> 点哪里 -> 填什么"的顺序教我操作。 +3. 最后提醒我,做完以后我需要从后台复制哪些内容给后端使用。 +4. 如果我容易走错,请顺便提醒我应该一直在测试模式里操作。 +``` + +### 6.2 第二步:准备环境变量 + +你通常至少需要准备这些环境变量: + +- `STRIPE_SECRET_KEY` +- `STRIPE_WEBHOOK_SECRET` +- `STRIPE_PRICE_PRO_MONTHLY` +- `STRIPE_PRICE_PRO_YEARLY` +- `APP_URL` +- `SUPABASE_URL` +- `SUPABASE_SERVICE_ROLE_KEY` + +你可以直接打开这些页面: + +- Stripe API Keys 文档:[API keys](https://docs.stripe.com/keys) +- Stripe Dashboard API Keys 页面:[API Keys](https://dashboard.stripe.com/test/apikeys) +- Stripe Webhooks 文档:[Receive Stripe events in your webhook endpoint](https://docs.stripe.com/webhooks) +- Stripe Dashboard Webhooks 页面:[Workbench Webhooks](https://dashboard.stripe.com/test/workbench/webhooks) + +> ⚠️ `STRIPE_SECRET_KEY` 和 `SUPABASE_SERVICE_ROLE_KEY` 都只能放在后端。 + +::: info 环境变量这一步的目的 +这一步不是为了"先把 `.env` 填满",而是为了把支付系统里最敏感的几样东西放到后端保管: + +- Stripe 的后端密钥 +- Webhook 验签密钥 +- 你自己的价格映射 + +简单理解: +前端只负责发起购买,真正的秘密和定价逻辑都应该留在服务端。 +::: + +这一步也可以直接让 AI 帮你整理: + +```text +请你先看看我这个项目现在是怎么放环境变量的,然后帮我把 Stripe 需要的环境变量整理出来。 + +请参考这些文档: +- https://docs.stripe.com/keys +- https://docs.stripe.com/webhooks + +我的情况是: +- 我是零基础 +- 我分不清哪些变量应该放前端,哪些应该放后端 +- 我也不确定当前项目应该改 `.env`、`.env.local` 还是别的文件 + +请你: +1. 先搜索当前项目里环境变量通常写在哪。 +2. 帮我列出 Stripe 接入最少需要哪些变量。 +3. 用最简单的话告诉我每个变量是干什么的。 +4. 告诉我每个变量应该去哪一个 Stripe 页面复制。 +5. 如果项目里有示例环境变量文件,请直接帮我补上变量名。 +``` + +### 6.3 第三步:后端创建 Checkout Session + +这一步你不用自己写接口,直接让 AI 参考官方文档帮你实现。 + +先把这些文档给它: + +- Stripe Checkout 快速开始:[Build a Stripe-hosted checkout page](https://docs.stripe.com/checkout/quickstart?lang=node) +- Checkout Sessions API:[Create a Checkout Session](https://docs.stripe.com/api/checkout/sessions/create) +- 订阅说明:[Subscriptions](https://docs.stripe.com/payments/subscriptions) + +然后直接贴这个 prompt: + +```text +请你先看看我当前项目的后端代码是怎么组织的,然后帮我把 Stripe 支付接进去。 + +请参考这些官方文档: +- https://docs.stripe.com/checkout/quickstart?lang=node +- https://docs.stripe.com/api/checkout/sessions/create +- https://docs.stripe.com/payments/subscriptions + +我的目标很简单: +- 用户点购买按钮后,能跳到 Stripe 的付款页面 +- 套餐只有月付和年付两种 +- 不要让我自己决定代码该放在哪,你先看项目再帮我放到合适的位置 + +请你: +1. 先搜索项目,弄清楚后端入口文件、路由文件、环境变量写法分别在哪里。 +2. 再参考官方文档,帮我把"创建 Stripe 支付链接"这一步接进去。 +3. 不要让我自己传金额,价格请用后端环境变量来决定。 +4. 做完后告诉我你改了哪些文件。 +5. 最后告诉我,我还需要去 Stripe 后台补哪些配置。 +``` + +### 6.4 第四步:前端跳转到支付页 + +这一步的目标非常简单:让定价页按钮调用你的后端接口,再跳转到 Stripe Checkout。 + +参考文档: + +- Stripe Checkout 集成说明:[Build an integration with Checkout](https://docs.stripe.com/payments/checkout/build-integration) + +给 AI 的 prompt: + +```text +帮我把项目里的"购买"按钮接上 Stripe。 + +要求: +- 不动现有页面,只改按钮点击后的逻辑 +- 点击后调用后端接口获取支付链接,然后跳转到 Stripe +- 如果出错,给用户一个简单提示(比如"支付暂时不可用,请稍后再试") + +参考文档:https://docs.stripe.com/payments/checkout/build-integration +``` + +### 6.5 第五步:Webhook 更新数据库状态 + +这是最关键的一步。 + +::: info 为什么这一步最关键 +很多人会以为"用户付完款并且跳转到了 success 页面"就算完成了。 + +不是。 + +对你的系统来说,真正重要的是: +**Stripe 有没有正式把事件打到你的 Webhook,而你的后端有没有把数据库状态更新成功。** +::: + +你也可以让 AI 按 Stripe 官方 Webhook 文档直接实现,不要自己手写。 + +参考文档: + +- Stripe Webhooks:[Receive Stripe events in your webhook endpoint](https://docs.stripe.com/webhooks) +- Stripe CLI:[Stripe CLI](https://docs.stripe.com/stripe-cli) +- Stripe CLI 用法:[Use the Stripe CLI](https://docs.stripe.com/stripe-cli/use-cli) + +给 AI 的 prompt: + +```text +请继续帮我把 Stripe 的"付款成功后自动生效"这一步接好。 + +请参考这些官方文档: +- https://docs.stripe.com/webhooks +- https://docs.stripe.com/stripe-cli +- https://docs.stripe.com/stripe-cli/use-cli + +我的目标是: +- 用户付完钱后,不只是跳转到成功页面 +- 而是真的把我数据库里的会员状态改成已开通 + +请你: +1. 先搜索当前项目里数据库相关代码和用户状态是怎么存的。 +2. 再帮我加 Stripe webhook。 +3. 支付成功后,把对应用户改成 active,或者更新成项目里现在已经在用的会员状态字段。 +4. 如果项目里已经有订阅表、订单表、用户表,请优先沿用现有结构。 +5. 做完后告诉我你改了哪些文件。 +6. 顺便告诉我本地怎么测试这一步有没有真的生效。 +``` + +## 7. 让 AI 帮你快速接入的提示词 + +如果你用的是 Codex、Claude Code、Trae、Cursor 一类工具,可以直接把下面这个提示词贴给它,让它在你的项目里做支付接入。 + +```text +请你帮我把当前项目接上 Stripe 支付,我希望做一个最简单能跑起来的会员收费功能。 + +我的要求: +1. 我是零基础,请你先自己看项目,再决定代码应该改哪里。 +2. 不要让我自己判断目录结构、路由结构、数据库结构。 +3. 我只想先做最简单版本:月付和年付两个套餐。 +4. 用户点击购买后,能跳到 Stripe 付款页面。 +5. 付款成功后,我数据库里的会员状态能变成已开通。 +6. 不要一开始加太多复杂功能,比如优惠券、升级降级、复杂发票。 + +输出要求: +1. 先给我一个改动计划。 +2. 然后直接修改代码。 +3. 最后告诉我怎么一步一步本地测试。 +4. 如果有哪个步骤还需要我去 Stripe 后台操作,请直接把链接和要点告诉我。 +``` + +如果你希望 AI 更贴近你的项目,还可以在开头补上: + +- 你的前端框架 +- 你的后端目录结构 +- 你的数据库表名 +- 你现在的用户系统是 Supabase Auth 还是自建 Auth + +## 7.1 本地联调也尽量交给 AI + +如果你希望连本地联调都让 AI 帮你串起来,可以直接用下面这段: + +```text +请继续帮我把 Stripe 支付真正跑通,我想一步一步照着做,不想自己猜。 + +请参考官方文档: +- https://docs.stripe.com/webhooks +- https://docs.stripe.com/stripe-cli +- https://docs.stripe.com/stripe-cli/use-cli + +我的目标: +1. 告诉我先打开哪些 Stripe 页面。 +2. 告诉我如何拿到 STRIPE_WEBHOOK_SECRET。 +3. 告诉我如何使用 stripe login 和 stripe listen。 +4. 告诉我怎样验证 checkout.session.completed 已经成功打到本地 webhook。 +5. 如果当前项目需要先启动前端和后端,也请顺带告诉我具体命令。 +6. 不要只讲原理,请按实际操作步骤输出。 +7. 如果我某一步做错了,也请告诉我最常见的报错会长什么样。 +``` + +## 8. 最容易踩坑的 4 件事 + +1. **把 `success` 页面当成支付成功** + 真正决定状态的是 Webhook,不是前端跳转。 +2. **让前端传金额** + 这会带来严重的价格篡改风险。 +3. **Webhook 路由被 `express.json()` 提前处理** + Stripe 验签需要原始请求体。 +4. **没有做幂等处理** + Webhook 可能重试,如果你每次都重复加会员或积分,就会出事故。 + +## 9. 一句话选型建议 + +如果你现在只是想先把收费跑起来: + +| 你的主要用户 | 最先尝试的方案 | +| :--- | :--- | +| 海外 SaaS / 国际用户 | Stripe | +| 中国大陆用户 | 支付宝 / 微信支付 | +| 香港或跨境团队 | Stripe + 本地钱包 / FPS 聚合方案 | + +后面的具体区别,我统一放到附录。 + +::: info 最简单的选型思路 +不要一开始就想"我要把全球支付方式一次全接完"。 + +更实际的顺序通常是: +- 先按主要用户所在地区选一条主支付链路 +- 先把最小可行支付跑通 +- 再根据真实用户来源补第二、第三种支付方式 +::: + +## 10. 小结 + +到这里,你已经掌握了最基础但最重要的一条收费链路: + +1. 前端发起购买。 +2. 后端创建 Checkout Session。 +3. 用户在 Stripe 页面支付。 +4. Stripe 通过 Webhook 通知后端。 +5. 后端更新数据库。 +6. 前端刷新后显示新的会员或订单状态。 + +如果你只想快速把支付接进项目,前面的内容已经够用了。下面的附录你可以在真正遇到问题时再回来看。 + +--- + +# 附录 + +## 附录 A:Stripe 里最常见的几个对象 + +第一次看 Stripe 文档,最容易被这些对象名绕晕。你其实只需要先理解下面几个: + +| 对象 | 作用 | 你可以把它理解成什么 | +| :--- | :--- | :--- | +| `Product` | 描述卖的是什么 | 商品或会员套餐 | +| `Price` | 描述卖多少钱、周期怎么收费 | 月付、年付、买断 | +| `Checkout Session` | Stripe 托管的支付流程 | 付款页 | +| `Subscription` | 周期订阅关系 | 自动续费会员 | +| `Customer` | 付款用户 | Stripe 中的客户档案 | +| `Webhook` | 异步通知 | Stripe 告诉你"这笔款怎么样了" | + +## 附录 B:为什么 `success` 页面不等于支付成功 + +很多人以为"用户付完钱,跳到了 success 页面"就算支付成功了。这是最容易踩的坑。 + +### 先讲一个真实场景 + +假设你做了一个会员网站: +1. 用户点击"购买会员" +2. 跳转到 Stripe 付款页面 +3. 用户输入信用卡,点击付款 +4. 页面跳转到你的 `success.html` +5. 你在 success 页面写代码:"既然到了这页,就给用户开通会员" + +**问题在哪?** + +用户可能根本没付钱,或者付到一半关页面了,也能直接访问 `success.html`。 + +### 两条完全不同的路径 + +```mermaid +flowchart TB + pay["用户在 Stripe 完成支付"] + + subgraph unreliable["❌ 不可靠路径:只看 success 页面"] + success["浏览器跳到 success 页面"] + fake["前端代码认为已开通"] + risk["风险:关页 / 断网 / 伪造 URL / 根本没付钱"] + success --> fake --> risk + end + + subgraph reliable["✅ 可靠路径:以后端 Webhook 为准"] + event["Stripe 服务器发送 Webhook"] + verify["后端校验签名"] + active["数据库正式更新为已付费"] + event --> verify --> active + end + + pay --> success + pay --> event +``` + +**关键区别:** + +| | success 页面跳转 | Webhook 通知 | +| :--- | :--- | :--- | +| 谁发起的 | 用户的浏览器 | Stripe 的服务器 | +| 能伪造吗 | 能,直接访问 URL 就行 | 不能,有签名验证 | +| 一定代表付款成功吗 | 不一定 | 一定 | +| 你的系统怎么知道 | 前端代码猜的 | Stripe 正式通知的 | + +### 完整流程应该是怎样的 + +```mermaid +sequenceDiagram + autonumber + actor User as 用户 + participant Frontend as 你的网页 + participant Stripe as Stripe + participant Webhook as 你的后端接口 + participant DB as 数据库 + + User->>Stripe: 在 Stripe 页面完成付款 + Note over Stripe: 钱真的到了 Stripe 账户 + + Stripe-->>Frontend: 浏览器跳转到 success 页面 + Note over Frontend: ⚠️ 这步只是跳转
不代表系统已确认 + + Stripe->>Webhook: 发送 Webhook 通知
"checkout.session.completed" + Note over Webhook: ✅ 这才是正式通知 + + Webhook->>Webhook: 校验签名
(确保是 Stripe 发的,不是黑客) + + Webhook->>DB: 更新用户状态为"已付费" + DB-->>Webhook: 保存成功 + Webhook-->>Stripe: 返回 200 OK + + Frontend->>DB: 用户刷新页面,查询状态 + DB-->>Frontend: 返回"已付费" + Note over Frontend: 这时候才显示会员功能 +``` + +### 每个环节的卡点 + +**第 1 步:用户在 Stripe 付款** + +这是唯一确定"钱真的付了"的时刻: +- 用户输入信用卡信息,点击确认 +- 银行从用户卡里扣款 +- Stripe 确认收到这笔钱 + +**第 2 步:浏览器跳转到 success 页面(问题最大)** + +这一步完全不可靠,因为: +- 用户可以直接在浏览器输入 `yoursite.com/success`,根本没付钱也能访问 +- 用户付到一半关页面了,但之前复制了 success 链接,之后直接打开 +- 网络问题导致跳转失败,但钱已经扣了(用户付了钱却没看到成功页面) +- 用户点返回键,又付了一次钱,但两次都跳转到同一个 success 页面 + +**第 3 步:Stripe 发送 Webhook** + +这是 Stripe 主动通知你的服务器"这笔款到账了": +- 只有 Stripe 的服务器能发起这个请求 +- 请求里带有签名,你的后端可以验证是不是真的 Stripe 发的 +- 即使 success 页面没打开、用户断网了,Webhook 也会发送 + +**第 4 步:后端校验签名** + +为什么要校验?防止黑客伪造通知。 + +假设没有校验,黑客可以直接给你的服务器发一个假通知:"用户 A 付了 1000 元"。你的系统就会给黑客开通会员。 + +校验的过程: +- Stripe 用你们约定的密钥对通知内容生成签名 +- 你的后端用同样的密钥验证签名是否匹配 +- 匹配 = 100% 是 Stripe 发的,不匹配 = 直接拒绝 + +**第 5 步:更新数据库** + +只有校验通过后,才更新数据库: +- 把用户状态从"待付款"改成"已付费" +- 记录订单号、金额、付款时间 +- 开通对应的会员权限 + +**第 6 步:前端查询状态** + +success 页面不要自己判断"到了这页就是成功了"。正确的做法: +- 页面加载时,向后端发送请求:"这个用户付费了吗?" +- 后端查数据库,返回真实状态 +- 根据返回结果显示"开通成功"或"等待确认" + +### 一个常见的错误做法 + +```javascript +// 错误:在 success 页面直接开通 +// success.html +if (window.location.pathname === '/success') { + // 危险!任何人都能访问 /success + activateMembership(); +} +``` + +```javascript +// 正确:每次刷新都查后端 +// success.html +async function checkStatus() { + const response = await fetch('/api/user/status'); + const data = await response.json(); + + if (data.paymentStatus === 'paid') { + showMemberFeatures(); + } else { + showPendingMessage(); + } +} +``` + +### 总结一句话 + +**success 页面只是"浏览器跳转成功",Webhook 才是"Stripe 正式确认收款"。** + +你的系统必须以 Webhook 为准,不能相信前端的跳转。 + +## 附录 C:订阅系统最值得监听的事件 + +| 事件 | 含义 | 你通常要做什么 | +| :--- | :--- | :--- | +| `checkout.session.completed` | 首次开通成功 | 创建本地订阅记录 | +| `invoice.paid` | 自动续费成功 | 延长有效期 | +| `invoice.payment_failed` | 自动扣费失败 | 标记风险状态并提醒用户 | +| `customer.subscription.deleted` | 订阅取消 | 回收权限或标记到期后失效 | + +### 订阅状态图 + +```mermaid +stateDiagram-v2 + [*] --> NotStarted: 用户未购买 + NotStarted --> Active: checkout.session.completed + Active --> Active: invoice.paid + Active --> PastDue: invoice.payment_failed + PastDue --> Active: 用户补款成功 + Active --> Canceled: customer.subscription.deleted + PastDue --> Canceled: 到期未恢复 + Canceled --> [*] + + state "未开通" as NotStarted + state "会员有效" as Active + state "扣费失败 / 待恢复" as PastDue + state "已取消 / 到期回收" as Canceled +``` + +### 续费 / 失败 / 取消时序图 + +```mermaid +sequenceDiagram + autonumber + participant Stripe as Stripe + participant Webhook as 你的 Webhook 接口 + participant DB as 订阅表 / 订单表 + participant App as 你的应用 + actor User as 用户 + + rect rgb(235, 248, 255) + Stripe->>Webhook: invoice.paid + Webhook->>DB: 延长 current_period_end + DB-->>Webhook: 更新成功 + Webhook-->>Stripe: 200 OK + App-->>User: 继续保持会员有效 + end + + rect rgb(255, 247, 237) + Stripe->>Webhook: invoice.payment_failed + Webhook->>DB: 标记 past_due + DB-->>Webhook: 更新成功 + Webhook-->>Stripe: 200 OK + App-->>User: 提醒更新支付方式 + end + + rect rgb(254, 242, 242) + Stripe->>Webhook: customer.subscription.deleted + Webhook->>DB: 标记 canceled + DB-->>Webhook: 更新成功 + Webhook-->>Stripe: 200 OK + App-->>User: 停止高级权限 + end +``` + +## 附录 D:其他支付方案怎么选 + +### 1. 中国大陆 + +主要用户在大陆的话,首选还是 **[支付宝](https://open.alipay.com/)** 和 **[微信支付](https://pay.wechatpay.cn/)**。 + +**业务模式:** + +两者都是"支付网关"模式。你需要: +- 申请商户资质(营业执照、对公账户) +- 用户付的钱直接到你的商户账户 +- 你自己负责税务、退款、对账 + +**技术模式:** + +两者都是"后端下单 + 前端调起 + 后端通知"的模型,跟 Stripe 思路一样。 + +**支付宝接入流程:** +1. 在支付宝开放平台创建应用 +2. 配置公私钥和回调地址 +3. 后端调用统一下单接口,生成支付链接或二维码 +4. 用户扫码或跳转付款 +5. 支付宝异步通知你的后端,更新订单状态 + +**微信支付接入流程:** +- JSAPI 支付:适合公众号、小程序,用户在微信内直接付款 +- Native 支付:PC 端生成二维码,用户扫码付款 +- H5 支付:手机浏览器内拉起微信 App 付款 + +流程:后端下单 → 拿到 `prepay_id` 或 `code_url` → 前端调起支付 → 后端接收通知确认成功 + +**参考链接:** +- 支付宝开放平台:https://open.alipay.com/ +- 微信支付商户文档:https://pay.wechatpay.cn/doc/v3/merchant/ + +### 2. 香港 + +香港市场比较混合,常见组合: + +- 银行卡:Visa / Mastercard +- FPS(转数快):香港本地即时转账 +- AlipayHK / WeChat Pay HK:香港版支付宝和微信 + +**推荐组合:** +- 用 **[Stripe](https://stripe.com/hk)** 覆盖国际卡和订阅 +- 用 **[Airwallex](https://www.airwallex.com/)** 或 **[Adyen](https://www.adyen.com/)** 补本地钱包和 FPS + +### 3. 海外 / 国际 SaaS + +#### [Stripe](https://stripe.com/) + +**业务模式:** 支付网关 + +- 你需要自己申请商户资质(部分国家 Stripe 可以帮你搞定) +- 用户付的钱到你的 Stripe 账户,再结算到你的银行账户 +- 你自己负责税务申报 + +**技术模式:** + +- API 体验最好,文档清晰 +- 支持 Checkout(托管页面)、Elements(自定义表单)、Payment Links(无代码) +- Webhook 通知支付状态 +- 支持订阅、发票、多币种 + +**适合谁:** 海外 SaaS、独立开发者、需要灵活定制的团队 + +**参考链接:** https://docs.stripe.com/ + +#### [PayPal](https://www.paypal.com/) + +**业务模式:** 支付网关 + +- 用户付的钱到你的 PayPal 账户,再提现到银行 +- 你自己负责税务 + +**技术模式:** + +- 一次性支付:前端放按钮,后端创建/确认订单 +- 订阅制:先建 Product 和 Plan,再用 SDK 拉起 +- 同样需要后端和 Webhook,不要只看前端回调 + +**适合谁:** 需要补充渠道的海外业务,用户习惯用 PayPal 付款 + +**参考链接:** https://developer.paypal.com/docs/ + +#### [Paddle](https://www.paddle.com/) + +**业务模式:** Merchant of Record (MoR) + +- Paddle 是"记录商家",法律上由 Paddle 向用户收款 +- Paddle 帮你处理全球税务、VAT、退款、合规 +- 用户付的钱到 Paddle,Paddle 扣除税费和手续费后结算给你 +- 你不需要在每个国家注册公司或处理税务 + +**技术模式:** + +- Paddle.js:前端嵌入托管结账页 +- 后端 API:创建 transaction,交给 checkout 处理 +- Webhook 同步订阅状态 + +**适合谁:** 不想处理全球税务的 SaaS 团队,尤其是 B2B SaaS + +**参考链接:** https://developer.paddle.com/ + +#### [Lemon Squeezy](https://www.lemonsqueezy.com/) + +**业务模式:** Merchant of Record (MoR) + +- 和 Paddle 类似,Lemon Squeezy 是"记录商家" +- 帮你处理全球税务、VAT、合规 +- 2024 年被 Stripe 收购,但独立运营 + +**技术模式:** + +- Hosted Checkout:最简单,直接生成付款链接 +- Checkout Overlay:浮层嵌入你的页面 +- 后端 API:创建 checkout,灵活控制 + +**适合谁:** 独立开发者、数字产品、软件授权 + +**参考链接:** https://docs.lemonsqueezy.com/ + +### 4. 企业级方案 + +#### [Airwallex(空中云汇)](https://www.airwallex.com/) + +**业务模式:** 支付网关 + 全球账户 + +- 提供全球收款账户(类似虚拟银行账户) +- 支持多币种收款、换汇、付款 +- 你自己负责税务 + +**技术模式:** + +- Payment Links:几乎不用代码,生成付款链接 +- Hosted Payment Page:托管页面 +- Drop-in / Embedded / Native API:深度接入,自定义程度高 +- 支持 Alipay HK、FPS、WeChat Pay 等本地支付方式 + +**适合谁:** 香港团队、跨境业务、需要多币种账户的公司 + +**参考链接:** https://www.airwallex.com/docs/ + +#### [Adyen](https://www.adyen.com/) + +**业务模式:** 支付网关 + +- 企业级支付平台,年处理交易额万亿欧元 +- 支持线上、线下、移动端全渠道 +- 你自己负责税务 + +**技术模式:** + +- Pay by Link:最简单,生成付款链接 +- Drop-in / Components:标准线上接入 +- 后台可启用 Alipay、Alipay HK、PayMe 等本地支付方式 + +**适合谁:** 大型企业、需要全渠道支付的公司 + +**参考链接:** https://docs.adyen.com/ + +### 5. 方案对比 + +| 方案 | 业务模式 | 税务处理 | 适合谁 | +| :--- | :--- | :--- | :--- | +| Stripe | 支付网关 | 自己处理 | 海外 SaaS、开发者 | +| PayPal | 支付网关 | 自己处理 | 海外补充渠道 | +| Paddle | MoR | Paddle 代处理 | B2B SaaS、不想管税务 | +| Lemon Squeezy | MoR | LS 代处理 | 独立开发者、数字产品 | +| Adyen | 支付网关 | 自己处理 | 大型企业 | +| Airwallex | 支付网关 + 账户 | 自己处理 | 跨境业务、香港团队 | +| 支付宝/微信 | 支付网关 | 自己处理 | 大陆用户 | + +### 6. 按地区选方案 + +| 你的市场 | 推荐方案 | +| :--- | :--- | +| 中国大陆 | 支付宝 / 微信支付 | +| 香港 | Stripe + Airwallex / Adyen | +| 海外 SaaS | Stripe(自己管税务)或 Paddle(MoR 代管) | +| 海外数字产品 | Stripe / Lemon Squeezy / Paddle | +| 多地区企业级 | Adyen / Airwallex / Stripe 组合 | diff --git a/docs/zh-cn/stage-2/frontend/2.3-multi-product-ui/index.md b/docs/zh-cn/stage-2/frontend/2.3-multi-product-ui/index.md index 4309839..8e08665 100644 --- a/docs/zh-cn/stage-2/frontend/2.3-multi-product-ui/index.md +++ b/docs/zh-cn/stage-2/frontend/2.3-multi-product-ui/index.md @@ -1,3 +1,425 @@ -# 参考 UI 设计规范与多产品 UI 设计 +# 参考 UI 设计规范设计页面和按钮 -> 本章节正在编写中,敬请期待... +很多人说"我想让页面更像 Apple 一点""按钮想做得更高级一点",但真正开始做时,往往会卡在一个问题上: + +**到底该参考什么?** + +盯着截图模仿,学到的只是"像不像"。但打开 Apple、Google、Microsoft、Atlassian 的设计规范,你会发现它们真正厉害的地方不是视觉风格,而是**把设计问题讲清楚**:页面先突出什么、按钮如何分级、操作怎么强调——这些判断标准才是核心。 + +> 参考设计规范,不是为了做得"像谁",而是学会别人怎么做判断。 + +:::: info 为什么现在还要学这些 +设计规则早已被训练进模型、被设计工具默认吸收,甚至贴几张截图 AI 就能学会。但我们仍然有必要知道这些规则从哪来、为什么这样定。 +:::: + +## 先看几段原文,感受差距 + +如果你以前觉得“设计规范不就是讲讲风格吗”,先看几条官方原文。 + +平时我们在团队里经常会这样说: + +- 做个下拉框 +- 这里放个菜单 +- 菜单栏加几个功能 +- 这里放两个按钮,一个确认一个取消 + +听起来没问题,但在大厂规范里,这些词都不是模糊概念,而是被拆得非常细。 + +| 平时随口说的话 | 官方原文 | 简单说 | +| :--- | :--- | :--- | +| “做个菜单” | Apple: [“A menu reveals its options...”](https://developer.apple.com/design/human-interface-guidelines/menus) | `Menu` 是拿来做操作的 | +| “菜单栏里放功能” | Apple: [“menu bar menus contain all the commands...”](https://developer.apple.com/design/human-interface-guidelines/menus) | 这是应用顶部的命令菜单 | +| “做个下拉框” | Apple: [“A pop-up list lets the user choose one option among several.”](https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/MenuList/Articles/ManagingPopUpItems.html) | `pop-up` 是从列表里选一个 | +| “也做个下拉框” | Apple: [“A pull-down list is generally used for selecting commands in a specific context.”](https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/MenuList/Articles/ManagingPopUpItems.html) | `pull-down` 是点开做当前操作 | +| “菜单也能拿来筛选吧” | Fluent: [“If you need to collect information from people, try a select, dropdown, or combobox instead.”](https://fluent2.microsoft.design/components/web/react/core/menu/usage) | `Menu` 不是拿来选值的 | +| “菜单也能当导航吧” | Material: [“Menus should not be used as a primary method for navigation within an app.”](https://m1.material.io/components/menus.html) | `Menu` 不是主导航 | +| “按钮随便写个 OK / Cancel” | Apple: [“Always use ‘Cancel’ to title a button that cancels the alert’s action.”](https://developer.apple.com/design/human-interface-guidelines/alerts) | 按钮文字不能随便写 | + +> 表格里的引文都可以直接点击,跳到对应的官方页面。 + +这就是第一次真正看设计规范时最容易被震到的地方: + +> 我们平时以为自己在讨论 UI,实际上很多时候只是在用一堆含糊词交流。 + +Apple 不会只说“做个菜单”;它会继续区分: + +- `menu` +- `menu bar menu` +- `pop-up button` +- `pull-down button` +- `context menu` + +Fluent 不会只说“下拉框”;它会继续区分: + +- `menu` +- `dropdown` +- `select` +- `combobox` + +这就是设计规范的必要性。 + +它不是为了让页面显得更专业,而是为了让团队在讨论 UI 时,不再每个人脑子里都是不同的东西。 + +## 你将学到 + +1. 为什么设计页面和按钮时要先看设计规范 +2. Apple、Material、Fluent、Atlassian 这些规范里,哪些内容最值得参考 +3. 如何把“页面层级”和“按钮层级”设计清楚 +4. 如何让 AI 参考别人的规范来生成页面和按钮 + +## 1. 设计规范为什么能帮你把页面做清楚 + +看完上面这些原文,你会发现一个关键点: + +**设计规范不是锦上添花,而是在先把词说准。** + +很多页面不好看,不是因为配色不够高级,而是因为信息层级混乱。 + +很多按钮不好用,也不是因为圆角不对,而是因为: + +- 主按钮太多,用户不知道该点哪个 +- 危险按钮和普通按钮看起来差不多 +- 页面里所有按钮都在抢注意力 +- 不同页面里的按钮样式和语义不一致 + +成熟的设计规范,恰好就是在解决这些问题。它们通常会定义: + +| 规范内容 | 它解决什么问题 | +| :--- | :--- | +| **页面层级** | 先看哪里、后看哪里,信息怎么组织 | +| **视觉基础** | 颜色、间距、字体、圆角、阴影怎样统一 | +| **按钮层级** | 主按钮、次按钮、文字按钮、危险按钮如何区分 | +| **状态规则** | hover、focus、disabled、loading 怎么表现 | +| **交互语义** | 哪个按钮是“确认”,哪个是“取消”,哪个是“更多操作” | + +所以,设计规范真正提供的不是一套“皮肤”,而是一套**判断标准**。 + +## 2. 参考大厂规范时,重点看什么 + +### 2.1 参考 Apple:学习“定义得足够细”这件事 + +Apple 最值得学的,不只是视觉上的克制感,而是它会把概念定义得非常细。 + +同样是很多团队口中的“菜单”或“下拉框”,Apple 会继续往下拆: + +- `menu`:一组命令、选项或状态 +- `menu bar menu`:应用级命令集合 +- `pop-up button`:选择一个值 +- `pull-down button`:在当前上下文里触发命令 +- `context menu`:与当前对象或任务相关的常用动作 + +这套区分非常重要,因为它会直接影响: + +- 这个组件是拿来选值,还是拿来做动作 +- 它属于页面局部,还是属于应用级 +- 它应该长期显示当前选中值,还是只临时展开命令 + +当你开始按这种粒度思考时,你设计出来的页面就会一下子清楚很多。 + +### 2.2 参考 Apple:学习页面层级和克制感 + +Apple Human Interface Guidelines 特别适合学习两件事: + +- 页面如何建立清晰层级 +- 控件如何在不喧宾夺主的前提下保持明确 + +Apple 强调 `Hierarchy`、`Harmony`、`Consistency`。这意味着页面设计时要回答: + +- 当前页面最重要的信息是什么 +- 用户的主要任务是什么 +- 哪个操作该最显眼,哪个操作应该退后 + +如果你参考 Apple 来设计页面,可以重点借鉴: + +- 首屏信息不要太碎,核心内容先聚焦 +- 用留白、字号、分组建立秩序,而不是靠堆很多边框 +- 按钮不要全部高强调,只有关键动作才应该最突出 + +### 2.3 参考 Material:学习清晰的页面结构 + +Material Design 很适合学习“页面是怎么组织任务流”的。 + +它的很多组件和布局规范,核心都在帮助你明确: + +- 页面是浏览型,还是执行任务型 +- 当前页面是让用户阅读、选择,还是提交 +- 一个页面里哪些元素应该稳定重复,哪些元素应该响应上下文变化 + +如果你参考 Material 来设计页面,可以重点借鉴: + +- 页面区块清楚,模块职责明确 +- 导航、内容区、操作区分工清晰 +- 不同按钮样式对应不同操作优先级 + +### 2.4 参考 Fluent:学习组件边界和按钮层级 + +Fluent 2 很适合后台、工具型产品和复杂表单系统。它最值得学的地方,是会直接告诉你“不要混用概念”。 + +例如它明确写到:如果你要“collect information”,就不要继续用 `menu`,而应该考虑 `select`、`dropdown`、`combobox`。 + +这句话非常重要,因为它把很多人脑中的“都差不多”打碎了。 + +Fluent 2 也很重视: + +- 操作层级 +- 组件语义边界 +- 密集信息场景下的清晰度 + +如果你参考 Fluent 来设计按钮,可以重点借鉴: + +- `Primary button` 用来承接当前最重要的动作 +- `Secondary button` 用来承接支持性动作 +- `Subtle`、`Transparent` 这类弱强调按钮用于不该抢主流程的操作 +- 页面里的按钮数量越多,越要控制视觉优先级 + +### 2.5 参考 Atlassian:学习系统化地管理页面和按钮 + +Atlassian Design System 特别适合“一个团队做很多页面”的情况。它强调: + +- foundations 是共享基础 +- tokens 是统一视觉决策的方法 +- components 是被反复复用的交互构件 + +如果你参考 Atlassian 来做页面和按钮,最有价值的是: + +- 把按钮尺寸、颜色、圆角、间距做成统一规则 +- 把页面布局的节奏固定下来 +- 让不同页面虽然内容不同,但结构语言一致 + +## 3. 设计页面时,应该参考规范里的哪些点 + +当你看一个设计系统时,不要先问“这个页面好不好看”,而要先问下面几个问题。 + +### 3.1 页面第一眼,主次是不是明确 + +一个页面通常至少要有三层: + +- **主信息**:当前页面最重要的内容 +- **辅助信息**:帮助理解或补充的内容 +- **次级操作**:不应该干扰主任务的动作 + +如果三层没有拉开,页面就会“都重要”,等于“都不重要”。 + +### 3.2 页面布局,是不是服务任务而不是堆模块 + +参考规范时,可以特别注意: + +- 标题区有没有明确页面目标 +- 主内容区是不是围绕任务组织 +- 操作按钮是不是贴近相关内容 +- 次要信息有没有被弱化 + +### 3.3 页面里的操作,是不是有优先级 + +很多页面一眼看过去有 6 个按钮,结果每个按钮都像 CTA,这是典型的层级失控。 + +更合理的方式是: + +- 一个区域通常只有一个主动作 +- 次级动作可以用描边、文字按钮或更弱的样式 +- 风险动作不要和主动作长得一样 + +## 4. 设计按钮时,应该参考规范里的哪些点 + +按钮是最容易被“随手设计”的部分,但也是最能暴露系统是否成熟的部分。 + +### 4.1 按钮先分“语义”,再分“样式” + +不要先想“蓝色按钮还是黑色按钮”,先想这个按钮是什么角色。 + +常见按钮角色可以这样分: + +| 按钮类型 | 作用 | 常见样式策略 | +| :--- | :--- | :--- | +| **Primary** | 当前区域最关键动作 | 实心、高对比、最显眼 | +| **Secondary** | 支持性动作 | 描边或低一级强调 | +| **Tertiary / Text** | 弱操作 | 文字或低视觉占比 | +| **Destructive** | 删除、停用、清空等风险操作 | 警示色或明确风险样式 | +| **Icon button** | 局部工具操作 | 简洁、靠近上下文 | + +### 4.2 一个页面不要有太多 Primary Button + +这是很多新手最容易踩的坑。 + +如果页面上有 4 个主按钮,那么等于没有主按钮。主按钮的意义本来就是“告诉用户现在最应该做什么”。 + +你可以借鉴很多设计系统的共同做法: + +- 一个主要区域通常只保留一个主按钮 +- 取消、返回、关闭一般不和确认按钮抢同级 +- 更多操作放到次级按钮或菜单中 + +### 4.3 按钮要能表达状态变化 + +设计规范通常会对按钮状态写得很清楚: + +- 默认态 +- 悬停态 +- 聚焦态 +- 禁用态 +- 加载态 +- 危险态 + +这很重要,因为按钮不是一张静态图,而是用户操作过程中最常被触发的控件之一。 + +### 4.4 按钮文案,也属于设计的一部分 + +按钮文案不只是“文案问题”,它直接影响用户理解。 + +例如: + +- `保存` +- `保存更改` +- `立即发布` +- `删除项目` +- `移到回收站` + +这些文案传达的心理预期完全不同。成熟规范通常会要求按钮标签清楚表达动作,而不是使用含糊词。 + +## 5. 一个很实用的页面与按钮设计清单 + +你自己设计页面时,可以先快速过一遍这张清单: + +### 页面清单 + +- 页面标题是否清楚说明当前任务 +- 首屏最重要的信息是否一眼可见 +- 页面是不是按任务流程组织,而不是按想到什么放什么 +- 同一个区域里是否只有一个主要动作 +- 次要内容是否被适当弱化 + +### 按钮清单 + +- 这个按钮是主动作还是次动作 +- 它为什么值得比别的按钮更显眼 +- 页面里是不是有太多主按钮 +- 危险操作是否被明确标识 +- 按钮文案是否足够具体 + +## 6. 怎样用 AI 参考别人的规范来设计页面 + +这一节最实用。 + +很多人让 AI 设计页面时,只会说: + +```md +帮我做一个设置页面,要高级一点,参考苹果风格 +``` + +这类提示词太模糊了,AI 最后通常只能模仿“白底、圆角、阴影”。 + +对新手来说,更实用的方式不是自己总结一大段,而是直接把**规范原文里的关键句**贴给 AI。 + +这样做有两个好处: + +- 你不用自己先“翻译”一遍设计思想 +- AI 更容易按官方定义去理解页面和按钮 + +### 6.1 例子一:让 AI 参考 Apple 设计一个设置页面 + +先找一句 Apple 原文: + +> ["Establish a clear visual hierarchy..."](https://developer.apple.com/design/human-interface-guidelines/) + +你可以直接这样贴给 AI: + +```md +参考 Apple Human Interface Guidelines 里的这句话: +"Establish a clear visual hierarchy..." + +帮我设计一个账号安全设置页面。 +要求页面层级清楚,重要信息放前面,分组整齐一点。 +``` + +这样写的重点是:不用你自己解释太多,直接把 Apple 的原话贴进去。 + +### 6.2 例子二:让 AI 参考 Fluent 设计后台页面按钮 + +先找一句 Fluent 原文: + +> ["Only use one primary button in a layout..."](https://fluent2.microsoft.design/components/web/react/core/button/usage) + +你可以直接这样贴给 AI: + +```md +参考 Fluent 2 里的这句话: +"Only use one primary button in a layout..." + +帮我设计一个团队管理后台的按钮。 +添加成员按钮最明显,导出、筛选、更多操作弱一点,删除按钮单独突出。 +``` + +这一句非常适合新手,因为它直接告诉 AI:一个区域不要放太多主按钮。 + +### 6.3 例子三:让 AI 同时参考页面规范和按钮规范 + +你也可以一次贴两句原文,让 AI 同时参考页面和按钮: + +> Apple: ["Establish a clear visual hierarchy..."](https://developer.apple.com/design/human-interface-guidelines/) +> +> Fluent: ["Only use one primary button in a layout..."](https://fluent2.microsoft.design/components/web/react/core/button/usage) + +然后直接这样写: + +```md +参考下面两句设计规范原文: +Apple: "Establish a clear visual hierarchy..." +Fluent: "Only use one primary button in a layout..." + +帮我设计一个项目详情页。 +页面包含项目介绍、成员、最近活动和设置入口。 +页面层级清楚一点,主按钮只保留一个,其他按钮弱一点。 +``` + +这种方式特别适合新手,因为你只要会复制原文,再加两句自己的需求就够了。 + +## 7. 怎样用 AI 参考按钮规范来直接生成按钮设计 + +如果你只想先做按钮,也可以直接贴按钮规范原文。 + +例如 Atlassian 对按钮的定义很短: + +> ["A button triggers an event or action."](https://atlassian.design/components/button/) + +你可以这样问 AI: + +```md +参考 Atlassian 的这句话: +"A button triggers an event or action." + +帮我设计一套后台页面按钮样式。 +我要有主按钮、次按钮、删除按钮,顺便告诉我分别用在什么地方。 +``` + +这类提示词尤其适合新手,基本就是“贴原文 + 说需求”。 + +## 8. 小结 + +参考 UI 设计规范设计页面和按钮,最重要的不是“做得像谁”,而是学会下面这几件事: + +1. 用层级组织页面,而不是把内容堆上去 +2. 用按钮分级表达操作优先级,而不是让所有按钮都一样抢眼 +3. 用设计规范里的定义、边界和判断标准指导设计 +4. 让 AI 参考别人规范时,参考的是“原则和结构”,而不是只参考皮肤 + +当你这样使用规范时,你参考到的就不只是一个风格,而是一套成熟的设计思考方式。 + +--- + +## 参考资料 + +以下链接都来自官方设计系统或官方文档: + +- Apple Human Interface Guidelines: [Overview](https://developer.apple.com/design/human-interface-guidelines/) +- Apple Human Interface Guidelines: [Menus](https://developer.apple.com/design/human-interface-guidelines/menus) +- Apple Human Interface Guidelines: [Alerts](https://developer.apple.com/design/human-interface-guidelines/alerts) +- Apple Human Interface Guidelines: [Buttons](https://developer.apple.com/design/human-interface-guidelines/buttons) +- Apple Archive: [How Menus Work](https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/MenuList/Articles/HowMenusWork.html) +- Apple Archive: [Managing Pop-Up Buttons and Pull-Down Lists](https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/MenuList/Articles/ManagingPopUpItems.html) +- Material Design: [Buttons overview](https://m3.material.io/components/buttons/overview) +- Material Design: [Menus](https://m1.material.io/components/menus.html) +- Microsoft Fluent 2: [Start designing](https://fluent2.microsoft.design/get-started/design) +- Microsoft Fluent 2: [Menu usage](https://fluent2.microsoft.design/components/web/react/core/menu/usage) +- Microsoft Fluent 2: [Button usage](https://fluent2.microsoft.design/components/web/react/core/button/usage) +- Atlassian Design System: [Foundations](https://atlassian.design/foundations/) +- Atlassian Design System: [Button](https://atlassian.design/components/button/) diff --git a/docs/zh-cn/stage-2/index.md b/docs/zh-cn/stage-2/index.md index 17c40d6..2b7f398 100644 --- a/docs/zh-cn/stage-2/index.md +++ b/docs/zh-cn/stage-2/index.md @@ -26,8 +26,8 @@ /> 本章节正在编写中,敬请期待... +在上一节里,我们做的是一个“前端就能跑起来”的小程序。但只要你的产品开始接近真实业务,很快就会遇到这样几类需求: + +- 用户登录后,需要识别“这是谁” +- 数据不能只存在本地,而要能跨设备同步 +- 图片、音频、文档需要上传到云端 +- 订单、支付、会员、积分这些逻辑不能放在前端裸奔 +- 你希望接入 AI、数据库、管理后台、定时任务、消息通知 + +这时候,你做的就不再只是一个“小程序页面”,而是一个完整的小程序产品。它需要前端,也需要后端。 + +截至 **2026 年 3 月 25 日**,如果你的目标是“尽快做出真实可上线的小程序,并且尽量少踩基础设施的坑”,最推荐的路线不是一上来就自己买服务器、配 Nginx、写一堆鉴权中间件,而是: + +::: tip 推荐路线 +**优先选择:微信小程序 + 微信云开发 / CloudBase** + +也就是用: + +- 小程序前端负责界面和交互 +- 云函数或云托管负责后端逻辑 +- 云数据库负责数据存储 +- 云存储负责文件 +- 安全规则、内容审核、日志等能力作为上线标配 +::: + +原因很简单:这条路线和微信生态贴得最近,登录态传递、用户身份识别、文件上传、数据库访问、服务端函数调用都更顺手,特别适合新手、独立开发者、MVP、内容产品、工具类产品,以及你现在这种 **“用 AI 快速把产品做出来”** 的场景。 + +当然,这并不意味着“自建后端”没价值。真正的最佳实践,不是所有项目都用同一种方案,而是 **先选最省心的默认方案,再在必要时升级到更重的架构** 。 + +# 1. 什么叫“小程序带后端” + +最简单的理解是: + +- **前端**:跑在微信里的界面,负责展示页面、接收点击、发起请求 +- **后端**:跑在服务器或云端,负责身份校验、数据库读写、支付签名、业务规则、第三方 API 调用 + +一个成熟的小程序,通常会把职责分成三层: + +1. **小程序前端层** + +负责页面 UI、表单输入、列表展示、加载状态、错误提示、用户操作。 + +2. **业务逻辑层** + +负责“真正重要”的事情,例如:创建订单、检查权限、扣减库存、生成支付参数、调用大模型、审核内容、写操作日志。 + +3. **数据与资源层** + +负责存储用户数据、文章内容、订单信息、上传文件、图片资源、审核结果等。 + +这三层不要混在一起。尤其是第二层,绝对不能为了省事直接塞到前端里。 + +## 1.1 哪些事情必须放到后端 + +下面这些能力,原则上都应该放在服务端,而不是直接写在小程序前端: + +- `AppSecret`、支付密钥、商户私钥、第三方平台密钥 +- 登录态换取、用户身份绑定、管理员权限判断 +- 支付下单、签名生成、支付回调验签 +- 数据库的写权限控制 +- 价格、库存、积分、优惠券等业务规则 +- 内容审核、风控、限流、反刷 +- 定时任务、批处理、异步任务 + +只要一条逻辑涉及“密钥、权限、金额、不可篡改的业务规则”,就不要把它放在前端。 + +# 2. 最推荐的架构是什么 + +对大部分第一次做“小程序 + 后端”的项目,我推荐你用下面这个架构: + +```mermaid +flowchart LR + miniapp["微信小程序页面"] + invoke["wx.cloud.callFunction
/ wx.cloud.callContainer"] + backend["云函数 / 云托管服务"] + infra["云数据库 / 云存储 / 第三方 API / 微信支付"] + + miniapp --> invoke + invoke --> backend + backend --> infra +``` + +这条路线背后的核心思路是: + +- **前端尽量薄** + 只做 UI、参数收集、结果展示,不直接碰密钥和关键业务规则。 +- **后端尽量集中** + 让鉴权、支付、数据写入、权限控制都从服务端入口走。 +- **数据权限默认收紧** + 先默认拒绝,再按角色和场景放开。 +- **先用托管能力跑通闭环** + 先把产品做出来,再考虑是否拆分成更复杂的微服务。 + +## 2.1 三种可选路线 + +### 路线 A:云开发(默认推荐) + +最适合: + +- 新手第一次做带后端的小程序 +- 工具类、内容类、社区类、表单类、轻电商类产品 +- 想快速做 MVP、验证需求、快速上线 +- 希望和微信登录态、云函数、云数据库配合得更顺 + +典型能力组合: + +- `wx.cloud.callFunction` +- 云函数 +- 云数据库 +- 云存储 +- 内容安全审核 +- 定时任务 + +这是我最推荐你先学、先用、先跑通的一条线。 + +### 路线 B:云托管 / HTTP 服务(推荐给中等复杂度项目) + +最适合: + +- 你已经有现成的 Node.js / NestJS / Express / Python / Go 服务 +- 需要标准 HTTP API、复杂路由、更多中间件 +- 需要更灵活的容器化部署 +- 需要对接更多第三方服务,或者你未来还想服务 H5、管理后台、App + +这条路线依然可以放在微信云开发体系里,但服务形态更像“真正的后端服务”,而不只是函数。 + +### 路线 C:完全自建后端 + +最适合: + +- 你有成熟的后端团队 +- 需要更强的私有化、专有网络、合规隔离 +- 已经有统一网关、统一鉴权、统一运维平台 + +对于本教程面向的大多数读者来说,这通常不是第一步,而是第二阶段甚至第三阶段的事情。 + +## 2.2 最佳实践结论 + +如果你现在问我一句最短答案: + +::: tip 最短答案 +**先用云开发把登录、数据、上传、审核、基础业务跑通;** +**当你需要标准 HTTP 服务、复杂中间件或更强扩展性时,再升级到云托管;** +**只有在明确有组织级后端要求时,才考虑完全自建。** +::: + +# 3. 为什么云开发是最好的起步方案 + +这不是因为“它最炫”,而是因为它在微信生态下的工程摩擦最低。 + +## 3.1 身份传递更自然 + +CloudBase 官方文档明确提到,小程序端调用云函数时,SDK 会自动携带当前用户身份,服务端可以结合上下文识别调用者;而在云函数里也可以通过 `getWXContext()` 获取当前调用的小程序用户信息。 + +这意味着你不用从第一天开始自己折腾一整套 token 分发系统,就能先跑通“谁在调用这个接口”。 + +## 3.2 数据、文件、函数是同一套体系 + +如果你的产品里有这些需求: + +- 用户上传头像 +- 发帖子、评论、收藏 +- 生成 AI 内容 +- 记录订单或表单 +- 后台查日志 + +那么云数据库、云存储、云函数直接配套,开发路径会非常短。 + +## 3.3 更适合 AI 协作开发 + +你用 Trae 或其他 AI 编程工具时,越“标准化”的工程结构,AI 越容易理解和修改。 + +相比“前端请求一个你自己七拼八凑的服务器”,下面这种结构对 AI 更友好: + +```text +miniprogram/ +cloudfunctions/ +database collections/ +cloud storage/ +``` + +因为职责清楚、目录简单、边界明确,AI 更容易帮你一次性生成能运行的版本。 + +# 4. 一个真正可上线的最小架构 + +如果你要做一个真实的小程序,我建议最少包含下面这些模块: + +```text +小程序前端 +├── pages/ 页面 +├── components/ 组件 +├── services/ 前端调用封装 +└── app.js 云环境初始化 + +云函数 / 云托管 +├── auth 登录态和身份补充信息 +├── user 用户资料读写 +├── content 内容 CRUD +├── order 订单创建与状态流转 +├── payment 支付下单与回调处理 +└── audit 内容审核、风控、限流 + +数据层 +├── users 用户表 +├── posts 内容表 +├── orders 订单表 +├── files 文件记录 +└── audit_logs 审计日志 +``` + +## 4.1 小程序前端只做三件事 + +前端最好只负责: + +1. 收集参数 +2. 调服务端接口 +3. 展示结果 + +例如: + +- 点击“发布”时,把标题、正文、图片 ID 发给后端 +- 点击“支付”时,请后端返回支付参数 +- 点击“生成文案”时,请后端去调用大模型 + +不要让前端直接决定价格、库存、积分、管理员身份。 + +## 4.2 后端负责“真实业务” + +后端应该统一处理: + +- 当前用户是谁 +- 有没有权限 +- 数据是否合法 +- 这次写入是否需要事务或幂等 +- 是否要记录操作日志 +- 是否要调用审核、支付、消息通知 + +一句话概括:**前端是入口,后端是裁判。** + +# 5. 用云开发快速落地的标准步骤 + +下面给你一条最务实的 SOP。你完全可以照着这条路,用 AI 在几个小时内搭出第一版。 + +## 5.1 第一步:初始化云环境 + +如果你是零基础,不要想着“我要怎么写初始化代码”。你现在只需要会对 AI 说一句人话。 + +```text +请帮我把这个微信小程序项目接上云开发,并直接改好项目文件。改完以后,请用最简单的话告诉我:我下一步去哪里填云环境 ID,以及我怎么判断这一步已经成功。 +``` + +如果 AI 第一次没理解,你就补一句: + +```text +我是零基础,请不要讲太多代码,直接帮我改好,并告诉我下一步点哪里。 +``` + +这一步你真正要理解的,不是几行代码,而是三件事: + +- 这个项目已经“接上云了” +- 后面的小程序页面可以开始调用云函数 +- 你要尽早把开发环境、测试环境、生产环境分开,不要一套环境用到底 + +你做完这一步后,理想状态应该是: + +- 项目还能正常启动 +- 控制台没有明显的云开发初始化报错 +- 后面可以继续往项目里加云函数和数据库能力 + +## 5.2 第二步:先写一个最简单的云函数 + +第二步也一样。你不需要先理解“云函数文件放在哪、怎么 export、怎么返回结果”,你只需要先把最小闭环跑通。 + +```text +请帮我做一个最简单的“前端调用后端”示例:让小程序页面可以调用一个云函数,并看到返回结果。请你直接修改真实项目文件,改完以后告诉我:我应该点哪里测试,以及成功时会看到什么。 +``` + +如果你想让 AI 更具体一点,可以再补一句: + +```text +这个示例可以用一个叫 `getCurrentUser` 的云函数来做,越简单越好。 +``` + +为什么这一步特别重要? + +因为它不是在教你背云函数语法,而是在帮你拿到第一个真正的“前端 -> 后端 -> 返回结果”的闭环。一旦这个闭环跑通,后面再加数据库、上传文件、内容审核、支付,都会顺很多。 + +如果你是第一次做,建议把成功标准定得非常简单: + +- 云函数已经创建成功 +- 前端能正常发起调用 +- 你能在调试结果里看到一份返回数据 + +做到这三点,这一步就算过关了。 + +## 5.3 第三步:把数据库写操作收回后端 + +很多新手一开始会想:“既然能直接访问数据库,我是不是前端直接写就行?” + +不建议。 + +最佳实践是: + +- 前端读操作可以根据业务适度开放 +- 关键写操作尽量通过云函数或云托管服务统一处理 + +例如: + +- 发布内容 +- 删除内容 +- 修改价格 +- 创建订单 +- 发放权益 + +都应该从服务端入口走。 + +如果你想让 AI 直接帮你往这个方向改,可以说: + +```text +请帮我检查这个小程序项目里哪些数据库写操作不应该放在前端。如果有不合适的地方,请改成通过云函数处理,并用最简单的话告诉我为什么要这样改。 +``` + +## 5.4 第四步:给数据库和函数加安全规则 + +CloudBase 官方提供了数据库安全规则和云函数安全规则。这一步一定不要省。 + +新手最容易犯的错误是:为了图省事,把权限直接开成“所有人可读写”。这样虽然调试很爽,但上线后风险极高。 + +更好的做法是: + +- 默认拒绝 +- 只允许登录用户读自己的数据 +- 管理员写操作单独校验 +- 涉及订单、支付、积分的数据只允许服务端改 + +如果你未来做的是社区、表单、课程、会员、电商,这一步几乎决定了项目能不能安全上线。 + +如果你不确定怎么开权限,就直接对 AI 说: + +```text +请帮我给这个小程序项目补一套最保守的安全规则。默认尽量收紧,只保留最基本的可用权限。改完以后,请告诉我哪些数据只能后端改,哪些数据可以前端读。 +``` + +## 5.5 第五步:文件上传统一走云存储 + +图片、音频、PDF、头像、海报,尽量都不要传到乱七八糟的外部图床。 + +更稳妥的方式是: + +1. 前端上传到云存储 +2. 后端记录文件元数据 +3. 业务表只保存文件 ID 或文件 URL + +这样后面做权限控制、清理垃圾文件、生成缩略图、审核资源时,结构会更清楚。 + +如果你已经走到上传这一步,可以直接对 AI 说: + +```text +请帮我把这个小程序项目的上传功能接到云存储,不要用外部图床。上传成功后,请顺手把文件信息记录下来,并告诉我后面应该把图片地址存在哪里。 +``` + +# 6. 如果你的项目更复杂,就升级到云托管 + +当项目开始出现下面这些信号时,就说明你不应该只靠简单云函数了: + +- API 路由越来越多 +- 需要 Express / NestJS / FastAPI 这类成熟框架 +- 需要复杂鉴权、中间件、统一错误处理 +- 需要连接更多外部系统 +- 需要更稳定的容器级部署 + +这时比较合理的做法不是“完全推倒重来”,而是升级到 **云托管**。 + +你可以理解为: + +- 云函数更像“一个个能力点” +- 云托管更像“一个完整的后端服务” + +## 6.1 云托管适合什么样的工程 + +比如你要做: + +- 带管理后台的内容平台 +- 有商品、订单、支付、售后的一套业务 +- 有 AI 工作流、异步队列、Webhook 回调的系统 +- 同时服务小程序、H5、后台管理端的统一 API + +那么云托管会更舒服。 + +## 6.2 一个更接近传统后端的结构 + +```text +server/ +├── src/ +│ ├── controllers/ +│ ├── services/ +│ ├── repositories/ +│ ├── middleware/ +│ └── app.js +├── Dockerfile +└── package.json +``` + +此时你的小程序前端可以通过: + +- `wx.cloud.callContainer` +- 或者你配置好的 HTTPS API + +去请求这个后端服务。 + +# 7. 支付为什么一定要有后端 + +这是“带后端小程序”最典型、也最不能偷懒的一件事。 + +微信支付的正确姿势是: + +1. 小程序前端点击“支付” +2. 前端请求你的后端 +3. 后端调用微信支付下单接口,拿到预支付信息 +4. 后端把支付参数返回给小程序 +5. 小程序调用 `wx.requestPayment` +6. 支付结果以服务端回调为准,后端更新订单状态 + +这里有三个原则: + +- **下单在服务端** +- **签名在服务端** +- **订单最终状态以服务端通知为准** + +不要用“前端支付成功弹窗出现了”来判断订单成功,那会出大问题。 + +## 7.1 一个正确的支付职责划分 + +**前端:** + +- 展示商品和价格 +- 发起“我要支付” +- 调起 `wx.requestPayment` +- 展示支付中、支付成功、支付失败状态 + +**后端:** + +- 校验商品和价格是否有效 +- 创建本地订单 +- 调微信支付下单 +- 保存交易流水 +- 处理回调通知 +- 更新订单状态 +- 做幂等处理,避免重复发货或重复记账 + +# 8. 传统自建后端的标准做法 + +如果你明确知道自己要走“自建服务”的路线,那么小程序和后端之间最常见的流程是: + +```text +小程序调用 wx.login + -> 把 code 发给你的后端 + -> 后端调用微信官方登录态接口 + -> 后端拿到用户标识并建立自己的用户体系 +``` + +再往后,小程序就只跟你的后端 API 交互。 + +这条路线没有问题,但它比“直接用云开发”多出很多基础设施工作: + +- 合法域名配置 +- HTTPS +- 登录态管理 +- 部署与运维 +- 日志和监控 +- 安全策略 +- 服务器扩缩容 + +所以我的建议一直是:**除非你已经明确需要这些能力,否则不要在第一版就把自己拖进运维泥潭。** + +# 9. 安全是“最佳实现”的一部分 + +很多人一说“最佳实践”,脑子里只想到技术栈。但真正的小程序后端最佳实践,安全一定是标配。 + +## 9.1 你至少要做到这些 + +- 不把任何密钥放进小程序前端 +- 订单、积分、价格、库存都由服务端决定 +- 数据库默认最小权限 +- 所有关键写操作走服务端 +- 上传内容做安全审核 +- 支付回调做验签和幂等 +- 区分开发、测试、生产环境 +- 给关键链路加日志和告警 + +## 9.2 内容型产品一定要加审核 + +如果你的产品允许用户上传: + +- 文本 +- 图片 +- 音频 +- 评论 +- 头像 +- 社区内容 + +那就应该把内容安全审核接进后端流程,而不是靠人工祈祷。 + +一个典型流程是: + +```text +用户提交内容 + -> 后端写入待审核状态 + -> 调用审核能力 + -> 审核通过后再公开展示 +``` + +这会比“前端一提交就直接全量公开”安全得多。 + +# 10. 一个真正适合 0 基础的 Prompt + +如果你完全是第一次做,不要发那种很长的任务清单。你可以先从下面这句开始: + +```text +请帮我做一个最简单的“微信小程序 + 云开发后端”版本。要求是:我几乎不懂代码,所以请你直接修改真实项目文件,少讲术语,每做完一步都告诉我下一步该点哪里、看到什么才算成功。 +``` + +如果 AI 已经开始干活了,你再一小步一小步加需求,例如: + +```text +下一步请帮我加一个最简单的云函数测试页面。 +``` + +```text +下一步请帮我把内容发布改成走云函数,不要前端直接写数据库。 +``` + +```text +下一步请帮我接云存储上传,并告诉我上传成功后我应该在哪个页面看到结果。 +``` + +0 基础最重要的原则不是“一次把 Prompt 写得很完美”,而是: + +- 先让 AI 帮你完成一个很小的动作 +- 确认成功 +- 再继续下一步 + +你不需要一开始就写出一份架构师级别的长 Prompt。 + +# 11. 你应该按什么顺序做 + +如果你是第一次做这一类项目,我建议顺序如下: + +1. 先把小程序前端页面搭出来 +2. 让 AI 帮你初始化云开发环境 +3. 让 AI 帮你生成一个最简单的 `getCurrentUser` 云函数 +4. 跑通第一条“前端调用后端”的闭环 +5. 加数据库集合和最小安全规则 +6. 把关键写操作收回到云函数或云托管 +7. 再接上传、审核、支付、AI 这些增强能力 + +不要一开始就同时搞: + +- 登录体系 +- 支付体系 +- 积分体系 +- 会员体系 +- 分销体系 +- 管理后台 + +那样非常容易把自己做崩。 + +# 12. 本节小结 + +如果把这一节压缩成一句话,那就是: + +::: tip 结论 +**做带后端的微信小程序时,默认最佳实现是“小程序前端 + 云开发后端”;** +**关键业务逻辑统一收口到服务端;** +**支付、密钥、权限、审核、安全规则一定不要放松。** +::: + +你可以先用最小成本做出一版: + +- 前端能展示页面 +- 云函数能处理业务 +- 数据库存数据 +- 云存储放文件 +- 审核保证内容安全 + +等业务变复杂,再逐步升级到云托管或更重的自建后端架构。 + +这才是真正适合独立开发者和 AI 协作开发的“小程序带后端最佳实践”。 + +# 参考资料 + +- 微信云开发小程序快速开始: +- CloudBase 云函数使用指南: +- CloudBase 数据库安全规则: +- CloudBase 云函数安全规则: +- CloudBase 云托管快速开始: +- CloudBase HTTP 访问服务: +- CloudBase 内容安全审核: +- 微信支付小程序调起支付文档: diff --git a/package-lock.json b/package-lock.json index fce99a5..7cd3820 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,6 +12,7 @@ "@element-plus/icons-vue": "^2.3.2", "claude": "^0.1.1", "element-plus": "^2.13.1", + "mermaid": "^11.13.0", "typeit": "^8.8.7", "viewerjs": "^1.11.7", "vitepress": "^2.0.0-alpha.16", @@ -30,6 +31,19 @@ "node": ">=18.0.0" } }, + "node_modules/@antfu/install-pkg": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/@antfu/install-pkg/-/install-pkg-1.1.0.tgz", + "integrity": "sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ==", + "license": "MIT", + "dependencies": { + "package-manager-detector": "^1.3.0", + "tinyexec": "^1.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, "node_modules/@babel/helper-string-parser": { "version": "7.27.1", "resolved": "https://registry.npmmirror.com/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", @@ -76,6 +90,51 @@ "node": ">=6.9.0" } }, + "node_modules/@braintree/sanitize-url": { + "version": "7.1.2", + "resolved": "https://registry.npmmirror.com/@braintree/sanitize-url/-/sanitize-url-7.1.2.tgz", + "integrity": "sha512-jigsZK+sMF/cuiB7sERuo9V7N9jx+dhmHHnQyDSVdpZwVutaBu7WvNYqMDLSgFgfB30n452TP3vjDAvFC973mA==", + "license": "MIT" + }, + "node_modules/@chevrotain/cst-dts-gen": { + "version": "11.1.2", + "resolved": "https://registry.npmmirror.com/@chevrotain/cst-dts-gen/-/cst-dts-gen-11.1.2.tgz", + "integrity": "sha512-XTsjvDVB5nDZBQB8o0o/0ozNelQtn2KrUVteIHSlPd2VAV2utEb6JzyCJaJ8tGxACR4RiBNWy5uYUHX2eji88Q==", + "license": "Apache-2.0", + "dependencies": { + "@chevrotain/gast": "11.1.2", + "@chevrotain/types": "11.1.2", + "lodash-es": "4.17.23" + } + }, + "node_modules/@chevrotain/gast": { + "version": "11.1.2", + "resolved": "https://registry.npmmirror.com/@chevrotain/gast/-/gast-11.1.2.tgz", + "integrity": "sha512-Z9zfXR5jNZb1Hlsd/p+4XWeUFugrHirq36bKzPWDSIacV+GPSVXdk+ahVWZTwjhNwofAWg/sZg58fyucKSQx5g==", + "license": "Apache-2.0", + "dependencies": { + "@chevrotain/types": "11.1.2", + "lodash-es": "4.17.23" + } + }, + "node_modules/@chevrotain/regexp-to-ast": { + "version": "11.1.2", + "resolved": "https://registry.npmmirror.com/@chevrotain/regexp-to-ast/-/regexp-to-ast-11.1.2.tgz", + "integrity": "sha512-nMU3Uj8naWer7xpZTYJdxbAs6RIv/dxYzkYU8GSwgUtcAAlzjcPfX1w+RKRcYG8POlzMeayOQ/znfwxEGo5ulw==", + "license": "Apache-2.0" + }, + "node_modules/@chevrotain/types": { + "version": "11.1.2", + "resolved": "https://registry.npmmirror.com/@chevrotain/types/-/types-11.1.2.tgz", + "integrity": "sha512-U+HFai5+zmJCkK86QsaJtoITlboZHBqrVketcO2ROv865xfCMSFpELQoz1GkX5GzME8pTa+3kbKrZHQtI0gdbw==", + "license": "Apache-2.0" + }, + "node_modules/@chevrotain/utils": { + "version": "11.1.2", + "resolved": "https://registry.npmmirror.com/@chevrotain/utils/-/utils-11.1.2.tgz", + "integrity": "sha512-4mudFAQ6H+MqBTfqLmU7G1ZwRzCLfJEooL/fsF6rCX5eePMbGhoy5n4g+G4vlh2muDcsCTJtL+uKbOzWxs5LHA==", + "license": "Apache-2.0" + }, "node_modules/@ctrl/tinycolor": { "version": "3.6.1", "resolved": "https://registry.npmmirror.com/@ctrl/tinycolor/-/tinycolor-3.6.1.tgz", @@ -764,12 +823,32 @@ "integrity": "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==", "license": "MIT" }, + "node_modules/@iconify/utils": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/@iconify/utils/-/utils-3.1.0.tgz", + "integrity": "sha512-Zlzem1ZXhI1iHeeERabLNzBHdOa4VhQbqAcOQaMKuTuyZCpwKbC2R4Dd0Zo3g9EAc+Y4fiarO8HIHRAth7+skw==", + "license": "MIT", + "dependencies": { + "@antfu/install-pkg": "^1.1.0", + "@iconify/types": "^2.0.0", + "mlly": "^1.8.0" + } + }, "node_modules/@jridgewell/sourcemap-codec": { "version": "1.5.5", "resolved": "https://registry.npmmirror.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", "license": "MIT" }, + "node_modules/@mermaid-js/parser": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/@mermaid-js/parser/-/parser-1.0.1.tgz", + "integrity": "sha512-opmV19kN1JsK0T6HhhokHpcVkqKpF+x2pPDKKM2ThHtZAB5F4PROopk0amuVYK5qMrIA4erzpNm8gmPNJgMDxQ==", + "license": "MIT", + "dependencies": { + "langium": "^4.0.0" + } + }, "node_modules/@popperjs/core": { "name": "@sxzz/popperjs-es", "version": "2.11.7", @@ -1189,12 +1268,271 @@ "integrity": "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==", "license": "MIT" }, + "node_modules/@types/d3": { + "version": "7.4.3", + "resolved": "https://registry.npmmirror.com/@types/d3/-/d3-7.4.3.tgz", + "integrity": "sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww==", + "license": "MIT", + "dependencies": { + "@types/d3-array": "*", + "@types/d3-axis": "*", + "@types/d3-brush": "*", + "@types/d3-chord": "*", + "@types/d3-color": "*", + "@types/d3-contour": "*", + "@types/d3-delaunay": "*", + "@types/d3-dispatch": "*", + "@types/d3-drag": "*", + "@types/d3-dsv": "*", + "@types/d3-ease": "*", + "@types/d3-fetch": "*", + "@types/d3-force": "*", + "@types/d3-format": "*", + "@types/d3-geo": "*", + "@types/d3-hierarchy": "*", + "@types/d3-interpolate": "*", + "@types/d3-path": "*", + "@types/d3-polygon": "*", + "@types/d3-quadtree": "*", + "@types/d3-random": "*", + "@types/d3-scale": "*", + "@types/d3-scale-chromatic": "*", + "@types/d3-selection": "*", + "@types/d3-shape": "*", + "@types/d3-time": "*", + "@types/d3-time-format": "*", + "@types/d3-timer": "*", + "@types/d3-transition": "*", + "@types/d3-zoom": "*" + } + }, + "node_modules/@types/d3-array": { + "version": "3.2.2", + "resolved": "https://registry.npmmirror.com/@types/d3-array/-/d3-array-3.2.2.tgz", + "integrity": "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==", + "license": "MIT" + }, + "node_modules/@types/d3-axis": { + "version": "3.0.6", + "resolved": "https://registry.npmmirror.com/@types/d3-axis/-/d3-axis-3.0.6.tgz", + "integrity": "sha512-pYeijfZuBd87T0hGn0FO1vQ/cgLk6E1ALJjfkC0oJ8cbwkZl3TpgS8bVBLZN+2jjGgg38epgxb2zmoGtSfvgMw==", + "license": "MIT", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-brush": { + "version": "3.0.6", + "resolved": "https://registry.npmmirror.com/@types/d3-brush/-/d3-brush-3.0.6.tgz", + "integrity": "sha512-nH60IZNNxEcrh6L1ZSMNA28rj27ut/2ZmI3r96Zd+1jrZD++zD3LsMIjWlvg4AYrHn/Pqz4CF3veCxGjtbqt7A==", + "license": "MIT", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-chord": { + "version": "3.0.6", + "resolved": "https://registry.npmmirror.com/@types/d3-chord/-/d3-chord-3.0.6.tgz", + "integrity": "sha512-LFYWWd8nwfwEmTZG9PfQxd17HbNPksHBiJHaKuY1XeqscXacsS2tyoo6OdRsjf+NQYeB6XrNL3a25E3gH69lcg==", + "license": "MIT" + }, + "node_modules/@types/d3-color": { + "version": "3.1.3", + "resolved": "https://registry.npmmirror.com/@types/d3-color/-/d3-color-3.1.3.tgz", + "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==", + "license": "MIT" + }, + "node_modules/@types/d3-contour": { + "version": "3.0.6", + "resolved": "https://registry.npmmirror.com/@types/d3-contour/-/d3-contour-3.0.6.tgz", + "integrity": "sha512-BjzLgXGnCWjUSYGfH1cpdo41/hgdWETu4YxpezoztawmqsvCeep+8QGfiY6YbDvfgHz/DkjeIkkZVJavB4a3rg==", + "license": "MIT", + "dependencies": { + "@types/d3-array": "*", + "@types/geojson": "*" + } + }, + "node_modules/@types/d3-delaunay": { + "version": "6.0.4", + "resolved": "https://registry.npmmirror.com/@types/d3-delaunay/-/d3-delaunay-6.0.4.tgz", + "integrity": "sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw==", + "license": "MIT" + }, + "node_modules/@types/d3-dispatch": { + "version": "3.0.7", + "resolved": "https://registry.npmmirror.com/@types/d3-dispatch/-/d3-dispatch-3.0.7.tgz", + "integrity": "sha512-5o9OIAdKkhN1QItV2oqaE5KMIiXAvDWBDPrD85e58Qlz1c1kI/J0NcqbEG88CoTwJrYe7ntUCVfeUl2UJKbWgA==", + "license": "MIT" + }, + "node_modules/@types/d3-drag": { + "version": "3.0.7", + "resolved": "https://registry.npmmirror.com/@types/d3-drag/-/d3-drag-3.0.7.tgz", + "integrity": "sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==", + "license": "MIT", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-dsv": { + "version": "3.0.7", + "resolved": "https://registry.npmmirror.com/@types/d3-dsv/-/d3-dsv-3.0.7.tgz", + "integrity": "sha512-n6QBF9/+XASqcKK6waudgL0pf/S5XHPPI8APyMLLUHd8NqouBGLsU8MgtO7NINGtPBtk9Kko/W4ea0oAspwh9g==", + "license": "MIT" + }, + "node_modules/@types/d3-ease": { + "version": "3.0.2", + "resolved": "https://registry.npmmirror.com/@types/d3-ease/-/d3-ease-3.0.2.tgz", + "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==", + "license": "MIT" + }, + "node_modules/@types/d3-fetch": { + "version": "3.0.7", + "resolved": "https://registry.npmmirror.com/@types/d3-fetch/-/d3-fetch-3.0.7.tgz", + "integrity": "sha512-fTAfNmxSb9SOWNB9IoG5c8Hg6R+AzUHDRlsXsDZsNp6sxAEOP0tkP3gKkNSO/qmHPoBFTxNrjDprVHDQDvo5aA==", + "license": "MIT", + "dependencies": { + "@types/d3-dsv": "*" + } + }, + "node_modules/@types/d3-force": { + "version": "3.0.10", + "resolved": "https://registry.npmmirror.com/@types/d3-force/-/d3-force-3.0.10.tgz", + "integrity": "sha512-ZYeSaCF3p73RdOKcjj+swRlZfnYpK1EbaDiYICEEp5Q6sUiqFaFQ9qgoshp5CzIyyb/yD09kD9o2zEltCexlgw==", + "license": "MIT" + }, + "node_modules/@types/d3-format": { + "version": "3.0.4", + "resolved": "https://registry.npmmirror.com/@types/d3-format/-/d3-format-3.0.4.tgz", + "integrity": "sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g==", + "license": "MIT" + }, + "node_modules/@types/d3-geo": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/@types/d3-geo/-/d3-geo-3.1.0.tgz", + "integrity": "sha512-856sckF0oP/diXtS4jNsiQw/UuK5fQG8l/a9VVLeSouf1/PPbBE1i1W852zVwKwYCBkFJJB7nCFTbk6UMEXBOQ==", + "license": "MIT", + "dependencies": { + "@types/geojson": "*" + } + }, + "node_modules/@types/d3-hierarchy": { + "version": "3.1.7", + "resolved": "https://registry.npmmirror.com/@types/d3-hierarchy/-/d3-hierarchy-3.1.7.tgz", + "integrity": "sha512-tJFtNoYBtRtkNysX1Xq4sxtjK8YgoWUNpIiUee0/jHGRwqvzYxkq0hGVbbOGSz+JgFxxRu4K8nb3YpG3CMARtg==", + "license": "MIT" + }, + "node_modules/@types/d3-interpolate": { + "version": "3.0.4", + "resolved": "https://registry.npmmirror.com/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", + "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==", + "license": "MIT", + "dependencies": { + "@types/d3-color": "*" + } + }, + "node_modules/@types/d3-path": { + "version": "3.1.1", + "resolved": "https://registry.npmmirror.com/@types/d3-path/-/d3-path-3.1.1.tgz", + "integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==", + "license": "MIT" + }, + "node_modules/@types/d3-polygon": { + "version": "3.0.2", + "resolved": "https://registry.npmmirror.com/@types/d3-polygon/-/d3-polygon-3.0.2.tgz", + "integrity": "sha512-ZuWOtMaHCkN9xoeEMr1ubW2nGWsp4nIql+OPQRstu4ypeZ+zk3YKqQT0CXVe/PYqrKpZAi+J9mTs05TKwjXSRA==", + "license": "MIT" + }, + "node_modules/@types/d3-quadtree": { + "version": "3.0.6", + "resolved": "https://registry.npmmirror.com/@types/d3-quadtree/-/d3-quadtree-3.0.6.tgz", + "integrity": "sha512-oUzyO1/Zm6rsxKRHA1vH0NEDG58HrT5icx/azi9MF1TWdtttWl0UIUsjEQBBh+SIkrpd21ZjEv7ptxWys1ncsg==", + "license": "MIT" + }, + "node_modules/@types/d3-random": { + "version": "3.0.3", + "resolved": "https://registry.npmmirror.com/@types/d3-random/-/d3-random-3.0.3.tgz", + "integrity": "sha512-Imagg1vJ3y76Y2ea0871wpabqp613+8/r0mCLEBfdtqC7xMSfj9idOnmBYyMoULfHePJyxMAw3nWhJxzc+LFwQ==", + "license": "MIT" + }, + "node_modules/@types/d3-scale": { + "version": "4.0.9", + "resolved": "https://registry.npmmirror.com/@types/d3-scale/-/d3-scale-4.0.9.tgz", + "integrity": "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==", + "license": "MIT", + "dependencies": { + "@types/d3-time": "*" + } + }, + "node_modules/@types/d3-scale-chromatic": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/@types/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz", + "integrity": "sha512-iWMJgwkK7yTRmWqRB5plb1kadXyQ5Sj8V/zYlFGMUBbIPKQScw+Dku9cAAMgJG+z5GYDoMjWGLVOvjghDEFnKQ==", + "license": "MIT" + }, + "node_modules/@types/d3-selection": { + "version": "3.0.11", + "resolved": "https://registry.npmmirror.com/@types/d3-selection/-/d3-selection-3.0.11.tgz", + "integrity": "sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w==", + "license": "MIT" + }, + "node_modules/@types/d3-shape": { + "version": "3.1.8", + "resolved": "https://registry.npmmirror.com/@types/d3-shape/-/d3-shape-3.1.8.tgz", + "integrity": "sha512-lae0iWfcDeR7qt7rA88BNiqdvPS5pFVPpo5OfjElwNaT2yyekbM0C9vK+yqBqEmHr6lDkRnYNoTBYlAgJa7a4w==", + "license": "MIT", + "dependencies": { + "@types/d3-path": "*" + } + }, + "node_modules/@types/d3-time": { + "version": "3.0.4", + "resolved": "https://registry.npmmirror.com/@types/d3-time/-/d3-time-3.0.4.tgz", + "integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==", + "license": "MIT" + }, + "node_modules/@types/d3-time-format": { + "version": "4.0.3", + "resolved": "https://registry.npmmirror.com/@types/d3-time-format/-/d3-time-format-4.0.3.tgz", + "integrity": "sha512-5xg9rC+wWL8kdDj153qZcsJ0FWiFt0J5RB6LYUNZjwSnesfblqrI/bJ1wBdJ8OQfncgbJG5+2F+qfqnqyzYxyg==", + "license": "MIT" + }, + "node_modules/@types/d3-timer": { + "version": "3.0.2", + "resolved": "https://registry.npmmirror.com/@types/d3-timer/-/d3-timer-3.0.2.tgz", + "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==", + "license": "MIT" + }, + "node_modules/@types/d3-transition": { + "version": "3.0.9", + "resolved": "https://registry.npmmirror.com/@types/d3-transition/-/d3-transition-3.0.9.tgz", + "integrity": "sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg==", + "license": "MIT", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-zoom": { + "version": "3.0.8", + "resolved": "https://registry.npmmirror.com/@types/d3-zoom/-/d3-zoom-3.0.8.tgz", + "integrity": "sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==", + "license": "MIT", + "dependencies": { + "@types/d3-interpolate": "*", + "@types/d3-selection": "*" + } + }, "node_modules/@types/estree": { "version": "1.0.8", "resolved": "https://registry.npmmirror.com/@types/estree/-/estree-1.0.8.tgz", "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", "license": "MIT" }, + "node_modules/@types/geojson": { + "version": "7946.0.16", + "resolved": "https://registry.npmmirror.com/@types/geojson/-/geojson-7946.0.16.tgz", + "integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==", + "license": "MIT" + }, "node_modules/@types/hast": { "version": "3.0.4", "resolved": "https://registry.npmmirror.com/@types/hast/-/hast-3.0.4.tgz", @@ -1257,6 +1595,13 @@ "integrity": "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==", "license": "MIT" }, + "node_modules/@types/trusted-types": { + "version": "2.0.7", + "resolved": "https://registry.npmmirror.com/@types/trusted-types/-/trusted-types-2.0.7.tgz", + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", + "license": "MIT", + "optional": true + }, "node_modules/@types/unist": { "version": "3.0.3", "resolved": "https://registry.npmmirror.com/@types/unist/-/unist-3.0.3.tgz", @@ -1281,6 +1626,16 @@ "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", "license": "ISC" }, + "node_modules/@upsetjs/venn.js": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/@upsetjs/venn.js/-/venn.js-2.0.0.tgz", + "integrity": "sha512-WbBhLrooyePuQ1VZxrJjtLvTc4NVfpOyKx0sKqioq9bX1C1m7Jgykkn8gLrtwumBioXIqam8DLxp88Adbue6Hw==", + "license": "MIT", + "optionalDependencies": { + "d3-selection": "^3.0.0", + "d3-transition": "^3.0.1" + } + }, "node_modules/@vitejs/plugin-vue": { "version": "6.0.4", "resolved": "https://registry.npmmirror.com/@vitejs/plugin-vue/-/plugin-vue-6.0.4.tgz", @@ -1629,10 +1984,9 @@ } }, "node_modules/acorn": { - "version": "8.15.0", - "resolved": "https://registry.npmmirror.com/acorn/-/acorn-8.15.0.tgz", - "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", - "dev": true, + "version": "8.16.0", + "resolved": "https://registry.npmmirror.com/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", "license": "MIT", "bin": { "acorn": "bin/acorn" @@ -1788,6 +2142,32 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/chevrotain": { + "version": "11.1.2", + "resolved": "https://registry.npmmirror.com/chevrotain/-/chevrotain-11.1.2.tgz", + "integrity": "sha512-opLQzEVriiH1uUQ4Kctsd49bRoFDXGGSC4GUqj7pGyxM3RehRhvTlZJc1FL/Flew2p5uwxa1tUDWKzI4wNM8pg==", + "license": "Apache-2.0", + "dependencies": { + "@chevrotain/cst-dts-gen": "11.1.2", + "@chevrotain/gast": "11.1.2", + "@chevrotain/regexp-to-ast": "11.1.2", + "@chevrotain/types": "11.1.2", + "@chevrotain/utils": "11.1.2", + "lodash-es": "4.17.23" + } + }, + "node_modules/chevrotain-allstar": { + "version": "0.3.1", + "resolved": "https://registry.npmmirror.com/chevrotain-allstar/-/chevrotain-allstar-0.3.1.tgz", + "integrity": "sha512-b7g+y9A0v4mxCW1qUhf3BSVPg+/NvGErk/dOkrDaHA0nQIQGAtrOjlX//9OQtRlSCy+x9rfB5N8yC71lH1nvMw==", + "license": "MIT", + "dependencies": { + "lodash-es": "^4.17.21" + }, + "peerDependencies": { + "chevrotain": "^11.0.0" + } + }, "node_modules/claude": { "version": "0.1.1", "resolved": "https://registry.npmmirror.com/claude/-/claude-0.1.1.tgz", @@ -1824,6 +2204,15 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmmirror.com/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmmirror.com/concat-map/-/concat-map-0.0.1.tgz", @@ -1831,6 +2220,12 @@ "dev": true, "license": "MIT" }, + "node_modules/confbox": { + "version": "0.1.8", + "resolved": "https://registry.npmmirror.com/confbox/-/confbox-0.1.8.tgz", + "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==", + "license": "MIT" + }, "node_modules/copy-anything": { "version": "4.0.5", "resolved": "https://registry.npmmirror.com/copy-anything/-/copy-anything-4.0.5.tgz", @@ -1846,6 +2241,15 @@ "url": "https://github.com/sponsors/mesqueeb" } }, + "node_modules/cose-base": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/cose-base/-/cose-base-1.0.3.tgz", + "integrity": "sha512-s9whTXInMSgAp/NVXVNuVxVKzGH2qck3aQlVHxDCdAEPgtMKwc4Wq6/QKhgdEdgbLSi9rBTAcPoRa6JpiG4ksg==", + "license": "MIT", + "dependencies": { + "layout-base": "^1.0.0" + } + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmmirror.com/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -1880,6 +2284,505 @@ "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", "license": "MIT" }, + "node_modules/cytoscape": { + "version": "3.33.1", + "resolved": "https://registry.npmmirror.com/cytoscape/-/cytoscape-3.33.1.tgz", + "integrity": "sha512-iJc4TwyANnOGR1OmWhsS9ayRS3s+XQ185FmuHObThD+5AeJCakAAbWv8KimMTt08xCCLNgneQwFp+JRJOr9qGQ==", + "license": "MIT", + "engines": { + "node": ">=0.10" + } + }, + "node_modules/cytoscape-cose-bilkent": { + "version": "4.1.0", + "resolved": "https://registry.npmmirror.com/cytoscape-cose-bilkent/-/cytoscape-cose-bilkent-4.1.0.tgz", + "integrity": "sha512-wgQlVIUJF13Quxiv5e1gstZ08rnZj2XaLHGoFMYXz7SkNfCDOOteKBE6SYRfA9WxxI/iBc3ajfDoc6hb/MRAHQ==", + "license": "MIT", + "dependencies": { + "cose-base": "^1.0.0" + }, + "peerDependencies": { + "cytoscape": "^3.2.0" + } + }, + "node_modules/cytoscape-fcose": { + "version": "2.2.0", + "resolved": "https://registry.npmmirror.com/cytoscape-fcose/-/cytoscape-fcose-2.2.0.tgz", + "integrity": "sha512-ki1/VuRIHFCzxWNrsshHYPs6L7TvLu3DL+TyIGEsRcvVERmxokbf5Gdk7mFxZnTdiGtnA4cfSmjZJMviqSuZrQ==", + "license": "MIT", + "dependencies": { + "cose-base": "^2.2.0" + }, + "peerDependencies": { + "cytoscape": "^3.2.0" + } + }, + "node_modules/cytoscape-fcose/node_modules/cose-base": { + "version": "2.2.0", + "resolved": "https://registry.npmmirror.com/cose-base/-/cose-base-2.2.0.tgz", + "integrity": "sha512-AzlgcsCbUMymkADOJtQm3wO9S3ltPfYOFD5033keQn9NJzIbtnZj+UdBJe7DYml/8TdbtHJW3j58SOnKhWY/5g==", + "license": "MIT", + "dependencies": { + "layout-base": "^2.0.0" + } + }, + "node_modules/cytoscape-fcose/node_modules/layout-base": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/layout-base/-/layout-base-2.0.1.tgz", + "integrity": "sha512-dp3s92+uNI1hWIpPGH3jK2kxE2lMjdXdr+DH8ynZHpd6PUlH6x6cbuXnoMmiNumznqaNO31xu9e79F0uuZ0JFg==", + "license": "MIT" + }, + "node_modules/d3": { + "version": "7.9.0", + "resolved": "https://registry.npmmirror.com/d3/-/d3-7.9.0.tgz", + "integrity": "sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA==", + "license": "ISC", + "dependencies": { + "d3-array": "3", + "d3-axis": "3", + "d3-brush": "3", + "d3-chord": "3", + "d3-color": "3", + "d3-contour": "4", + "d3-delaunay": "6", + "d3-dispatch": "3", + "d3-drag": "3", + "d3-dsv": "3", + "d3-ease": "3", + "d3-fetch": "3", + "d3-force": "3", + "d3-format": "3", + "d3-geo": "3", + "d3-hierarchy": "3", + "d3-interpolate": "3", + "d3-path": "3", + "d3-polygon": "3", + "d3-quadtree": "3", + "d3-random": "3", + "d3-scale": "4", + "d3-scale-chromatic": "3", + "d3-selection": "3", + "d3-shape": "3", + "d3-time": "3", + "d3-time-format": "4", + "d3-timer": "3", + "d3-transition": "3", + "d3-zoom": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-array": { + "version": "3.2.4", + "resolved": "https://registry.npmmirror.com/d3-array/-/d3-array-3.2.4.tgz", + "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", + "license": "ISC", + "dependencies": { + "internmap": "1 - 2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-axis": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/d3-axis/-/d3-axis-3.0.0.tgz", + "integrity": "sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-brush": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/d3-brush/-/d3-brush-3.0.0.tgz", + "integrity": "sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "3", + "d3-transition": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-chord": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/d3-chord/-/d3-chord-3.0.1.tgz", + "integrity": "sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==", + "license": "ISC", + "dependencies": { + "d3-path": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-contour": { + "version": "4.0.2", + "resolved": "https://registry.npmmirror.com/d3-contour/-/d3-contour-4.0.2.tgz", + "integrity": "sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==", + "license": "ISC", + "dependencies": { + "d3-array": "^3.2.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-delaunay": { + "version": "6.0.4", + "resolved": "https://registry.npmmirror.com/d3-delaunay/-/d3-delaunay-6.0.4.tgz", + "integrity": "sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==", + "license": "ISC", + "dependencies": { + "delaunator": "5" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dispatch": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/d3-dispatch/-/d3-dispatch-3.0.1.tgz", + "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-drag": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/d3-drag/-/d3-drag-3.0.0.tgz", + "integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-selection": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dsv": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/d3-dsv/-/d3-dsv-3.0.1.tgz", + "integrity": "sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==", + "license": "ISC", + "dependencies": { + "commander": "7", + "iconv-lite": "0.6", + "rw": "1" + }, + "bin": { + "csv2json": "bin/dsv2json.js", + "csv2tsv": "bin/dsv2dsv.js", + "dsv2dsv": "bin/dsv2dsv.js", + "dsv2json": "bin/dsv2json.js", + "json2csv": "bin/json2dsv.js", + "json2dsv": "bin/json2dsv.js", + "json2tsv": "bin/json2dsv.js", + "tsv2csv": "bin/dsv2dsv.js", + "tsv2json": "bin/dsv2json.js" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-ease": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/d3-ease/-/d3-ease-3.0.1.tgz", + "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-fetch": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/d3-fetch/-/d3-fetch-3.0.1.tgz", + "integrity": "sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==", + "license": "ISC", + "dependencies": { + "d3-dsv": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-force": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/d3-force/-/d3-force-3.0.0.tgz", + "integrity": "sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-quadtree": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-format": { + "version": "3.1.2", + "resolved": "https://registry.npmmirror.com/d3-format/-/d3-format-3.1.2.tgz", + "integrity": "sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-geo": { + "version": "3.1.1", + "resolved": "https://registry.npmmirror.com/d3-geo/-/d3-geo-3.1.1.tgz", + "integrity": "sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==", + "license": "ISC", + "dependencies": { + "d3-array": "2.5.0 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-hierarchy": { + "version": "3.1.2", + "resolved": "https://registry.npmmirror.com/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz", + "integrity": "sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-path": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/d3-path/-/d3-path-3.1.0.tgz", + "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-polygon": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/d3-polygon/-/d3-polygon-3.0.1.tgz", + "integrity": "sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-quadtree": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/d3-quadtree/-/d3-quadtree-3.0.1.tgz", + "integrity": "sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-random": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/d3-random/-/d3-random-3.0.1.tgz", + "integrity": "sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-sankey": { + "version": "0.12.3", + "resolved": "https://registry.npmmirror.com/d3-sankey/-/d3-sankey-0.12.3.tgz", + "integrity": "sha512-nQhsBRmM19Ax5xEIPLMY9ZmJ/cDvd1BG3UVvt5h3WRxKg5zGRbvnteTyWAbzeSvlh3tW7ZEmq4VwR5mB3tutmQ==", + "license": "BSD-3-Clause", + "dependencies": { + "d3-array": "1 - 2", + "d3-shape": "^1.2.0" + } + }, + "node_modules/d3-sankey/node_modules/d3-array": { + "version": "2.12.1", + "resolved": "https://registry.npmmirror.com/d3-array/-/d3-array-2.12.1.tgz", + "integrity": "sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==", + "license": "BSD-3-Clause", + "dependencies": { + "internmap": "^1.0.0" + } + }, + "node_modules/d3-sankey/node_modules/d3-path": { + "version": "1.0.9", + "resolved": "https://registry.npmmirror.com/d3-path/-/d3-path-1.0.9.tgz", + "integrity": "sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg==", + "license": "BSD-3-Clause" + }, + "node_modules/d3-sankey/node_modules/d3-shape": { + "version": "1.3.7", + "resolved": "https://registry.npmmirror.com/d3-shape/-/d3-shape-1.3.7.tgz", + "integrity": "sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==", + "license": "BSD-3-Clause", + "dependencies": { + "d3-path": "1" + } + }, + "node_modules/d3-sankey/node_modules/internmap": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/internmap/-/internmap-1.0.1.tgz", + "integrity": "sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw==", + "license": "ISC" + }, + "node_modules/d3-scale": { + "version": "4.0.2", + "resolved": "https://registry.npmmirror.com/d3-scale/-/d3-scale-4.0.2.tgz", + "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", + "license": "ISC", + "dependencies": { + "d3-array": "2.10.0 - 3", + "d3-format": "1 - 3", + "d3-interpolate": "1.2.0 - 3", + "d3-time": "2.1.1 - 3", + "d3-time-format": "2 - 4" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale-chromatic": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz", + "integrity": "sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3", + "d3-interpolate": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-selection": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/d3-selection/-/d3-selection-3.0.0.tgz", + "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-shape": { + "version": "3.2.0", + "resolved": "https://registry.npmmirror.com/d3-shape/-/d3-shape-3.2.0.tgz", + "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", + "license": "ISC", + "dependencies": { + "d3-path": "^3.1.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/d3-time/-/d3-time-3.1.0.tgz", + "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", + "license": "ISC", + "dependencies": { + "d3-array": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time-format": { + "version": "4.1.0", + "resolved": "https://registry.npmmirror.com/d3-time-format/-/d3-time-format-4.1.0.tgz", + "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", + "license": "ISC", + "dependencies": { + "d3-time": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-timer": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/d3-timer/-/d3-timer-3.0.1.tgz", + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-transition": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/d3-transition/-/d3-transition-3.0.1.tgz", + "integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3", + "d3-dispatch": "1 - 3", + "d3-ease": "1 - 3", + "d3-interpolate": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "d3-selection": "2 - 3" + } + }, + "node_modules/d3-zoom": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/d3-zoom/-/d3-zoom-3.0.0.tgz", + "integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "2 - 3", + "d3-transition": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/dagre-d3-es": { + "version": "7.0.14", + "resolved": "https://registry.npmmirror.com/dagre-d3-es/-/dagre-d3-es-7.0.14.tgz", + "integrity": "sha512-P4rFMVq9ESWqmOgK+dlXvOtLwYg0i7u0HBGJER0LZDJT2VHIPAMZ/riPxqJceWMStH5+E61QxFra9kIS3AqdMg==", + "license": "MIT", + "dependencies": { + "d3": "^7.9.0", + "lodash-es": "^4.17.21" + } + }, "node_modules/dayjs": { "version": "1.11.19", "resolved": "https://registry.npmmirror.com/dayjs/-/dayjs-1.11.19.tgz", @@ -1911,6 +2814,15 @@ "dev": true, "license": "MIT" }, + "node_modules/delaunator": { + "version": "5.1.0", + "resolved": "https://registry.npmmirror.com/delaunator/-/delaunator-5.1.0.tgz", + "integrity": "sha512-AGrQ4QSgssa1NGmWmLPqN5NY2KajF5MqxetNEO+o0n3ZwZZeTmt7bBnvzHWrmkZFxGgr4HdyFgelzgi06otLuQ==", + "license": "ISC", + "dependencies": { + "robust-predicates": "^3.0.2" + } + }, "node_modules/dequal": { "version": "2.0.3", "resolved": "https://registry.npmmirror.com/dequal/-/dequal-2.0.3.tgz", @@ -1933,6 +2845,15 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/dompurify": { + "version": "3.3.3", + "resolved": "https://registry.npmmirror.com/dompurify/-/dompurify-3.3.3.tgz", + "integrity": "sha512-Oj6pzI2+RqBfFG+qOaOLbFXLQ90ARpcGG6UePL82bJLtdsa6CYJD7nmiU8MW9nQNOtCHV3lZ/Bzq1X0QYbBZCA==", + "license": "(MPL-2.0 OR Apache-2.0)", + "optionalDependencies": { + "@types/trusted-types": "^2.0.7" + } + }, "node_modules/element-plus": { "version": "2.13.1", "resolved": "https://registry.npmmirror.com/element-plus/-/element-plus-2.13.1.tgz", @@ -2361,6 +3282,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/hachure-fill": { + "version": "0.5.2", + "resolved": "https://registry.npmmirror.com/hachure-fill/-/hachure-fill-0.5.2.tgz", + "integrity": "sha512-3GKBOn+m2LX9iq+JC1064cSFprJY4jL1jCXTcpnfER5HYE2l/4EfWSGzkPa/ZDBmYI0ZOEj5VHV/eKnPGkHuOg==", + "license": "MIT" + }, "node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmmirror.com/has-flag/-/has-flag-4.0.0.tgz", @@ -2439,6 +3366,18 @@ "url": "https://github.com/sponsors/typicode" } }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmmirror.com/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmmirror.com/ignore/-/ignore-5.3.2.tgz", @@ -2476,6 +3415,15 @@ "node": ">=0.8.19" } }, + "node_modules/internmap": { + "version": "2.0.3", + "resolved": "https://registry.npmmirror.com/internmap/-/internmap-2.0.3.tgz", + "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmmirror.com/is-extglob/-/is-extglob-2.1.1.tgz", @@ -2575,6 +3523,34 @@ "json-buffer": "3.0.1" } }, + "node_modules/khroma": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/khroma/-/khroma-2.1.0.tgz", + "integrity": "sha512-Ls993zuzfayK269Svk9hzpeGUKob/sIgZzyHYdjQoAdQetRKpOLj+k/QQQ/6Qi0Yz65mlROrfd+Ev+1+7dz9Kw==" + }, + "node_modules/langium": { + "version": "4.2.1", + "resolved": "https://registry.npmmirror.com/langium/-/langium-4.2.1.tgz", + "integrity": "sha512-zu9QWmjpzJcomzdJQAHgDVhLGq5bLosVak1KVa40NzQHXfqr4eAHupvnPOVXEoLkg6Ocefvf/93d//SB7du4YQ==", + "license": "MIT", + "dependencies": { + "chevrotain": "~11.1.1", + "chevrotain-allstar": "~0.3.1", + "vscode-languageserver": "~9.0.1", + "vscode-languageserver-textdocument": "~1.0.11", + "vscode-uri": "~3.1.0" + }, + "engines": { + "node": ">=20.10.0", + "npm": ">=10.2.3" + } + }, + "node_modules/layout-base": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/layout-base/-/layout-base-1.0.2.tgz", + "integrity": "sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg==", + "license": "MIT" + }, "node_modules/levn": { "version": "0.4.1", "resolved": "https://registry.npmmirror.com/levn/-/levn-0.4.1.tgz", @@ -2612,9 +3588,9 @@ "license": "MIT" }, "node_modules/lodash-es": { - "version": "4.17.22", - "resolved": "https://registry.npmmirror.com/lodash-es/-/lodash-es-4.17.22.tgz", - "integrity": "sha512-XEawp1t0gxSi9x01glktRZ5HDy0HXqrM0x5pXQM98EaI0NxO6jVM7omDOxsuEo5UIASAnm2bRp1Jt/e0a2XU8Q==", + "version": "4.17.23", + "resolved": "https://registry.npmmirror.com/lodash-es/-/lodash-es-4.17.23.tgz", + "integrity": "sha512-kVI48u3PZr38HdYz98UmfPnXl2DXrpdctLrFLCd3kOx1xUkOmpFPx7gCWWM5MPkL/fD8zb+Ph0QzjGFs4+hHWg==", "license": "MIT" }, "node_modules/lodash-unified": { @@ -2660,6 +3636,18 @@ "katex": "^0.6.0" } }, + "node_modules/marked": { + "version": "16.4.2", + "resolved": "https://registry.npmmirror.com/marked/-/marked-16.4.2.tgz", + "integrity": "sha512-TI3V8YYWvkVf3KJe1dRkpnjs68JUPyEa5vjKrp1XEEJUAOaQc+Qj+L1qWbPd0SJuAdQkFU0h73sXXqwDYxsiDA==", + "license": "MIT", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 20" + } + }, "node_modules/match-at": { "version": "0.1.1", "resolved": "https://registry.npmmirror.com/match-at/-/match-at-0.1.1.tgz", @@ -2693,6 +3681,60 @@ "integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==", "license": "MIT" }, + "node_modules/mermaid": { + "version": "11.13.0", + "resolved": "https://registry.npmmirror.com/mermaid/-/mermaid-11.13.0.tgz", + "integrity": "sha512-fEnci+Immw6lKMFI8sqzjlATTyjLkRa6axrEgLV2yHTfv8r+h1wjFbV6xeRtd4rUV1cS4EpR9rwp3Rci7TRWDw==", + "license": "MIT", + "dependencies": { + "@braintree/sanitize-url": "^7.1.1", + "@iconify/utils": "^3.0.2", + "@mermaid-js/parser": "^1.0.1", + "@types/d3": "^7.4.3", + "@upsetjs/venn.js": "^2.0.0", + "cytoscape": "^3.33.1", + "cytoscape-cose-bilkent": "^4.1.0", + "cytoscape-fcose": "^2.2.0", + "d3": "^7.9.0", + "d3-sankey": "^0.12.3", + "dagre-d3-es": "7.0.14", + "dayjs": "^1.11.19", + "dompurify": "^3.3.1", + "katex": "^0.16.25", + "khroma": "^2.1.0", + "lodash-es": "^4.17.23", + "marked": "^16.3.0", + "roughjs": "^4.6.6", + "stylis": "^4.3.6", + "ts-dedent": "^2.2.0", + "uuid": "^11.1.0" + } + }, + "node_modules/mermaid/node_modules/commander": { + "version": "8.3.0", + "resolved": "https://registry.npmmirror.com/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/mermaid/node_modules/katex": { + "version": "0.16.42", + "resolved": "https://registry.npmmirror.com/katex/-/katex-0.16.42.tgz", + "integrity": "sha512-sZ4jqyEXfHTLEFK+qsFYToa3UZ0rtFcPGwKpyiRYh2NJn8obPWOQ+/u7ux0F6CAU/y78+Mksh1YkxTPXTh47TQ==", + "funding": [ + "https://opencollective.com/katex", + "https://github.com/sponsors/katex" + ], + "license": "MIT", + "dependencies": { + "commander": "^8.3.0" + }, + "bin": { + "katex": "cli.js" + } + }, "node_modules/micromark-util-character": { "version": "2.1.1", "resolved": "https://registry.npmmirror.com/micromark-util-character/-/micromark-util-character-2.1.1.tgz", @@ -2807,6 +3849,18 @@ "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", "license": "MIT" }, + "node_modules/mlly": { + "version": "1.8.2", + "resolved": "https://registry.npmmirror.com/mlly/-/mlly-1.8.2.tgz", + "integrity": "sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA==", + "license": "MIT", + "dependencies": { + "acorn": "^8.16.0", + "pathe": "^2.0.3", + "pkg-types": "^1.3.1", + "ufo": "^1.6.3" + } + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz", @@ -2925,6 +3979,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/package-manager-detector": { + "version": "1.6.0", + "resolved": "https://registry.npmmirror.com/package-manager-detector/-/package-manager-detector-1.6.0.tgz", + "integrity": "sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA==", + "license": "MIT" + }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmmirror.com/parent-module/-/parent-module-1.0.1.tgz", @@ -2938,6 +3998,12 @@ "node": ">=6" } }, + "node_modules/path-data-parser": { + "version": "0.1.0", + "resolved": "https://registry.npmmirror.com/path-data-parser/-/path-data-parser-0.1.0.tgz", + "integrity": "sha512-NOnmBpt5Y2RWbuv0LMzsayp3lVylAHLPUTut412ZA3l+C4uw4ZVkQbjShYCQ8TCpUMdPapr4YjUqLYD6v68j+w==", + "license": "MIT" + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmmirror.com/path-exists/-/path-exists-4.0.0.tgz", @@ -2958,6 +4024,12 @@ "node": ">=8" } }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmmirror.com/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "license": "MIT" + }, "node_modules/perfect-debounce": { "version": "2.1.0", "resolved": "https://registry.npmmirror.com/perfect-debounce/-/perfect-debounce-2.1.0.tgz", @@ -2982,6 +4054,33 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/pkg-types": { + "version": "1.3.1", + "resolved": "https://registry.npmmirror.com/pkg-types/-/pkg-types-1.3.1.tgz", + "integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==", + "license": "MIT", + "dependencies": { + "confbox": "^0.1.8", + "mlly": "^1.7.4", + "pathe": "^2.0.1" + } + }, + "node_modules/points-on-curve": { + "version": "0.2.0", + "resolved": "https://registry.npmmirror.com/points-on-curve/-/points-on-curve-0.2.0.tgz", + "integrity": "sha512-0mYKnYYe9ZcqMCWhUjItv/oHjvgEsfKvnUTg8sAtnHr3GVy7rGkXCb6d5cSyqrWqL4k81b9CPg3urd+T7aop3A==", + "license": "MIT" + }, + "node_modules/points-on-path": { + "version": "0.2.1", + "resolved": "https://registry.npmmirror.com/points-on-path/-/points-on-path-0.2.1.tgz", + "integrity": "sha512-25ClnWWuw7JbWZcgqY/gJ4FQWadKxGWk+3kR/7kD0tCaDtPPMj7oHu2ToLaVhfpnHrZzYby2w6tUA0eOIuUg8g==", + "license": "MIT", + "dependencies": { + "path-data-parser": "0.1.0", + "points-on-curve": "0.2.0" + } + }, "node_modules/postcss": { "version": "8.5.6", "resolved": "https://registry.npmmirror.com/postcss/-/postcss-8.5.6.tgz", @@ -3110,6 +4209,12 @@ "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", "license": "MIT" }, + "node_modules/robust-predicates": { + "version": "3.0.3", + "resolved": "https://registry.npmmirror.com/robust-predicates/-/robust-predicates-3.0.3.tgz", + "integrity": "sha512-NS3levdsRIUOmiJ8FZWCP7LG3QpJyrs/TE0Zpf1yvZu8cAJJ6QMW92H1c7kWpdIHo8RvmLxN/o2JXTKHp74lUA==", + "license": "Unlicense" + }, "node_modules/rollup": { "version": "4.59.0", "resolved": "https://registry.npmmirror.com/rollup/-/rollup-4.59.0.tgz", @@ -3154,6 +4259,30 @@ "fsevents": "~2.3.2" } }, + "node_modules/roughjs": { + "version": "4.6.6", + "resolved": "https://registry.npmmirror.com/roughjs/-/roughjs-4.6.6.tgz", + "integrity": "sha512-ZUz/69+SYpFN/g/lUlo2FXcIjRkSu3nDarreVdGGndHEBJ6cXPdKguS8JGxwj5HA5xIbVKSmLgr5b3AWxtRfvQ==", + "license": "MIT", + "dependencies": { + "hachure-fill": "^0.5.2", + "path-data-parser": "^0.1.0", + "points-on-curve": "^0.2.0", + "points-on-path": "^0.2.1" + } + }, + "node_modules/rw": { + "version": "1.3.3", + "resolved": "https://registry.npmmirror.com/rw/-/rw-1.3.3.tgz", + "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==", + "license": "BSD-3-Clause" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmmirror.com/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, "node_modules/semver": { "version": "7.7.4", "resolved": "https://registry.npmmirror.com/semver/-/semver-7.7.4.tgz", @@ -3261,6 +4390,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/stylis": { + "version": "4.3.6", + "resolved": "https://registry.npmmirror.com/stylis/-/stylis-4.3.6.tgz", + "integrity": "sha512-yQ3rwFWRfwNUY7H5vpU0wfdkNSnvnJinhF9830Swlaxl03zsOjCfmX0ugac+3LtK0lYSgwL/KXc8oYL3mG4YFQ==", + "license": "MIT" + }, "node_modules/superjson": { "version": "2.2.6", "resolved": "https://registry.npmmirror.com/superjson/-/superjson-2.2.6.tgz", @@ -3292,6 +4427,15 @@ "integrity": "sha512-05PUHKSNE8ou2dwIxTngl4EzcnsCDZGJ/iCLtDflR/SHB/ny14rXc+qU5P4mG9JkusiV7EivzY9Mhm55AzAvCg==", "license": "MIT" }, + "node_modules/tinyexec": { + "version": "1.0.4", + "resolved": "https://registry.npmmirror.com/tinyexec/-/tinyexec-1.0.4.tgz", + "integrity": "sha512-u9r3uZC0bdpGOXtlxUIdwf9pkmvhqJdrVCH9fapQtgy/OeTTMZ1nqH7agtvEfmGui6e1XxjcdrlxvxJvc3sMqw==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/tinyglobby": { "version": "0.2.15", "resolved": "https://registry.npmmirror.com/tinyglobby/-/tinyglobby-0.2.15.tgz", @@ -3318,6 +4462,15 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/ts-dedent": { + "version": "2.2.0", + "resolved": "https://registry.npmmirror.com/ts-dedent/-/ts-dedent-2.2.0.tgz", + "integrity": "sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==", + "license": "MIT", + "engines": { + "node": ">=6.10" + } + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmmirror.com/type-check/-/type-check-0.4.0.tgz", @@ -3354,6 +4507,12 @@ "@types/web-animations-js": "^2.2.16" } }, + "node_modules/ufo": { + "version": "1.6.3", + "resolved": "https://registry.npmmirror.com/ufo/-/ufo-1.6.3.tgz", + "integrity": "sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==", + "license": "MIT" + }, "node_modules/unist-util-is": { "version": "6.0.1", "resolved": "https://registry.npmmirror.com/unist-util-is/-/unist-util-is-6.0.1.tgz", @@ -3439,6 +4598,19 @@ "dev": true, "license": "MIT" }, + "node_modules/uuid": { + "version": "11.1.0", + "resolved": "https://registry.npmmirror.com/uuid/-/uuid-11.1.0.tgz", + "integrity": "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/esm/bin/uuid" + } + }, "node_modules/vfile": { "version": "6.0.3", "resolved": "https://registry.npmmirror.com/vfile/-/vfile-6.0.3.tgz", @@ -3637,6 +4809,55 @@ "vue": "^3.5.0" } }, + "node_modules/vscode-jsonrpc": { + "version": "8.2.0", + "resolved": "https://registry.npmmirror.com/vscode-jsonrpc/-/vscode-jsonrpc-8.2.0.tgz", + "integrity": "sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA==", + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/vscode-languageserver": { + "version": "9.0.1", + "resolved": "https://registry.npmmirror.com/vscode-languageserver/-/vscode-languageserver-9.0.1.tgz", + "integrity": "sha512-woByF3PDpkHFUreUa7Hos7+pUWdeWMXRd26+ZX2A8cFx6v/JPTtd4/uN0/jB6XQHYaOlHbio03NTHCqrgG5n7g==", + "license": "MIT", + "dependencies": { + "vscode-languageserver-protocol": "3.17.5" + }, + "bin": { + "installServerIntoExtension": "bin/installServerIntoExtension" + } + }, + "node_modules/vscode-languageserver-protocol": { + "version": "3.17.5", + "resolved": "https://registry.npmmirror.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.5.tgz", + "integrity": "sha512-mb1bvRJN8SVznADSGWM9u/b07H7Ecg0I3OgXDuLdn307rl/J3A9YD6/eYOssqhecL27hK1IPZAsaqh00i/Jljg==", + "license": "MIT", + "dependencies": { + "vscode-jsonrpc": "8.2.0", + "vscode-languageserver-types": "3.17.5" + } + }, + "node_modules/vscode-languageserver-textdocument": { + "version": "1.0.12", + "resolved": "https://registry.npmmirror.com/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.12.tgz", + "integrity": "sha512-cxWNPesCnQCcMPeenjKKsOCKQZ/L6Tv19DTRIGuLWe32lyzWhihGVJ/rcckZXJxfdKCFvRLS3fpBIsV/ZGX4zA==", + "license": "MIT" + }, + "node_modules/vscode-languageserver-types": { + "version": "3.17.5", + "resolved": "https://registry.npmmirror.com/vscode-languageserver-types/-/vscode-languageserver-types-3.17.5.tgz", + "integrity": "sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg==", + "license": "MIT" + }, + "node_modules/vscode-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/vscode-uri/-/vscode-uri-3.1.0.tgz", + "integrity": "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==", + "license": "MIT" + }, "node_modules/vue": { "version": "3.5.29", "resolved": "https://registry.npmmirror.com/vue/-/vue-3.5.29.tgz", diff --git a/package.json b/package.json index 263cc85..33ff7ec 100644 --- a/package.json +++ b/package.json @@ -38,6 +38,7 @@ "@element-plus/icons-vue": "^2.3.2", "claude": "^0.1.1", "element-plus": "^2.13.1", + "mermaid": "^11.13.0", "typeit": "^8.8.7", "viewerjs": "^1.11.7", "vitepress": "^2.0.0-alpha.16",