add
This commit is contained in:
20
src/app/(site)/reports/[skill]/not-found.tsx
Normal file
20
src/app/(site)/reports/[skill]/not-found.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
||||||
69
src/app/(site)/reports/[skill]/page.tsx
Normal file
69
src/app/(site)/reports/[skill]/page.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
250
src/components/skill-detail.tsx
Normal file
250
src/components/skill-detail.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
||||||
42
src/components/skill-example-content.tsx
Normal file
42
src/components/skill-example-content.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
||||||
119
src/components/skill-how-to-use.tsx
Normal file
119
src/components/skill-how-to-use.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
||||||
191
src/components/skill-marketplace.tsx
Normal file
191
src/components/skill-marketplace.tsx
Normal 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}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -188,6 +188,196 @@ export const site = {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
skillMarket: {
|
||||||
|
eyebrow: "产品",
|
||||||
|
title: "SKILL 广场",
|
||||||
|
lead: "27 个经过真实场景验证的 SKILL,覆盖银行、投资、法务、采购四大客群。配置海宇数科 MCP,3 分钟跑通首份尽调报告。",
|
||||||
|
howToUse: {
|
||||||
|
eyebrow: "使用指南",
|
||||||
|
title: "怎么使用 SKILL ?",
|
||||||
|
subtitle: "三步完成,在 AI 对话框里直接操作,不需要写代码",
|
||||||
|
steps: [
|
||||||
|
{
|
||||||
|
no: "01",
|
||||||
|
title: "配置海宇数科 MCP",
|
||||||
|
desc: "注册账号、获取 API Key、粘贴配置到你的 AI 工具。只需做一次,所有 SKILL 共用。",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
no: "02",
|
||||||
|
title: "发送给 AI 加载",
|
||||||
|
desc: "把下方配置原样发给你的 AI,AI 会自动加载对应客群的 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
289
src/content/skills.ts
Normal 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) =>
|
||||||
|
`将下面这段配置发给你的 AI,AI 会自动加载「${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);
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user