import { useCallback, useEffect, useMemo, useState } from 'react' import type { ReactNode } from 'react' import { Alert, Card, Descriptions, Table, Tag, Typography } from 'antd' import EndpointDoc, { CommonResponseDoc } from '../openapi/EndpointDoc' import { commonResponseFields, endpoints, errorCodes, orderStatusTable, paymentStatusTable, } from '../openapi/endpoints' import { endpointSections, type EndpointSpec } from '../openapi/types' const { Paragraph, Text } = Typography const baseUrl = typeof window !== 'undefined' ? window.location.origin.replace(':5173', ':8080') : 'http://localhost:8080' const deliveryModes = [ { mode: 'Web 发货页', scene: '商户想要“只返回一个链接”,让用户直接打开平台页面填写 UID', interfaces: '/api/client/v1/orders/{order_no}/delivery-link', }, { mode: '自建发货页', scene: '商户自己有前端页面,想在自己的系统里完成发货流程', interfaces: '/api/client/v1/orders/{order_no}/delivery\n/api/client/v1/orders/{order_no}/delivery/bind\n/api/client/v1/orders/{order_no}/delivery/submit', }, ] // ---------- TOC 目录构建 ---------- interface TocLeaf { id: string label: string level: 3 // 接口子小节(接口地址/请求方式/...) } interface TocItem { id: string label: string children?: TocLeaf[] } interface TocGroup { title: string items: TocItem[] } function buildToc(): TocGroup[] { return [ { title: '开始', items: [ { id: 'overview', label: '概述' }, { id: 'auth', label: '鉴权与签名' }, ], }, { title: '接口', items: endpoints.map((ep) => ({ id: `ep-${ep.key}`, label: ep.title, children: endpointSections.map((s) => ({ id: `ep-${ep.key}-${s.id}`, label: s.label, level: 3 as const, })), })), }, { title: '附录', items: [{ id: 'status', label: '状态说明' }], }, ] } const toc = buildToc() const pageItems = toc.flatMap((group) => group.items) const pageIds = new Set(pageItems.map((item) => item.id)) function findPageId(id: string): string { if (pageIds.has(id)) return id for (const item of pageItems) { if (item.children?.some((child) => child.id === id)) return item.id } return 'overview' } function getInitialHash() { if (typeof window === 'undefined') return 'overview' return decodeURIComponent(window.location.hash.replace(/^#/, '')) || 'overview' } // ---------- 锚点跳转 ---------- function scrollToId(id: string) { const el = document.getElementById(id) if (!el) return const root = document.getElementById('docScroll') if (!root) return const top = root.scrollTop + el.getBoundingClientRect().top - root.getBoundingClientRect().top - 22 root.scrollTo({ top, behavior: 'smooth' }) } export default function OpenApiDocs() { const initialHash = useMemo(() => getInitialHash(), []) const [activePageId, setActivePageId] = useState(() => findPageId(initialHash)) const [activeAnchorId, setActiveAnchorId] = useState(initialHash) const activeEndpoint = useMemo( () => endpoints.find((spec) => `ep-${spec.key}` === activePageId), [activePageId], ) useEffect(() => { const syncHash = () => { const nextHash = getInitialHash() setActivePageId(findPageId(nextHash)) setActiveAnchorId(nextHash) } window.addEventListener('hashchange', syncHash) return () => window.removeEventListener('hashchange', syncHash) }, []) useEffect(() => { const root = document.getElementById('docScroll') if (!root) return if (activeAnchorId !== activePageId) { requestAnimationFrame(() => scrollToId(activeAnchorId)) return } root.scrollTo({ top: 0 }) }, [activeAnchorId, activePageId]) const activeIndex = useMemo( () => pageItems.findIndex((item) => item.id === activePageId), [activePageId], ) const handleNav = useCallback((id: string) => { const pageId = findPageId(id) setActivePageId(pageId) setActiveAnchorId(id) if (typeof window !== 'undefined') { window.history.replaceState(null, '', `${window.location.pathname}${window.location.search}#${id}`) } }, []) const handleStep = useCallback( (direction: -1 | 1) => { const next = pageItems[activeIndex + direction] if (next) handleNav(next.id) }, [activeIndex, handleNav], ) return (