This commit is contained in:
2026-06-17 11:19:08 +08:00
parent 4c56c7cb04
commit 50312c4c6c
10 changed files with 1933 additions and 4 deletions

View File

@@ -0,0 +1,20 @@
import Link from "next/link";
import { SubpageShell } from "@/components/subpage-shell";
export default function SkillNotFound() {
return (
<SubpageShell>
<section className="band-light band-section">
<div className="container-site section-stack gap-6 py-16 text-center">
<h1 className="type-display-lg text-ink">SKILL </h1>
<p className="type-lead text-body">
访线 SKILL 广
</p>
<Link href="/reports" className="btn-primary w-fit mx-auto">
SKILL 广
</Link>
</div>
</section>
</SubpageShell>
);
}

View File

@@ -0,0 +1,69 @@
import type { Metadata } from "next";
import { notFound } from "next/navigation";
import { Suspense } from "react";
import { SubpageShell } from "@/components/subpage-shell";
import { JsonLd } from "@/components/json-ld";
import { SkillDetail } from "@/components/skill-detail";
import {
getAllSkillIds,
getSkillById,
categoryMeta,
} from "@/content/skills";
import { pageMetadata } from "@/lib/site-metadata";
type PageProps = {
params: Promise<{ skill: string }>;
};
export function generateStaticParams() {
return getAllSkillIds().map((skill) => ({ skill }));
}
export async function generateMetadata({
params,
}: PageProps): Promise<Metadata> {
const { skill: id } = await params;
const skill = getSkillById(id);
if (!skill) {
return { title: "SKILL 未找到", robots: { index: false, follow: false } };
}
return pageMetadata({
title: `${skill.title} - SKILL 详情`,
description: skill.body,
path: `/reports/${skill.id}`,
keywords: [skill.title, skill.slug, "SKILL", "海宇数科"],
});
}
export default async function SkillDetailPage({ params }: PageProps) {
const { skill: id } = await params;
const skill = getSkillById(id);
if (!skill) {
notFound();
}
const cat = categoryMeta[skill.category];
const jsonLd = {
"@context": "https://schema.org",
"@type": "SoftwareApplication",
name: skill.title,
description: skill.body,
applicationCategory: "BusinessApplication",
operatingSystem: "Web",
url: `/reports/${skill.id}`,
keywords: [skill.title, skill.slug, cat.label].join(", "),
};
return (
<SubpageShell>
<JsonLd data={jsonLd} />
<section className="band-light band-section">
<div className="container-site section-stack gap-8 py-12">
<Suspense fallback={null}>
<SkillDetail skill={skill} />
</Suspense>
</div>
</section>
</SubpageShell>
);
}

View File

@@ -1,24 +1,34 @@
import type { Metadata } from "next"; import type { Metadata } from "next";
import { SubpageShell } from "@/components/subpage-shell"; import { SubpageShell } from "@/components/subpage-shell";
import { PageHero } from "@/components/page-hero"; import { PageHero } from "@/components/page-hero";
import { SectionIntelligentReport } from "@/components/section-intelligent-report"; import { SkillHowToUse } from "@/components/skill-how-to-use";
import { SkillMarketplace } from "@/components/skill-marketplace";
import { site } from "@/content/site"; import { site } from "@/content/site";
import { pageMetadata } from "@/lib/site-metadata"; import { pageMetadata } from "@/lib/site-metadata";
const s = site.intelligentReport; const s = site.skillMarket;
export const metadata: Metadata = pageMetadata({ export const metadata: Metadata = pageMetadata({
title: s.title, title: s.title,
description: s.lead, description: s.lead,
path: "/reports", path: "/reports",
keywords: ["智能报告", "KYB", "企业核验", "尽调", "海宇数科"], keywords: ["智能报告", "SKILL", "KYB", "企业核验", "尽调", "海宇数科"],
}); });
export default function ReportsPage() { export default function ReportsPage() {
return ( return (
<SubpageShell> <SubpageShell>
<PageHero eyebrow={s.eyebrow} title={s.title} lead={s.lead} /> <PageHero eyebrow={s.eyebrow} title={s.title} lead={s.lead} />
<SectionIntelligentReport /> <section className="band-light band-section" id="how-to-use">
<div className="container-site">
<SkillHowToUse />
</div>
</section>
<section className="band-muted band-section" id="skills">
<div className="container-site section-stack">
<SkillMarketplace />
</div>
</section>
</SubpageShell> </SubpageShell>
); );
} }

View File

