修复动静态ip标题跳转问题 & 添加admin页面
This commit is contained in:
@@ -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"
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user