修复动静态ip标题跳转问题 & 添加admin页面

This commit is contained in:
Eamon
2026-08-18 13:33:22 +08:00
parent 1336a550fe
commit fc034dc971
35 changed files with 2516 additions and 524 deletions

View File

@@ -1,8 +1,13 @@
import { useEffect, useRef, useState } from "react"
import { useSearchParams } from "react-router-dom"
import { fetchProducts } from "@/api/product"
import {
fetchProducts,
fetchStaticProducts,
fetchWindowProducts,
} from "@/api/product"
import Wrap from "@/components/wrap"
import type { Tab } from "@/lib/models/product"
import type { ApiResponse } from "@/lib/api"
import type { ProductItem, Tab, TabSource } from "@/lib/models/product"
import { transformToTabs } from "@/lib/models/product"
import { cn } from "@/lib/utils"
import ProductGrid from "./productGrid"
@@ -13,11 +18,20 @@ import PurchaseInfo from "./purchaseInfo"
const TAB_ALIAS: Record<string, string> = {
short: "dynamic",
long: "dynamic",
http: "http",
tunnel: "dynamic",
exclusive: "static",
}
const TAB_SOURCES: {
id: string
label: string
fetch: () => Promise<ApiResponse<ProductItem[]>>
}[] = [
{ id: "dynamic", label: "动态独享IP", fetch: fetchProducts },
{ id: "static", label: "静态IP", fetch: fetchStaticProducts },
{ id: "window", label: "单窗口单IP", fetch: fetchWindowProducts },
]
export default function ProductPage() {
const [searchParams, setSearchParams] = useSearchParams()
const [tabs, setTabs] = useState<Tab[]>([])
@@ -29,19 +43,56 @@ export default function ProductPage() {
const initializedRef = useRef(false)
useEffect(() => {
fetchProducts().then(result => {
if (result.success) {
const newTabs = transformToTabs(result.data)
setTabs(newTabs)
if (!initializedRef.current && newTabs[0]?.brands[0]) {
setActiveBrandId(newTabs[0].brands[0].id)
initializedRef.current = true
let cancelled = false
const load = async () => {
const results = await Promise.allSettled(
TAB_SOURCES.map(source => source.fetch()),
)
console.log(results, "resultsresultsresults")
if (cancelled) return
const sources: TabSource[] = []
const failedLabels: string[] = []
TAB_SOURCES.forEach((source, index) => {
const result = results[index]
if (result?.status === "fulfilled" && result.value.success) {
sources.push({
id: source.id,
label: source.label,
data: result.value.data,
})
} else {
failedLabels.push(source.label)
}
} else {
setError(result.message)
})
if (sources.length === 0) {
setError("产品列表加载失败,请稍后重试")
setLoading(false)
return
}
if (failedLabels.length > 0) {
console.warn(`部分产品列表加载失败: ${failedLabels.join("、")}`)
}
const newTabs = transformToTabs(sources)
setTabs(newTabs)
if (!initializedRef.current && newTabs[0]?.brands[0]) {
setActiveBrandId(newTabs[0].brands[0].id)
initializedRef.current = true
}
setLoading(false)
})
}
load()
return () => {
cancelled = true
}
}, [])
const urlTab = searchParams.get("tab") || "dynamic"

View File

@@ -15,13 +15,13 @@ export default function ProductTabs({
}: ProductTabsProps) {
return (
<Tabs value={activeId} onValueChange={onTabChange} className="w-full">
<TabsList className="grid w-full grid-cols-3 bg-slate-100 rounded-xl h-14">
<TabsList className="grid w-full h-auto bg-transparent p-0 grid-cols-3">
{tabs.map(tab => (
<TabsTrigger
key={tab.id}
value={tab.id}
className={cn(
"w-full py-2 rounded-full text-sm font-medium transition-all cursor-pointer text-center outline-none border-0 ring-0",
"w-full h-auto py-2 rounded-full text-sm font-medium transition-all cursor-pointer text-center outline-none border-0 ring-0",
activeId === tab.id
? "bg-linear-to-r from-blue-500 to-cyan-400 text-white! shadow-none"
: "bg-gray-100 text-gray-600 hover:bg-gray-200",

View File

@@ -1,4 +1,7 @@
import { useState } from "react"
import { zodResolver } from "@hookform/resolvers/zod"
import { useEffect, useState } from "react"
import { Controller, useForm } from "react-hook-form"
import { z } from "zod"
import { Badge } from "@/components/ui/badge"
import { Button } from "@/components/ui/button"
import { Card, CardContent } from "@/components/ui/card"
@@ -9,6 +12,7 @@ import {
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog"
import { FieldError } from "@/components/ui/field"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
import { Separator } from "@/components/ui/separator"
@@ -18,6 +22,14 @@ import alipay from "../_assets/alipay.svg"
import balance from "../_assets/balance.svg"
import wechat from "../_assets/wechat.svg"
const shippingSchema = z.object({
name: z.string().trim().min(1, "请输入收货人"),
phone: z.string().regex(/^1\d{10}$/, "请输入正确的手机号"),
address: z.string().trim().min(1, "请输入地址"),
})
type ShippingValues = z.infer<typeof shippingSchema>
const PRODUCT_VARIANTS = [
{
id: 1,