@@ -0,0 +1,250 @@
"use client";
import Link from "next/link";
import { useCallback, useEffect, useMemo, useState } from "react";
import { useRouter, useSearchParams } from "next/navigation";
import type { Skill } from "@/content/skills";
import { categoryMeta } from "@/content/skills";
import { site } from "@/content/site";
import { SkillExampleContent } from "@/components/skill-example-content";
type Tab = "experience" | "install" | "example";
const TABS: { id: Tab; label: string }[] = [
{ id: "experience", label: "在线运行" },
{ id: "install", label: "安装方式" },
{ id: "example", label: "示例报告" },
];
type Report = (typeof site.intelligentReport.reports)[number];
type SkillDetailProps = {
skill: Skill;
};
export function SkillDetail({ skill }: SkillDetailProps) {
const router = useRouter();
const searchParams = useSearchParams();
const cat = categoryMeta[skill.category];
const rawTab = searchParams.get("tab");
const tab: Tab = rawTab === "install" || rawTab === "example" ? rawTab : "experience";
const [company, setCompany] = useState(skill.detail.runDefaultCompany);
const [promptCopied, setPromptCopied] = useState(false);
const [configCopied, setConfigCopied] = useState(false);
const reports = site.intelligentReport.reports;
const reportById = useMemo(() => {
const map: Record<string, Report> = {};
for (const r of reports) map[r.id] = r;
return map;
}, [reports]);
const example = skill.exampleId ? reportById[skill.exampleId] : undefined;
const switchTab = useCallback(
(next: Tab) => {
const params = new URLSearchParams(searchParams.toString());
if (next === "experience") params.delete("tab");
else params.set("tab", next);
const qs = params.toString();
router.replace(qs ? `?${qs}` : "?", { scroll: false });
},
[router, searchParams]
);
useEffect(() => {
if (!promptCopied) return;
const t = setTimeout(() => setPromptCopied(false), 1800);
return () => clearTimeout(t);
}, [promptCopied]);
useEffect(() => {
if (!configCopied) return;
const t = setTimeout(() => setConfigCopied(false), 1800);
return () => clearTimeout(t);
}, [configCopied]);
const copyPrompt = async () => {
try {
await navigator.clipboard.writeText(skill.detail.installPrompt);
setPromptCopied(true);
} catch {
/* ignore */
}
};
const copyConfig = async () => {
try {
await navigator.clipboard.writeText(
JSON.stringify(site.skillMarket.howToUse.configJson, null, 2)
);
setConfigCopied(true);
} catch {
/* ignore */
}
};
return (
<div className="skill-detail">
<nav className="skill-detail__crumbs" aria-label="面包屑">
<ol>
<li>
<Link href="/reports">SKILL 广</Link>
</li>
<li aria-hidden>/</li>
<li>{cat.label}</li>
<li aria-hidden>/</li>
<li aria-current="page">{skill.title}</li>
</ol>
</nav>
<header className="skill-detail__head">
<h1 className="skill-detail__title type-display-lg text-ink">
{skill.title}
</h1>
<p className="skill-detail__lead type-lead text-body">{skill.body}</p>
</header>
<div className="skill-detail__tabs" role="tablist" aria-label="SKILL 详情">
{TABS.map((t) => {
const active = t.id === tab;
return (
<button
key={t.id}
type="button"
role="tab"
aria-selected={active}
className={`skill-detail__tab${active ? " skill-detail__tab--active" : ""}`}
onClick={() => switchTab(t.id)}
>
{t.label}
</button>
);
})}
</div>
<div className="skill-detail__panel">
{tab === "experience" ? (
<section className="skill-run">
<div className="skill-run__head">
<h2 className="skill-run__title type-display-md text-ink">
线 · {skill.detail.runSubtitle}
</h2>
<p className="skill-run__hint type-body-md text-body">
· AI SKILL
</p>
</div>
<div className="skill-run__field">
<label className="skill-run__label type-caption-strong text-ink">
</label>
<input
type="text"
value={company}
onChange={(e) => setCompany(e.target.value)}
placeholder={skill.detail.runPlaceholder}
className="skill-run__input"
/>
{company ? (
<button
type="button"
className="skill-run__clear"
aria-label="清空企业名称"
onClick={() => setCompany("")}
>
×
</button>
) : null}
</div>
<button type="button" className="btn-primary skill-run__go">
{skill.detail.runButton}
</button>
<p className="skill-run__note type-caption text-body-light">
MCP 使 SKILL
</p>
</section>
) : null}
{tab === "install" ? (
<section className="skill-install">
<div className="skill-install__step">
<h2 className="skill-install__title type-display-md text-ink">
MCP
</h2>
<p className="skill-install__desc type-body-md text-body">
API Key AI SKILL
</p>
<div className="skill-code-block" data-open="true">
<div className="skill-code-block__header">
<span className="skill-code-block__filename">
<span className="skill-code-block__dot" aria-hidden />
{site.skillMarket.howToUse.configLabel}
</span>
<div className="skill-code-block__actions">
<button type="button" onClick={copyConfig}>
{configCopied
? site.skillMarket.howToUse.copiedLabel
: site.skillMarket.howToUse.copyLabel}
</button>
</div>
</div>
<pre className="skill-code-block__pre">
<code>
{JSON.stringify(site.skillMarket.howToUse.configJson, null, 2)}
</code>
</pre>
</div>
</div>
<div className="skill-install__step">
<h3 className="skill-install__subtitle type-display-sm text-ink">
SKILL AI
</h3>
<p className="skill-install__desc type-body-md text-body">
AI {skill.title}
</p>
<div className="skill-code-block" data-open="true">
<div className="skill-code-block__header">
<span className="skill-code-block__filename">
<span className="skill-code-block__dot" aria-hidden />
AI
</span>
<div className="skill-code-block__actions">
<button type="button" onClick={copyPrompt}>
{promptCopied
? site.skillMarket.howToUse.copiedLabel
: site.skillMarket.howToUse.copyLabel}
</button>
</div>
</div>
<pre className="skill-code-block__pre skill-code-block__pre--text">
<code>{skill.detail.installPrompt}</code>
</pre>
</div>
</div>
</section>
) : null}
{tab === "example" ? (
<section className="skill-example">
{example ? (
<SkillExampleContent report={example} />
) : (
<div className="skill-example__empty">
<p className="type-body-md text-body">{skill.detail.exampleMissingHint}</p>
<button
type="button"
className="btn-outline"
onClick={() => switchTab("experience")}
>
线
</button>
</div>
)}
</section>
) : null}
</div>
</div>
);
}

View File

@@ -0,0 +1,42 @@
import { site } from "@/content/site";
type Report = (typeof site.intelligentReport.reports)[number];
type SkillExampleContentProps = {
report: Report;
};
/**
* 示例报告内容渲染 — 卡片弹窗与详情页「示例报告」tab 共用同一套结构与样式。
* 外层overlay/dialog/footer由各自调用方自行包裹。
*/
export function SkillExampleContent({ report }: SkillExampleContentProps) {
return (
<div className="report-example-content">
<header className="report-example-content__header flex flex-col gap-3">
<p className="type-mono-caps-label text-body">{report.audience}</p>
<h3 className="report-example-content__title type-display-lg text-balance text-ink">
{report.example.title}
</h3>
<p className="type-mono-caption text-body">{report.example.meta}</p>
</header>
<div className="report-example-content__body">
{report.example.sections.map((sec) => (
<section key={sec.heading} className="report-example-section">
<h4 className="type-caption-strong text-ink">{sec.heading}</h4>
{"body" in sec && sec.body ? (
<p className="type-body-md text-body">{sec.body}</p>
) : null}
{"bullets" in sec && sec.bullets ? (
<ul className="report-example-list">
{sec.bullets.map((item) => (
<li key={item}>{item}</li>
))}
</ul>
) : null}
</section>
))}
</div>
</div>
);
}

View File

@@ -0,0 +1,119 @@
"use client";
import { useState } from "react";
import { site } from "@/content/site";
import { ScrollReveal } from "@/components/scroll-reveal";
export function SkillHowToUse() {
const h = site.skillMarket.howToUse;
const [copied, setCopied] = useState(false);
const [codeOpen, setCodeOpen] = useState(false);
const jsonString = JSON.stringify(h.configJson, null, 2);
const preview = jsonString.split("\n").slice(0, 3).join("\n");
const handleCopy = async () => {
try {
await navigator.clipboard.writeText(jsonString);
setCopied(true);
setTimeout(() => setCopied(false), 1800);
} catch {
// 剪贴板不可用时静默失败
}
};
return (
<ScrollReveal className="skill-howto">
<header className="skill-howto__head">
<p className="section-eyebrow type-eyebrow">{h.eyebrow}</p>
<h2 className="skill-howto__title type-display-lg text-ink">{h.title}</h2>
<p className="skill-howto__subtitle type-lead text-body">{h.subtitle}</p>
</header>
<div className="skill-howto__layout">
<ol className="skill-howto__steps">
{h.steps.map((step) => (
<li key={step.no} className="skill-howto-step">
<span className="skill-howto-step__no" aria-hidden>
{step.no}
</span>
<div className="skill-howto-step__copy">
<h3 className="skill-howto-step__title type-caption-strong text-ink">
{step.title}
</h3>
<p className="skill-howto-step__desc type-body-md text-body">
{step.desc}
</p>
</div>
</li>
))}
</ol>
<div className="skill-howto-config">
<p className="skill-howto-config__intro type-body-md text-body">
{h.configIntro}
</p>
<div className="skill-code-block" data-open={codeOpen ? "true" : "false"}>
<div className="skill-code-block__header">
<span className="skill-code-block__filename">
<span className="skill-code-block__dot" aria-hidden />
{h.configLabel}
</span>
<div className="skill-code-block__actions">
<button
type="button"
className="skill-code-block__btn"
onClick={handleCopy}
>
{copied ? h.copiedLabel : h.copyLabel}
</button>
<button
type="button"
className="skill-code-block__btn skill-code-block__btn--toggle"
onClick={() => setCodeOpen((v) => !v)}
aria-expanded={codeOpen}
>
{codeOpen ? h.collapseLabel : h.expandLabel}
<svg
className="skill-code-block__chevron"
width="12"
height="12"
viewBox="0 0 12 12"
fill="none"
aria-hidden
>
<path
d="M3 4.5L6 7.5L9 4.5"
stroke="currentColor"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
</button>
</div>
</div>
<pre className="skill-code-block__pre">
<code>{codeOpen ? jsonString : preview + "\n…"}</code>
</pre>
</div>
<p className="skill-howto-config__apps">
<span className="skill-howto-config__apps-label">
{h.supportedAppsLabel}
</span>
{h.supportedApps.map((app) => (
<span key={app} className="skill-howto-config__app">
{app}
</span>
))}
</p>
<p className="skill-howto-config__soon type-caption text-body-light">
{h.comingSoonHint}
</p>
</div>
</div>
</ScrollReveal>
);
}

