Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ab7a964934 | |||
| ba36e3ff4c | |||
| d837dd38bf | |||
| 53ce705572 | |||
| 836ee73d73 | |||
| dbbad274b6 |
@@ -0,0 +1,2 @@
|
|||||||
|
DROP INDEX IF EXISTS idx_product_active_category;
|
||||||
|
DROP INDEX IF EXISTS idx_product_active_quality;
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
-- 检索优化(可扩展性路线图 阶段0):为最常见的"浏览"路径补部分/复合索引。
|
||||||
|
-- 搜索查询恒带 WHERE status = 'active';无关键词时按 quality_score DESC, name 排序。
|
||||||
|
-- 现有索引无法同时满足"过滤 active + 按 quality_score/name 排序",深翻页时需要对全部
|
||||||
|
-- active 行排序。下面的部分复合索引让规划器直接走索引顺序扫描,省掉排序、加速深翻页与
|
||||||
|
-- count(*)。索引只覆盖 active 行,体积更小。
|
||||||
|
|
||||||
|
-- 默认浏览(无关键词、无品类):ORDER BY quality_score DESC, name
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_product_active_quality
|
||||||
|
ON product (quality_score DESC, name)
|
||||||
|
WHERE status = 'active';
|
||||||
|
|
||||||
|
-- 品类内浏览:先按 category_id 收敛,再按 quality_score 排序
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_product_active_category
|
||||||
|
ON product (category_id, quality_score DESC)
|
||||||
|
WHERE status = 'active';
|
||||||
@@ -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() {
|
||||||
@@ -78,7 +81,6 @@ export default function App() {
|
|||||||
<Home
|
<Home
|
||||||
onOpen={(id) => setView({ name: "product", id })}
|
onOpen={(id) => setView({ name: "product", id })}
|
||||||
onContribute={() => setView({ name: "contribute" })}
|
onContribute={() => setView({ name: "contribute" })}
|
||||||
onApi={() => setView({ name: "api" })}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{view.name === "product" && (
|
{view.name === "product" && (
|
||||||
@@ -89,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">
|
||||||
@@ -110,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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -26,11 +26,9 @@ const FEATURES = [
|
|||||||
export default function Home({
|
export default function Home({
|
||||||
onOpen,
|
onOpen,
|
||||||
onContribute,
|
onContribute,
|
||||||
onApi,
|
|
||||||
}: {
|
}: {
|
||||||
onOpen: (id: string) => void;
|
onOpen: (id: string) => void;
|
||||||
onContribute: () => void;
|
onContribute: () => void;
|
||||||
onApi: () => void;
|
|
||||||
}) {
|
}) {
|
||||||
const [q, setQ] = useState("");
|
const [q, setQ] = useState("");
|
||||||
const [items, setItems] = useState<ProductSummary[]>([]);
|
const [items, setItems] = useState<ProductSummary[]>([]);
|
||||||
@@ -99,13 +97,6 @@ export default function Home({
|
|||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button
|
|
||||||
onClick={onApi}
|
|
||||||
className="mt-5 inline-flex items-center gap-1.5 text-sm font-medium text-brand-700 hover:underline"
|
|
||||||
>
|
|
||||||
<Code2 className="w-4 h-4" /> 开发者?查看 API 调用说明,免鉴权接入商品档案
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user