feat(public-frontend): 新增联系我们页面
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
@@ -1,10 +1,11 @@
|
|||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { Boxes, Search, PlusCircle, Code2, KeyRound } from "lucide-react";
|
import { Boxes, Search, PlusCircle, Code2, KeyRound, Headset } from "lucide-react";
|
||||||
import Home from "./components/Home";
|
import Home from "./components/Home";
|
||||||
import ProductView from "./components/ProductView";
|
import ProductView from "./components/ProductView";
|
||||||
import Contribute from "./components/Contribute";
|
import Contribute from "./components/Contribute";
|
||||||
import ApiDocs from "./components/ApiDocs";
|
import ApiDocs from "./components/ApiDocs";
|
||||||
import Account from "./components/Account";
|
import Account from "./components/Account";
|
||||||
|
import Contact from "./components/Contact";
|
||||||
import { api } from "./api";
|
import { api } from "./api";
|
||||||
|
|
||||||
type View =
|
type View =
|
||||||
@@ -12,13 +13,15 @@ type View =
|
|||||||
| { name: "product"; id: string }
|
| { name: "product"; id: string }
|
||||||
| { name: "contribute" }
|
| { name: "contribute" }
|
||||||
| { name: "api" }
|
| { name: "api" }
|
||||||
| { name: "account" };
|
| { name: "account" }
|
||||||
|
| { name: "contact" };
|
||||||
|
|
||||||
const NAV: { key: View["name"]; label: string; icon: typeof Search }[] = [
|
const NAV: { key: View["name"]; label: string; icon: typeof Search }[] = [
|
||||||
{ key: "home", label: "检索", icon: Search },
|
{ key: "home", label: "检索", icon: Search },
|
||||||
{ key: "contribute", label: "贡献档案", icon: PlusCircle },
|
{ key: "contribute", label: "贡献档案", icon: PlusCircle },
|
||||||
{ key: "api", label: "API", icon: Code2 },
|
{ key: "api", label: "API", icon: Code2 },
|
||||||
{ key: "account", label: "API 密钥", icon: KeyRound },
|
{ key: "account", label: "API 密钥", icon: KeyRound },
|
||||||
|
{ key: "contact", label: "联系我们", icon: Headset },
|
||||||
];
|
];
|
||||||
|
|
||||||
export default function App() {
|
export default function App() {
|
||||||
@@ -88,6 +91,7 @@ export default function App() {
|
|||||||
)}
|
)}
|
||||||
{view.name === "api" && <ApiDocs onRegister={() => setView({ name: "account" })} />}
|
{view.name === "api" && <ApiDocs onRegister={() => setView({ name: "account" })} />}
|
||||||
{view.name === "account" && <Account />}
|
{view.name === "account" && <Account />}
|
||||||
|
{view.name === "contact" && <Contact />}
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<footer className="border-t border-gray-200/70 bg-white/60">
|
<footer className="border-t border-gray-200/70 bg-white/60">
|
||||||
@@ -109,6 +113,12 @@ export default function App() {
|
|||||||
>
|
>
|
||||||
API 调用说明
|
API 调用说明
|
||||||
</button>
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => setView({ name: "contact" })}
|
||||||
|
className="ml-1 text-brand-600 font-medium hover:underline"
|
||||||
|
>
|
||||||
|
联系我们
|
||||||
|
</button>
|
||||||
</p>
|
</p>
|
||||||
<div className="mt-3">
|
<div className="mt-3">
|
||||||
<a
|
<a
|
||||||
|
|||||||
@@ -0,0 +1,113 @@
|
|||||||
|
import { useState } from "react";
|
||||||
|
import { Phone, Mail, Globe, MessageSquare, Copy, Check, Headset } from "lucide-react";
|
||||||
|
|
||||||
|
type Channel = {
|
||||||
|
key: string;
|
||||||
|
icon: typeof Phone;
|
||||||
|
label: string;
|
||||||
|
value: string;
|
||||||
|
href?: string;
|
||||||
|
copy: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const CHANNELS: Channel[] = [
|
||||||
|
{
|
||||||
|
key: "phone",
|
||||||
|
icon: Phone,
|
||||||
|
label: "电话",
|
||||||
|
value: "188 6595 7520",
|
||||||
|
href: "tel:18865957520",
|
||||||
|
copy: "18865957520",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "email",
|
||||||
|
icon: Mail,
|
||||||
|
label: "邮箱",
|
||||||
|
value: "1115084741@qq.com",
|
||||||
|
href: "mailto:1115084741@qq.com",
|
||||||
|
copy: "1115084741@qq.com",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "site",
|
||||||
|
icon: Globe,
|
||||||
|
label: "官网",
|
||||||
|
value: "www.wenyaoyu.com",
|
||||||
|
href: "https://www.wenyaoyu.com",
|
||||||
|
copy: "https://www.wenyaoyu.com",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "wechat",
|
||||||
|
icon: MessageSquare,
|
||||||
|
label: "微信",
|
||||||
|
value: "s-b-m-y",
|
||||||
|
copy: "s-b-m-y",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export default function Contact() {
|
||||||
|
const [copied, setCopied] = useState<string | null>(null);
|
||||||
|
|
||||||
|
async function copy(channel: Channel) {
|
||||||
|
try {
|
||||||
|
await navigator.clipboard.writeText(channel.copy);
|
||||||
|
setCopied(channel.key);
|
||||||
|
setTimeout(() => setCopied((k) => (k === channel.key ? null : k)), 1500);
|
||||||
|
} catch {
|
||||||
|
/* clipboard unavailable */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="max-w-3xl mx-auto animate-fade-up">
|
||||||
|
<div className="text-center">
|
||||||
|
<span className="mx-auto grid h-14 w-14 place-items-center rounded-2xl bg-gradient-to-br from-brand-500 to-brand-700 text-white shadow-glow">
|
||||||
|
<Headset className="w-7 h-7" />
|
||||||
|
</span>
|
||||||
|
<h1 className="mt-4 text-2xl font-semibold tracking-tight text-gray-900">联系我们</h1>
|
||||||
|
<p className="mx-auto mt-2 max-w-xl text-sm text-gray-500">
|
||||||
|
欢迎交流与对接合作。无论是数据接入、商务合作还是问题反馈,都可以通过以下任一方式联系我们。
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mt-8 grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||||
|
{CHANNELS.map((c) => {
|
||||||
|
const Icon = c.icon;
|
||||||
|
return (
|
||||||
|
<div key={c.key} className="card p-5 flex items-center gap-4">
|
||||||
|
<span className="grid h-11 w-11 shrink-0 place-items-center rounded-xl bg-brand-50 text-brand-600">
|
||||||
|
<Icon className="w-5 h-5" />
|
||||||
|
</span>
|
||||||
|
<div className="min-w-0 flex-1">
|
||||||
|
<div className="text-xs font-medium text-gray-400">{c.label}</div>
|
||||||
|
{c.href ? (
|
||||||
|
<a
|
||||||
|
href={c.href}
|
||||||
|
target={c.key === "site" ? "_blank" : undefined}
|
||||||
|
rel={c.key === "site" ? "noreferrer" : undefined}
|
||||||
|
className="block truncate font-medium text-gray-800 hover:text-brand-600 hover:underline"
|
||||||
|
>
|
||||||
|
{c.value}
|
||||||
|
</a>
|
||||||
|
) : (
|
||||||
|
<div className="truncate font-medium text-gray-800">{c.value}</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => copy(c)}
|
||||||
|
title="复制"
|
||||||
|
className="shrink-0 grid h-9 w-9 place-items-center rounded-lg border border-gray-200 text-gray-400 transition hover:bg-gray-50 hover:text-brand-600"
|
||||||
|
>
|
||||||
|
{copied === c.key ? (
|
||||||
|
<Check className="w-4 h-4 text-brand-600" />
|
||||||
|
) : (
|
||||||
|
<Copy className="w-4 h-4" />
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user