View File

@@ -0,0 +1,191 @@
"use client";
import { useCallback, useEffect, useMemo, useState } from "react";
import Link from "next/link";
import { site } from "@/content/site";
import { ScrollRevealItem } from "@/components/scroll-reveal";
import { SkillExampleContent } from "@/components/skill-example-content";
type Skill = (typeof site.skillMarket.skills)[number];
type Report = (typeof site.intelligentReport.reports)[number];
export function SkillMarketplace() {
const m = site.skillMarket;
const reports = site.intelligentReport.reports;
const [activeCategory, setActiveCategory] = useState<string>("all");
const [query, setQuery] = useState("");
const [activeExample, setActiveExample] = useState<Report | null>(null);
const exampleById = useMemo(() => {
const map: Record<string, Report> = {};
for (const r of reports) map[r.id] = r;
return map;
}, [reports]);
const counts = useMemo(() => {
const c: Record<string, number> = { all: m.skills.length };
for (const cat of m.categories) {
if (cat.id === "all") continue;
c[cat.id] = m.skills.filter((s) => s.category === cat.id).length;
}
return c;
}, [m.skills, m.categories]);
const filtered = useMemo(() => {
const q = query.trim().toLowerCase();
return m.skills.filter((s) => {
if (activeCategory !== "all" && s.category !== activeCategory) return false;
if (!q) return true;
return (
s.title.toLowerCase().includes(q) ||
s.body.toLowerCase().includes(q) ||
s.slug.toLowerCase().includes(q)
);
});
}, [m.skills, activeCategory, query]);
const openExample = useCallback(
(skill: Skill) => {
if (!("exampleId" in skill) || !skill.exampleId) return;
const ex = exampleById[skill.exampleId];
if (ex) setActiveExample(ex);
},
[exampleById]
);
const closeExample = useCallback(() => setActiveExample(null), []);
useEffect(() => {
if (!activeExample) return;
const prev = document.body.style.overflow;
document.body.style.overflow = "hidden";
return () => {
document.body.style.overflow = prev;
};
}, [activeExample]);
return (
<>
<div className="skill-marketplace">
<div className="skill-marketplace__toolbar">
<div className="skill-marketplace__categories" role="tablist" aria-label="技能分类">
{m.categories.map((cat) => {
const isActive = activeCategory === cat.id;
return (
<button
key={cat.id}
type="button"
role="tab"
aria-selected={isActive}
className={`skill-category-chip${isActive ? " skill-category-chip--active" : ""}`}
onClick={() => setActiveCategory(cat.id)}
>
<span className="skill-category-chip__emoji" aria-hidden>
{cat.emoji}
</span>
<span className="skill-category-chip__label">{cat.label}</span>
<span className="skill-category-chip__count">{counts[cat.id] ?? 0}</span>
</button>
);
})}
</div>
<div className="skill-marketplace__search">
<input
type="search"
value={query}
onChange={(e) => setQuery(e.target.value)}
placeholder="搜索 SKILL..."
aria-label="搜索 SKILL"
className="skill-search-input"
/>
</div>
</div>
{filtered.length === 0 ? (
<p className="skill-marketplace__empty type-body-md text-body">
SKILL
</p>
) : (
<div className="skill-marketplace__grid">
{filtered.map((skill, index) => {
const exampleId =
"exampleId" in skill ? skill.exampleId : undefined;
const hasExample = Boolean(exampleId && exampleById[exampleId]);
return (
<ScrollRevealItem key={skill.id} index={index} className="h-full">
<article className="skill-card">
<div className="skill-card__head">
<h3 className="skill-card__title type-display-md text-ink">
<Link
href={`/reports/${skill.id}`}
className="skill-card__title-link"
>
{skill.title}
</Link>
</h3>
<code className="skill-card__slug">{skill.slug}</code>
</div>
<p className="skill-card__body type-body-md text-body">
{skill.body}
</p>
<div className="skill-card__cta">
<Link
href={`/reports/${skill.id}?tab=experience`}
className="btn-outline btn-sm"
>
{m.runLabel}
</Link>
<Link
href={`/reports/${skill.id}?tab=install`}
className="btn-outline btn-sm"
>
{m.installLabel}
</Link>
<button
type="button"
className="btn-outline btn-sm"
onClick={() => openExample(skill)}
disabled={!hasExample}
title={hasExample ? m.exampleLabel : m.exampleMissingHint}
>
{m.exampleLabel}
</button>
</div>
</article>
</ScrollRevealItem>
);
})}
</div>
)}
</div>
{activeExample ? (
<div
className="report-example-overlay"
role="dialog"
aria-modal="true"
aria-labelledby="skill-example-title"
onClick={(e) => {
if (e.target === e.currentTarget) closeExample();
}}
onKeyDown={(e) => {
if (e.key === "Escape") closeExample();
}}
>
<div className="report-example-dialog">
<div className="report-example-dialog__inner">
<SkillExampleContent report={activeExample} />
<footer className="report-example-dialog__footer">
<button type="button" className="btn-primary" onClick={closeExample}>
</button>
</footer>
</div>
</div>
</div>
) : null}
</>
);
}

View File

@@ -188,6 +188,196 @@ export const site = {
}, },
], ],
}, },
skillMarket: {
eyebrow: "产品",
title: "SKILL 广场",
lead: "27 个经过真实场景验证的 SKILL覆盖银行、投资、法务、采购四大客群。配置海宇数科 MCP3 分钟跑通首份尽调报告。",
howToUse: {
eyebrow: "使用指南",
title: "怎么使用 SKILL ",
subtitle: "三步完成,在 AI 对话框里直接操作,不需要写代码",
steps: [
{
no: "01",
title: "配置海宇数科 MCP",
desc: "注册账号、获取 API Key、粘贴配置到你的 AI 工具。只需做一次,所有 SKILL 共用。",
},
{
no: "02",
title: "发送给 AI 加载",
desc: "把下方配置原样发给你的 AIAI 会自动加载对应客群的 SKILL 技能包。",
},
{
no: "03",
title: "像聊天一样下指令",
desc: "AI 确认加载后,像平时聊天一样说出需求,例如「帮我做一份某科技公司的 KYB 核验」。",
},
],
configLabel: "MCP 配置",
configIntro: "安装海宇数科 MCP Server即可在对话中调用全部企业核验与尽调 SKILL。",
configJson: {
mcpServers: {
haiyushuke: {
command: "npx",
args: ["-y", "@haiyushuke/mcp-server"],
env: {
HAIYUSHUKE_API_KEY: "your_api_key_here",
},
},
},
},
supportedAppsLabel: "已接入",
supportedApps: ["小龙虾", "OpenClaw", "Cursor", "Claude Code", "更多智能体"],
comingSoonHint: "一键安装通道即将开放,当前可手动按上方配置接入。",
expandLabel: "展开",
collapseLabel: "收起",
copyLabel: "复制",
copiedLabel: "已复制",
},
categories: [
{ id: "all", label: "全部", emoji: "✨" },
{ id: "bank", label: "银行", emoji: "🏦" },
{ id: "invest", label: "投资", emoji: "📊" },
{ id: "legal", label: "法务", emoji: "⚖️" },
{ id: "supply", label: "供应链", emoji: "🔗" },
],
skills: [
{
id: "kyb-verification",
category: "bank",
title: "KYB 企业核验",
slug: "/kyb-verification",
body: "银行开户和授信审批前的核心合规工具。输入企业名称AI 自动完成主体真实性核验、股权结构穿透、34 类司法与经营风险扫描3 分钟输出符合 FATF / PBOC / CBIRC 标准的合规报告,替代传统客户经理 2-3 小时人工尽调流程。",
exampleId: "kyb",
},
{
id: "ic-memo",
category: "invest",
title: "IC Memo 投资备忘录",
slug: "/ic-memo",
body: "PE/VC 投资决策的核心尽调工具。一次对话并行完成目标公司工商登记、多层股权穿透、司法风险、知识产权全维度扫描,直接输出符合投委会要求的标准格式备忘录,传统分析师 2-3 小时工作量压缩至约 3 分钟。",
exampleId: "ic-memo",
},
{
id: "contract-review",
category: "legal",
title: "合同核验",
slug: "/contract-review",
body: "合同审核与主体核验一体化工具。AI 审阅合同文本的同时自动核验合同双方的工商登记状态、34 类司法与经营风险,营业执照注销、被执行、股权冻结等异常立即生成高风险批注,防范与问题主体签约导致合同无效或履约失败。",
exampleId: "contract",
},
{
id: "new-supplier-screening",
category: "supply",
title: "新供应商快速筛选",
slug: "/new-supplier-screening",
body: "招投标评审阶段的候选供应商快速筛查工具。对多家候选供应商同步核查工商合规性、失信被执行黑名单状态与资质证件真实性3 分钟批量出具筛查结论,在评审会召开前帮助采购团队快速排除存在硬性风险的候选方。",
exampleId: "supplier",
},
{
id: "history-evolution",
category: "invest",
title: "企业历史沿革与发展历程",
slug: "/history-evolution",
body: "PE/VC 投前立项与拓客调研的主体背调工具。一次对话并行整合目标企业工商登记、股权历史、董监高沿革、知识产权与荣誉资质,生成涵盖发展里程碑、股权演变、关联方网络与资质荣誉的历史沿革报告,小微/融资/上市三档自适应3 分钟替代分析师 4-6 小时手工梳理。",
},
{
id: "contract-party-check",
category: "legal",
title: "合同相对方主体核验",
slug: "/contract-party-check",
body: "合同签署前的主体快速核验工具。自动查验合同相对方的工商登记状态、法定代表人真实性与经营异常情况,在签字盖章前识别已注销、吊销或存在重大风险的问题主体,防范合同无效与履约失败风险。",
},
{
id: "vendor-assess",
category: "supply",
title: "供应商准入评估",
slug: "/vendor-assess",
body: "采购准入阶段的供应商深度尽调工具。输入供应商名称AI 自动完成多维度风险评估,覆盖 34 类中国特有风险信号,包括司法执行、经营异常、税务违规、破产风险等,输出结构化准入报告,辅助采购评审委员会做出有据可查的准入决策。",
},
{
id: "fundraising-tracker",
category: "invest",
title: "融资历史追踪",
slug: "/fundraising-tracker",
body: "早期投资研究的融资历史溯源工具。系统追溯目标企业历次融资记录,结合各轮次股权变更情况推演估值区间变化趋势,帮助 VC 和 FA 在接触企业前快速建立对其融资历史的完整认知,为估值谈判提供数据参考。",
},
{
id: "ubo-screening",
category: "bank",
title: "受益所有人识别",
slug: "/ubo-screening",
body: "对公开户、高净值客户尽调与反洗钱日常筛查场景下的受益所有人识别工具。3 分钟自动锁定企业最终受益人,按央行 3 号令 25% 阈值穿透股权链路、查明实控人画像与关联企业网络,输出可直接归档 KYC 系统的合规档案。",
},
{
id: "equity-structure",
category: "invest",
title: "股权结构穿透分析",
slug: "/equity-structure",
body: "投资决策前的控制权核查工具。自动完成多层股权穿透并生成可视化结构图,识别实际控制人、一致行动人及隐性关联关系,帮助投资团队在 DD 阶段快速厘清目标公司的实际控制架构,识别潜在的关联交易风险。",
},
{
id: "executive-background",
category: "bank",
title: "高管背景核查",
slug: "/executive-background",
body: "高管背景核查工具,默认对全体董监高(含董事/监事/独立董事/职工代表董事)+ 实际控制人 + 主要高管做完整画像。覆盖失信、限高、限出境、股权冻结、税收违法、控制企业、在外任职、历史合作等维度。",
},
{
id: "credit-due-diligence",
category: "bank",
title: "授信尽调报告",
slug: "/credit-due-diligence",
body: "信贷审批流程中的企业全维度尽调工具。授信前自动汇总目标企业工商登记、实控人信息、经营异常记录与司法风险状况,输出标准化授信尽调底稿,帮助信贷审批人在放款前全面掌握企业风险画像,支撑有据可查的授信决策。",
},
{
id: "credit-monitoring",
category: "bank",
title: "信贷风险定期监控",
slug: "/credit-monitoring",
body: "贷后管理中对存量借款客户的持续风险监控工具。定期对贷款企业进行风险体检,失信被执行、经营异常登记、法定代表人变更等关键风险信号一旦发生变化即时推送预警,帮助客户经理在风险暴露初期及时采取措施。",
},
{
id: "litigation-analysis",
category: "legal",
title: "诉讼风险评估",
slug: "/litigation-analysis",
body: "立案前的对手方诉讼风险评估工具。以「企业当前 × 企业历史 × 核心人员」三层穿透扫描对手方,整合裁判文书、立案信息、执行风险、限高/限出境与法代/实控人个人涉诉档案,基于 5-10 年历史数据做时间序列趋势分析,支撑立案、授信、投资决策。",
},
{
id: "counterparty-risk",
category: "bank",
title: "交易对手风险评估",
slug: "/counterparty-risk",
body: "贸易融资业务中对交易对手的多维风险评估工具。整合企业进出口信用等级、行政处罚记录、失信被执行情况与经营异常信息,生成综合风险评分,帮助贸易金融团队在开证、议付等关键节点识别潜在违约风险。",
},
{
id: "strip-profile",
category: "invest",
title: "企业画像速览",
slug: "/strip-profile",
body: "投资前快速了解目标公司的轻量尽调工具。3 分钟生成一页纸企业画像,整合工商登记、核心风险信号与知识产权资产,以结构化方式呈现企业基本面,适用于 LP 推介前、项目初步筛选和内部立项汇报场景。",
},
{
id: "ip-asset-inventory",
category: "invest",
title: "知产资产清单",
slug: "/ip-asset-inventory",
body: "科技型企业投资与并购前的知产资产核查工具。全量输出目标企业专利、商标、软件著作权的数量与有效性状态,评估技术壁垒深度与核心资产的法律稳定性,帮助投资团队在 DD 阶段准确判断企业的知产护城河。",
},
{
id: "supplier-annual-check",
category: "supply",
title: "供应商年度健康体检",
slug: "/supplier-annual-check",
body: "供应商年度评审的标准化核查工具。对核心供应商进行全面年度体检,一次性输出经营状态变化、资质证件到期情况、信用记录变更的结构化报告,帮助采购团队在年度供应商评审和续签决策中有充分的数据支撑。",
},
],
runLabel: "在线运行",
installLabel: "安装方式",
exampleLabel: "示例报告",
exampleMissingHint: "示例报告整理中",
},
reportAgent: { reportAgent: {
eyebrow: "Skill", eyebrow: "Skill",
title: "智能体技能一键安装", title: "智能体技能一键安装",

289
src/content/skills.ts Normal file
View File

@@ -0,0 +1,289 @@
/**
* SKILL 技能数据 — 列表页与详情页共用同一数据源。
*
* id: 列表卡片与路由的唯一标识(用作 /reports/[skill] 的动态段)
* category: 分类 id对应 skillMarket.categories
* title: 技能名
* slug: 短命令标识(展示用)
* body: 一句话描述(列表卡片用)
* exampleId: 关联 intelligentReport.reports 的 id没有则留空
* detail: 详情页专属内容
*/
export type SkillCategory = "bank" | "invest" | "legal" | "supply";
export type SkillDetail = {
/** 在线运行 tab副标题 */
runSubtitle: string;
/** 在线运行 tab输入框提示 */
runPlaceholder: string;
/** 在线运行 tab企业名称默认示例 */
runDefaultCompany: string;
/** 在线运行 tab运行按钮文案 */
runButton: string;
/** 安装方式 tab加载本技能给 AI 的提示词Markdown 片段) */
installPrompt: string;
/** 示例报告 tab空状态提示无 exampleId 时显示) */
exampleMissingHint: string;
};
export type Skill = {
id: string;
category: SkillCategory;
title: string;
slug: string;
body: string;
exampleId?: string;
detail: SkillDetail;
};
export const categoryMeta: Record<
SkillCategory,
{ slug: string; label: string }
> = {
bank: { slug: "banking", label: "银行 · 合规风控" },
invest: { slug: "invest", label: "投资 · 尽调" },
legal: { slug: "legal", label: "法务 · 合规" },
supply: { slug: "supply", label: "供应链 · 采购" },
};
const sharedRun = {
runPlaceholder: "支持企业完整登记名称或 18 位统一社会信用代码",
runDefaultCompany: "示例:某科技有限公司",
runButton: "打开在线运行",
exampleMissingHint: "该 SKILL 的示例报告正在整理中,敬请期待。",
};
const sharedInstall = (title: string, slug: string) =>
`将下面这段配置发给你的 AIAI 会自动加载「${title}」技能:\n\n\`\`\`\n@haiyushuke/mcp ${slug}\n\`\`\`\n\nAI 确认加载后,像平时聊天一样说出你的需求即可。`;
export const skills: Skill[] = [
{
id: "kyb-verification",
category: "bank",
title: "KYB 企业核验",
slug: "/kyb-verification",
body: "银行开户和授信审批前的核心合规工具。输入企业名称AI 自动完成主体真实性核验、股权结构穿透、34 类司法与经营风险扫描3 分钟输出符合 FATF / PBOC / CBIRC 标准的合规报告,替代传统客户经理 2-3 小时人工尽调流程。",
exampleId: "kyb",
detail: {
runSubtitle: "输入企业名称AI 自动完成 KYB 企业核验",
...sharedRun,
installPrompt: sharedInstall("KYB 企业核验", "/kyb-verification"),
},
},
{
id: "ic-memo",
category: "invest",
title: "IC Memo 投资备忘录",
slug: "/ic-memo",
body: "PE/VC 投资决策的核心尽调工具。一次对话并行完成目标公司工商登记、多层股权穿透、司法风险、知识产权全维度扫描,直接输出符合投委会要求的标准格式备忘录,传统分析师 2-3 小时工作量压缩至约 3 分钟。",
exampleId: "ic-memo",
detail: {
runSubtitle: "输入目标公司AI 自动生成 IC Memo 投资备忘录",
...sharedRun,
installPrompt: sharedInstall("IC Memo 投资备忘录", "/ic-memo"),
},
},
{
id: "contract-review",
category: "legal",
title: "合同核验",
slug: "/contract-review",
body: "合同审核与主体核验一体化工具。AI 审阅合同文本的同时自动核验合同双方的工商登记状态、34 类司法与经营风险,营业执照注销、被执行、股权冻结等异常立即生成高风险批注,防范与问题主体签约导致合同无效或履约失败。",
exampleId: "contract",
detail: {
runSubtitle: "上传合同或输入双方名称AI 自动核验并批注风险",
...sharedRun,
installPrompt: sharedInstall("合同核验", "/contract-review"),
},
},
{
id: "new-supplier-screening",
category: "supply",
title: "新供应商快速筛选",
slug: "/new-supplier-screening",
body: "招投标评审阶段的候选供应商快速筛查工具。对多家候选供应商同步核查工商合规性、失信被执行黑名单状态与资质证件真实性3 分钟批量出具筛查结论,在评审会召开前帮助采购团队快速排除存在硬性风险的候选方。",
exampleId: "supplier",
detail: {
runSubtitle: "批量输入候选供应商AI 同步筛查并给出结论",
...sharedRun,
installPrompt: sharedInstall("新供应商快速筛选", "/new-supplier-screening"),
},
},
{
id: "history-evolution",
category: "invest",
title: "企业历史沿革与发展历程",
slug: "/history-evolution",
body: "PE/VC 投前立项与拓客调研的主体背调工具。一次对话并行整合目标企业工商登记、股权历史、董监高沿革、知识产权与荣誉资质,生成涵盖发展里程碑、股权演变、关联方网络与资质荣誉的历史沿革报告,小微/融资/上市三档自适应3 分钟替代分析师 4-6 小时手工梳理。",
detail: {
runSubtitle: "输入目标企业AI 自动生成历史沿革与发展历程报告",
...sharedRun,
installPrompt: sharedInstall("企业历史沿革与发展历程", "/history-evolution"),
},
},
{
id: "contract-party-check",
category: "legal",
title: "合同相对方主体核验",
slug: "/contract-party-check",
body: "合同签署前的主体快速核验工具。自动查验合同相对方的工商登记状态、法定代表人真实性与经营异常情况,在签字盖章前识别已注销、吊销或存在重大风险的问题主体,防范合同无效与履约失败风险。",
detail: {
runSubtitle: "输入合同相对方名称AI 快速核验主体真实性",
...sharedRun,
installPrompt: sharedInstall("合同相对方主体核验", "/contract-party-check"),
},
},
{
id: "vendor-assess",
category: "supply",
title: "供应商准入评估",
slug: "/vendor-assess",
body: "采购准入阶段的供应商深度尽调工具。输入供应商名称AI 自动完成多维度风险评估,覆盖 34 类中国特有风险信号,包括司法执行、经营异常、税务违规、破产风险等,输出结构化准入报告,辅助采购评审委员会做出有据可查的准入决策。",
detail: {
runSubtitle: "输入供应商名称AI 输出多维度风险评估与准入结论",
...sharedRun,
installPrompt: sharedInstall("供应商准入评估", "/vendor-assess"),
},
},
{
id: "fundraising-tracker",
category: "invest",
title: "融资历史追踪",
slug: "/fundraising-tracker",
body: "早期投资研究的融资历史溯源工具。系统追溯目标企业历次融资记录,结合各轮次股权变更情况推演估值区间变化趋势,帮助 VC 和 FA 在接触企业前快速建立对其融资历史的完整认知,为估值谈判提供数据参考。",
detail: {
runSubtitle: "输入目标企业AI 追溯历次融资并推演估值趋势",
...sharedRun,
installPrompt: sharedInstall("融资历史追踪", "/fundraising-tracker"),
},
},
{
id: "ubo-screening",
category: "bank",
title: "受益所有人识别",
slug: "/ubo-screening",
body: "对公开户、高净值客户尽调与反洗钱日常筛查场景下的受益所有人识别工具。3 分钟自动锁定企业最终受益人,按央行 3 号令 25% 阈值穿透股权链路、查明实控人画像与关联企业网络,输出可直接归档 KYC 系统的合规档案。",
detail: {
runSubtitle: "输入企业名称AI 按央行 3 号令识别最终受益人",
...sharedRun,
installPrompt: sharedInstall("受益所有人识别", "/ubo-screening"),
},
},
{
id: "equity-structure",
category: "invest",
title: "股权结构穿透分析",
slug: "/equity-structure",
body: "投资决策前的控制权核查工具。自动完成多层股权穿透并生成可视化结构图,识别实际控制人、一致行动人及隐性关联关系,帮助投资团队在 DD 阶段快速厘清目标公司的实际控制架构,识别潜在的关联交易风险。",
detail: {
runSubtitle: "输入目标公司AI 多层穿透股权并生成结构图",
...sharedRun,
installPrompt: sharedInstall("股权结构穿透分析", "/equity-structure"),
},
},
{
id: "executive-background",
category: "bank",
title: "高管背景核查",
slug: "/executive-background",
body: "高管背景核查工具,默认对全体董监高(含董事/监事/独立董事/职工代表董事)+ 实际控制人 + 主要高管做完整画像。覆盖失信、限高、限出境、股权冻结、税收违法、控制企业、在外任职、历史合作等维度。",
detail: {
runSubtitle: "输入企业名称AI 对全体董监高与实控人做完整画像",
...sharedRun,
installPrompt: sharedInstall("高管背景核查", "/executive-background"),
},
},
{
id: "credit-due-diligence",
category: "bank",
title: "授信尽调报告",
slug: "/credit-due-diligence",
body: "信贷审批流程中的企业全维度尽调工具。授信前自动汇总目标企业工商登记、实控人信息、经营异常记录与司法风险状况,输出标准化授信尽调底稿,帮助信贷审批人在放款前全面掌握企业风险画像,支撑有据可查的授信决策。",
detail: {
runSubtitle: "输入借款企业AI 汇总风险并输出授信尽调底稿",
...sharedRun,
installPrompt: sharedInstall("授信尽调报告", "/credit-due-diligence"),
},
},
{
id: "credit-monitoring",
category: "bank",
title: "信贷风险定期监控",
slug: "/credit-monitoring",
body: "贷后管理中对存量借款客户的持续风险监控工具。定期对贷款企业进行风险体检,失信被执行、经营异常登记、法定代表人变更等关键风险信号一旦发生变化即时推送预警,帮助客户经理在风险暴露初期及时采取措施。",
detail: {
runSubtitle: "输入存量借款企业AI 定期体检并推送风险预警",
...sharedRun,
installPrompt: sharedInstall("信贷风险定期监控", "/credit-monitoring"),
},
},
{
id: "litigation-analysis",
category: "legal",
title: "诉讼风险评估",
slug: "/litigation-analysis",
body: "立案前的对手方诉讼风险评估工具。以「企业当前 × 企业历史 × 核心人员」三层穿透扫描对手方,整合裁判文书、立案信息、执行风险、限高/限出境与法代/实控人个人涉诉档案,基于 5-10 年历史数据做时间序列趋势分析,支撑立案、授信、投资决策。",
detail: {
runSubtitle: "输入对手方企业AI 三层穿透扫描并研判诉讼风险",
...sharedRun,
installPrompt: sharedInstall("诉讼风险评估", "/litigation-analysis"),
},
},
{
id: "counterparty-risk",
category: "bank",
title: "交易对手风险评估",
slug: "/counterparty-risk",
body: "贸易融资业务中对交易对手的多维风险评估工具。整合企业进出口信用等级、行政处罚记录、失信被执行情况与经营异常信息,生成综合风险评分,帮助贸易金融团队在开证、议付等关键节点识别潜在违约风险。",
detail: {
runSubtitle: "输入交易对手AI 生成综合风险评分",
...sharedRun,
installPrompt: sharedInstall("交易对手风险评估", "/counterparty-risk"),
},
},
{
id: "strip-profile",
category: "invest",
title: "企业画像速览",
slug: "/strip-profile",
body: "投资前快速了解目标公司的轻量尽调工具。3 分钟生成一页纸企业画像,整合工商登记、核心风险信号与知识产权资产,以结构化方式呈现企业基本面,适用于 LP 推介前、项目初步筛选和内部立项汇报场景。",
detail: {
runSubtitle: "输入目标公司AI 3 分钟生成一页纸企业画像",
...sharedRun,
installPrompt: sharedInstall("企业画像速览", "/strip-profile"),
},
},
{
id: "ip-asset-inventory",
category: "invest",
title: "知产资产清单",
slug: "/ip-asset-inventory",
body: "科技型企业投资与并购前的知产资产核查工具。全量输出目标企业专利、商标、软件著作权的数量与有效性状态,评估技术壁垒深度与核心资产的法律稳定性,帮助投资团队在 DD 阶段准确判断企业的知产护城河。",
detail: {
runSubtitle: "输入目标企业AI 全量核查知产资产并评估有效性",
...sharedRun,
installPrompt: sharedInstall("知产资产清单", "/ip-asset-inventory"),
},
},
{
id: "supplier-annual-check",
category: "supply",
title: "供应商年度健康体检",
slug: "/supplier-annual-check",
body: "供应商年度评审的标准化核查工具。对核心供应商进行全面年度体检,一次性输出经营状态变化、资质证件到期情况、信用记录变更的结构化报告,帮助采购团队在年度供应商评审和续签决策中有充分的数据支撑。",
detail: {
runSubtitle: "输入核心供应商AI 输出年度健康体检报告",
...sharedRun,
installPrompt: sharedInstall("供应商年度健康体检", "/supplier-annual-check"),
},
},
];
export function getSkillById(id: string): Skill | undefined {
return skills.find((s) => s.id === id);
}
export function getAllSkillIds(): string[] {
return skills.map((s) => s.id);
}

View File

@@ -1647,4 +1647,753 @@
border-top: none; border-top: none;
box-shadow: inset 0 1px 0 var(--color-hairline); box-shadow: inset 0 1px 0 var(--color-hairline);
} }
.btn-sm {
min-height: 34px;
padding: 7px var(--spacing-lg);
font-size: 13px;
border-radius: var(--radius-sm);
}
.btn-outline:disabled {
color: var(--color-body-light);
background-color: var(--color-canvas-muted);
box-shadow: none;
cursor: not-allowed;
opacity: 0.7;
}
.btn-outline:disabled:hover {
transform: none;
background-color: var(--color-canvas-muted);
box-shadow: none;
}
.skill-marketplace {
display: flex;
flex-direction: column;
gap: var(--spacing-3xl);
}
.skill-marketplace__toolbar {
display: flex;
flex-direction: column;
gap: var(--spacing-xl);
align-items: flex-start;
}
@media (width >= 768px) {
.skill-marketplace__toolbar {
flex-direction: row;
align-items: center;
justify-content: space-between;
}
}
.skill-marketplace__categories {
display: flex;
flex-wrap: wrap;
gap: var(--spacing-sm);
}
.skill-category-chip {
display: inline-flex;
align-items: center;
gap: var(--spacing-xs);
font-family: var(--font-display);
font-size: 13px;
font-weight: 500;
line-height: 1;
color: var(--color-body);
background-color: var(--color-canvas);
border: 1px solid var(--color-hairline);
border-radius: var(--radius-full);
padding: 8px var(--spacing-lg);
cursor: pointer;
transition:
color 0.2s ease,
background-color 0.2s ease,
border-color 0.2s ease;
}
.skill-category-chip:hover {
color: var(--color-accent-ink);
border-color: var(--color-accent-border);
}
.skill-category-chip--active {
color: var(--color-on-primary);
background-color: var(--color-ink);
border-color: var(--color-ink);
}
.skill-category-chip--active:hover {
color: var(--color-on-primary);
}
.skill-category-chip__emoji {
font-size: 14px;
line-height: 1;
}
.skill-category-chip__count {
font-family: var(--font-mono-caps);
font-size: 11px;
font-weight: 500;
color: inherit;
opacity: 0.7;
margin-left: var(--spacing-xxs);
}
.skill-marketplace__search {
width: 100%;
max-width: 280px;
}
@media (width < 768px) {
.skill-marketplace__search {
max-width: 100%;
}
}
.skill-search-input {
width: 100%;
font-family: var(--font-display);
font-size: 14px;
color: var(--color-ink);
background-color: var(--color-canvas);
border: 1px solid var(--color-hairline);
border-radius: var(--radius-md);
padding: 9px var(--spacing-lg);
min-height: 38px;
box-shadow: var(--shadow-field);
transition:
border-color 0.2s ease,
box-shadow 0.2s ease;
}
.skill-search-input::placeholder {
color: var(--color-body-light);
}
.skill-search-input:focus {
outline: none;
border-color: var(--color-accent-border);
box-shadow: var(--shadow-field-focus);
}
.skill-marketplace__grid {
display: grid;
gap: var(--spacing-xl);
grid-template-columns: 1fr;
}
@media (width >= 768px) {
.skill-marketplace__grid {
grid-template-columns: repeat(2, 1fr);
}
}
@media (width >= 1024px) {
.skill-marketplace__grid {
grid-template-columns: repeat(3, 1fr);
}
}
.skill-marketplace__empty {
text-align: center;
padding: var(--spacing-5xl) var(--spacing-lg);
}
.skill-card {
position: relative;
display: flex;
flex-direction: column;
gap: var(--spacing-lg);
height: 100%;
border-radius: var(--radius-md);
padding: var(--spacing-2xl);
background-color: var(--color-canvas);
color: var(--color-ink);
border: 1px solid var(--color-hairline);
box-shadow: var(--shadow-surface);
transition: box-shadow 0.2s ease, transform 0.2s ease;
}
.skill-card:hover {
box-shadow: var(--shadow-surface-hover);
transform: translateY(-2px);
}
@media (prefers-reduced-motion: reduce) {
.skill-card:hover {
transform: none;
}
}
.skill-card__head {
display: flex;
flex-direction: column;
gap: var(--spacing-xs);
}
.skill-card__title {
font-size: 18px;
line-height: 1.35;
}
.skill-card__slug {
font-family: var(--font-mono-caps);
font-size: 11px;
font-weight: 500;
line-height: 1;
letter-spacing: 0.04em;
color: var(--color-accent-soft);
background-color: var(--color-accent-wash);
border-radius: var(--radius-xs);
padding: 4px var(--spacing-sm);
align-self: flex-start;
}
.skill-card__body {
font-size: 13.5px;
line-height: 1.65;
flex: 1;
display: -webkit-box;
-webkit-line-clamp: 4;
line-clamp: 4;
-webkit-box-orient: vertical;
overflow: hidden;
}
.skill-card__cta {
display: flex;
flex-wrap: wrap;
gap: var(--spacing-sm);
margin-top: auto;
padding-top: var(--spacing-sm);
}
/* ===== 怎么使用 SKILL ===== */
.skill-howto {
display: flex;
flex-direction: column;
gap: var(--spacing-3xl);
}
.skill-howto__head {
display: flex;
flex-direction: column;
gap: var(--spacing-md);
max-width: 720px;
}
.skill-howto__title {
font-size: clamp(24px, 3vw, 34px);
line-height: 1.2;
}
.skill-howto__subtitle {
color: var(--color-body);
}
.skill-howto__layout {
display: grid;
gap: var(--spacing-3xl);
grid-template-columns: 1fr;
align-items: start;
}
@media (width >= 960px) {
.skill-howto__layout {
grid-template-columns: 1fr 1fr;
gap: var(--spacing-4xl);
}
}
.skill-howto__steps {
display: flex;
flex-direction: column;
gap: var(--spacing-2xl);
margin: 0;
padding: 0;
list-style: none;
}
.skill-howto-step {
display: flex;
gap: var(--spacing-lg);
}
.skill-howto-step__no {
font-family: var(--font-mono-caps);
font-size: 12px;
font-weight: 500;
line-height: 1;
letter-spacing: 0.06em;
color: var(--color-accent-ink);
background-color: var(--color-accent-wash);
border-radius: var(--radius-full);
width: 32px;
height: 32px;
flex-shrink: 0;
display: inline-flex;
align-items: center;
justify-content: center;
}
.skill-howto-step__copy {
display: flex;
flex-direction: column;
gap: var(--spacing-xs);
padding-top: 3px;
}
.skill-howto-step__title {
font-size: 15px;
}
.skill-howto-step__desc {
font-size: 13.5px;
line-height: 1.65;
}
.skill-howto-config {
display: flex;
flex-direction: column;
gap: var(--spacing-lg);
}
.skill-howto-config__intro {
font-size: 13.5px;
line-height: 1.65;
margin: 0;
}
/* ===== 可折叠代码面板(仿企查查) ===== */
.skill-code-block {
border: 1px solid var(--color-hairline);
border-radius: var(--radius-md);
background-color: var(--color-canvas-muted);
overflow: hidden;
box-shadow: var(--shadow-field);
}
.skill-code-block__header {
display: flex;
align-items: center;
justify-content: space-between;
gap: var(--spacing-md);
padding: var(--spacing-sm) var(--spacing-md);
background-color: var(--color-canvas);
border-bottom: 1px solid var(--color-hairline);
}
.skill-code-block__filename {
display: inline-flex;
align-items: center;
gap: var(--spacing-sm);
font-family: var(--font-mono-caps);
font-size: 12px;
font-weight: 500;
letter-spacing: 0.04em;
color: var(--color-ink-soft);
}
.skill-code-block__dot {
width: 8px;
height: 8px;
border-radius: var(--radius-full);
background-color: var(--color-accent-ink);
flex-shrink: 0;
}
.skill-code-block__actions {
display: inline-flex;
align-items: center;
gap: var(--spacing-xs);
}
.skill-code-block__btn {
display: inline-flex;
align-items: center;
gap: var(--spacing-xs);
font-family: var(--font-display);
font-size: 12px;
font-weight: 500;
line-height: 1;
color: var(--color-body);
background-color: transparent;
border: 1px solid transparent;
border-radius: var(--radius-sm);
padding: 6px var(--spacing-sm);
cursor: pointer;
transition:
color 0.2s ease,
background-color 0.2s ease,
border-color 0.2s ease;
}
.skill-code-block__btn:hover {
color: var(--color-accent-ink);
background-color: var(--color-accent-wash);
}
.skill-code-block__btn--toggle {
color: var(--color-accent-ink);
}
.skill-code-block__chevron {
transition: transform 0.2s ease;
}
.skill-code-block[data-open="true"] .skill-code-block__chevron {
transform: rotate(180deg);
}
.skill-code-block__pre {
margin: 0;
padding: var(--spacing-lg) var(--spacing-md);
overflow-x: auto;
font-family: var(--font-mono-caps);
font-size: 12.5px;
line-height: 1.75;
color: var(--color-ink-soft);
}
.skill-code-block__pre code {
font-family: inherit;
white-space: pre;
}
.skill-howto-config__apps {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: var(--spacing-sm);
margin: 0;
}
.skill-howto-config__apps-label {
font-family: var(--font-mono-caps);
font-size: 11px;
letter-spacing: 0.06em;
color: var(--color-body-light);
}
.skill-howto-config__app {
font-size: 12px;
color: var(--color-ink-soft);
background-color: var(--color-canvas);
border: 1px solid var(--color-hairline);
border-radius: var(--radius-full);
padding: 4px var(--spacing-md);
}
.skill-howto-config__soon {
margin: 0;
}
/* ===== SKILL 详情页 ===== */
.skill-detail {
display: flex;
flex-direction: column;
gap: var(--spacing-2xl);
max-width: 880px;
margin: 0 auto;
width: 100%;
}
.skill-detail__crumbs ol {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: var(--spacing-xs);
list-style: none;
margin: 0;
padding: 0;
font-family: var(--font-mono-caps);
font-size: 12px;
letter-spacing: 0.08em;
color: var(--color-body-light);
text-transform: uppercase;
}
.skill-detail__crumbs a {
color: var(--color-body-light);
text-decoration: none;
transition: color 0.2s ease;
}
.skill-detail__crumbs a:hover {
color: var(--color-accent-ink);
}
.skill-detail__crumbs li[aria-current="page"] {
color: var(--color-ink);
}
.skill-detail__head {
display: flex;
flex-direction: column;
gap: var(--spacing-md);
}
.skill-detail__title {
margin: 0;
}
.skill-detail__lead {
margin: 0;
}
.skill-detail__tabs {
display: flex;
gap: var(--spacing-xs);
border-bottom: 1px solid var(--color-hairline);
padding: 0;
}
.skill-detail__tab {
font-family: var(--font-display);
font-size: 15px;
font-weight: 500;
color: var(--color-body-light);
background: transparent;
border: none;
border-bottom: 2px solid transparent;
padding: var(--spacing-sm) var(--spacing-lg);
margin-bottom: -1px;
cursor: pointer;
transition:
color 0.2s ease,
border-color 0.2s ease;
}
.skill-detail__tab:hover {
color: var(--color-ink-soft);
}
.skill-detail__tab--active {
color: var(--color-accent-ink);
border-bottom-color: var(--color-accent-ink);
}
.skill-detail__panel {
min-height: 320px;
}
/* 在线运行 */
.skill-run {
display: flex;
flex-direction: column;
gap: var(--spacing-xl);
}
.skill-run__head {
display: flex;
flex-direction: column;
gap: var(--spacing-xs);
}
.skill-run__title {
margin: 0;
}
.skill-run__hint {
margin: 0;
}
.skill-run__field {
position: relative;
display: flex;
flex-direction: column;
gap: var(--spacing-xs);
}
.skill-run__label {
margin: 0;
}
.skill-run__input {
font-family: var(--font-display);
font-size: 15px;
color: var(--color-ink);
background-color: var(--color-canvas);
border: 1px solid var(--color-hairline);
border-radius: var(--radius-md);
padding: 14px var(--spacing-xl);
padding-right: 44px;
box-shadow: var(--shadow-field);
transition:
border-color 0.2s ease,
box-shadow 0.2s ease;
outline: none;
width: 100%;
}
.skill-run__input:focus {
border-color: var(--color-accent-ink);
box-shadow: var(--shadow-field-focus);
}
.skill-run__clear {
position: absolute;
right: var(--spacing-sm);
bottom: 9px;
width: 28px;
height: 28px;
display: inline-flex;
align-items: center;
justify-content: center;
font-size: 18px;
line-height: 1;
color: var(--color-body-light);
background: transparent;
border: none;
border-radius: 50%;
cursor: pointer;
transition: color 0.2s ease, background-color 0.2s ease;
}
.skill-run__clear:hover {
color: var(--color-ink);
background-color: var(--color-canvas-muted);
}
.skill-run__go {
align-self: flex-start;
}
.skill-run__note {
margin: 0;
}
/* 安装方式 */
.skill-install {
display: flex;
flex-direction: column;
gap: var(--spacing-3xl);
}
.skill-install__step {
display: flex;
flex-direction: column;
gap: var(--spacing-md);
}
.skill-install__title,
.skill-install__subtitle {
margin: 0;
}
.skill-install__desc {
margin: 0;
}
.skill-code-block__pre--text {
white-space: pre-wrap;
word-break: break-word;
}
/* 示例报告 */
.skill-example {
display: flex;
flex-direction: column;
gap: var(--spacing-xl);
}
.skill-example__content {
display: flex;
flex-direction: column;
gap: var(--spacing-2xl);
}
.skill-example__head {
display: flex;
flex-direction: column;
gap: var(--spacing-xs);
}
.skill-example__title {
margin: 0;
}
.skill-example__sections {
display: flex;
flex-direction: column;
gap: var(--spacing-xl);
}
.skill-example__section {
display: flex;
flex-direction: column;
gap: var(--spacing-sm);
padding-bottom: var(--spacing-xl);
border-bottom: 1px solid var(--color-hairline);
}
.skill-example__section:last-child {
border-bottom: none;
padding-bottom: 0;
}
.skill-example__section-title {
margin: 0;
}
.skill-example__bullets {
margin: 0;
padding-left: var(--spacing-xl);
display: flex;
flex-direction: column;
gap: var(--spacing-xs);
}
.skill-example__bullets li {
color: var(--color-body);
font-size: 15px;
line-height: 1.6;
}
.skill-example__empty {
display: flex;
flex-direction: column;
align-items: flex-start;
gap: var(--spacing-lg);
padding: var(--spacing-2xl);
background-color: var(--color-canvas-muted);
border-radius: var(--radius-md);
}
.skill-example__empty p {
margin: 0;
}
/* 卡片标题链接 */
.skill-card__title-link {
color: inherit;
text-decoration: none;
transition: color 0.2s ease;
}
.skill-card__title-link:hover {
color: var(--color-accent-ink);
}
@media (max-width: 640px) {
.skill-detail {
gap: var(--spacing-xl);
}
.skill-detail__tab {
padding: var(--spacing-sm) var(--spacing-md);
font-size: 14px;
}
.skill-install {
gap: var(--spacing-2xl);
}
}
} }