diff --git a/src/admin/channels/page.tsx b/src/admin/channels/page.tsx index 91d8a0d..14ccbd7 100644 --- a/src/admin/channels/page.tsx +++ b/src/admin/channels/page.tsx @@ -116,6 +116,11 @@ export default function ChannelsPage() { const [page, setPage] = useState(1) const [selection, setSelection] = useState>({}) + const selectedAccounts = useMemo( + () => data.filter(item => selection[String(item.Id)]), + [data, selection], + ) + const selectedProduct = useMemo( () => products.find(item => String(item.Product.Id) === query.productId) ?? @@ -207,7 +212,10 @@ export default function ChannelsPage() { 全部产品 {products.map(item => ( - + {item.Product.Name} ))} @@ -256,7 +264,25 @@ export default function ChannelsPage() {
diff --git a/src/api/shuidi.ts b/src/api/shuidi.ts new file mode 100644 index 0000000..79285db --- /dev/null +++ b/src/api/shuidi.ts @@ -0,0 +1,84 @@ +import type { ApiResponse } from "@/lib/api" +import type { + ShuidiGame, + ShuidiPackageList, + ShuidiRegion, +} from "@/lib/models/shuidi" +import { phpFetch, phpGet, phpPost } from "./http" + +// ==================== 水滴静态/固态(jinyou) ==================== +// - 游戏列表:POST /jinyou/api/gameList(不带 cookie) +// - 套餐列表:POST /jinyou/Jinyoujt/getPackage?type=1(1:静态 2:固态),需带 cookie +// - 线路列表:GET /jinyou/api/regionSurplusOuts?product_id=&is_unshar=&game_id=(不带 cookie) + +type JinyouResult = { + code: number + msg?: string + data?: T +} + +export async function fetchShuidiGameList(): Promise< + ApiResponse +> { + const res = await phpFetch>( + "POST", + "/jinyou/api/gameList", + ) + if (!res.success) return res + const data = res.data?.data + return { + success: true, + data: Array.isArray(data) ? data : [], + } +} + +export async function fetchShuidiPackages( + type: 1 | 2 = 1, +): Promise> { + const res = await phpPost>( + `/jinyou/Jinyoujt/getPackage?type=${type}`, + { cookie: document.cookie }, + ) + if (!res.success) return res + const data = res.data?.data + if (!data || typeof data !== "object") { + return { success: true, data: { game: {}, line: {} } } + } + return { + success: true, + data: { + game: data.game ?? {}, + line: data.line ?? {}, + }, + } +} + +export async function fetchShuidiRegions(params: { + product_id: number + is_unshar: 0 | 1 + game_id: number | "" +}): Promise> { + const query = new URLSearchParams({ + product_id: String(params.product_id), + is_unshar: String(params.is_unshar), + game_id: String(params.game_id), + }) + const res = await phpGet>( + `/jinyou/api/regionSurplusOuts?${query.toString()}`, + ) + if (!res.success) return res + const region = res.data?.data?.region + if (!region || typeof region !== "object") { + return { + success: true, + data: { province_surplus_outs: {} }, + } + } + return { + success: true, + data: { + surplus_out_total: region.surplus_out_total, + province_surplus_outs: region.province_surplus_outs ?? {}, + }, + } +} diff --git a/src/home/product/buy.tsx b/src/home/product/buy.tsx index 3c1bc18..8701294 100644 --- a/src/home/product/buy.tsx +++ b/src/home/product/buy.tsx @@ -247,6 +247,8 @@ export default function BuyPage() { PackageId: packageId, Pwd: pwd.trim(), ConnectCount: displayConnectCount, + OrderType: 1, + Price: product.price, CouponAmount: 0, CouponId: 0, OPayType: oPayType, diff --git a/src/home/product/buyStatic.tsx b/src/home/product/buyStatic.tsx index 5705428..84e383e 100644 --- a/src/home/product/buyStatic.tsx +++ b/src/home/product/buyStatic.tsx @@ -1,15 +1,26 @@ -import { useEffect, useState } from "react" +import { ChevronDownIcon } from "lucide-react" +import { Popover } from "radix-ui" +import { useCallback, useEffect, useMemo, useState } from "react" import { useLocation, useNavigate } from "react-router-dom" import { toast } from "sonner" -import { createOrder } from "@/api/order" +import { + fetchShuidiGameList, + fetchShuidiPackages, + fetchShuidiRegions, +} from "@/api/shuidi" import RealNameCheck from "@/components/realNameCheck" import { Button } from "@/components/ui/button" import { Input } from "@/components/ui/input" import { Label } from "@/components/ui/label" import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group" import Wrap from "@/components/wrap" -import type { CreateOrderRequest } from "@/lib/models/order" -import { clearPayPaid, PAY_PAID_KEY, saveOrderPay } from "@/lib/recharge" +import type { + ShuidiCity, + ShuidiGame, + ShuidiPackage, + ShuidiPackageList, + ShuidiRegion, +} from "@/lib/models/shuidi" import { cn } from "@/lib/utils" import alipay from "../_assets/alipay.svg" import balance from "../_assets/balance.svg" @@ -33,8 +44,90 @@ function randomDigits(length: number): string { return result } +// 静态产品 product_id 固定为 1;线路品质固定为优质尊享(等级 100) +const PRODUCT_ID = 1 +const LINE_LEVEL = "100" + +function ispLabel(isp: number): string { + switch (isp) { + case 0: + return "多线" + case 1: + return "电信" + case 2: + return "联通" + case 3: + return "移动" + default: + return "未知" + } +} + +// 单节点分配数量在 order_info.product_info 中的 key +function addrKey(city: ShuidiCity): string { + return `${city.province}-${city.city ?? ""}-${city.rid}-${LINE_LEVEL}` +} + +// 省市名 -> 拼音首字母,用于左侧字母导航 +const PROVINCE_PINYIN: Record = { + 北京: "B", + 天津: "T", + 上海: "S", + 重庆: "C", + 河北: "H", + 山西: "S", + 辽宁: "L", + 吉林: "J", + 黑龙江: "H", + 江苏: "J", + 浙江: "Z", + 安徽: "A", + 福建: "F", + 江西: "J", + 山东: "S", + 河南: "H", + 湖北: "H", + 湖南: "H", + 广东: "G", + 海南: "H", + 四川: "S", + 贵州: "G", + 云南: "Y", + 陕西: "S", + 甘肃: "G", + 青海: "Q", + 内蒙古: "N", + 广西: "G", + 西藏: "X", + 宁夏: "N", + 新疆: "X", + 台湾: "T", + 香港: "X", + 澳门: "A", +} + +function provinceLetter(name: string): string { + if (!name) return "" + if (PROVINCE_PINYIN[name]) return PROVINCE_PINYIN[name] + let short = name + for (const suffix of [ + "特别行政区", + "维吾尔自治区", + "壮族自治区", + "回族自治区", + "自治区", + "省", + "市", + ]) { + if (short.endsWith(suffix)) { + short = short.slice(0, -suffix.length) + break + } + } + return PROVINCE_PINYIN[short] ?? "" +} + type BuyStaticState = { - // packageId:待后端静态池套餐数据就绪后由 ShuidiPools 传入 pool?: { id?: string; title?: string; packageId?: number } } @@ -74,191 +167,102 @@ function ExclusiveTabs({ ) } -// ============ 运营商选择 ============ -function OperatorSelect({ +// ============ 可搜索项目选择(可搜索下拉) ============ +function GameSelect({ + games, value, onChange, + disabled, }: { - value: string - onChange: (val: string) => void + games: ShuidiGame[] + value: number + onChange: (id: number) => void + disabled?: boolean }) { - const operators = ["电信", "联通", "移动"] + const [open, setOpen] = useState(false) + const [keyword, setKeyword] = useState("") + const selected = games.find(g => g.id === value) + const kw = keyword.trim().toLowerCase() + const filtered = kw + ? games.filter(g => g.name.toLowerCase().includes(kw)) + : games return ( -
- {operators.map(op => ( + { + setOpen(o) + if (!o) setKeyword("") + }} + > + - ))} -
- ) -} - -function RegionGrid({ - selected, - onSelect, -}: { - selected: string - onSelect: (region: string) => void -}) { - // 模拟地区数据,实际应从接口获取 - const regions = [ - { id: "A", name: "郑州电信", stock: 20 }, - { id: "B", name: "郑州电信", stock: 20 }, - { id: "C", name: "天津", stock: 20 }, - { id: "D", name: "上海", stock: 20 }, - { id: "E", name: "安徽", stock: 20 }, - { id: "G", name: "山东", stock: 20 }, - { id: "H", name: "湖南", stock: 20 }, - { id: "I", name: "陕西", stock: 20 }, - ] - - // 按首字母分组 - const groupedRegions: Record = {} - const letterMap: Record = { - A: "A", - B: "B", - C: "C", - D: "D", - E: "E", - G: "G", - H: "H", - I: "I", - } - regions.forEach(r => { - const key = letterMap[r.id] || "其他" - if (!groupedRegions[key]) groupedRegions[key] = [] - groupedRegions[key].push(r) - }) - - return ( -
- {Object.entries(groupedRegions).map(([letter, items]) => ( -
- - {letter} + + {selected?.name ?? "-请选择项目-"} -
- {items.map(region => ( + + + + + + setKeyword(e.target.value)} + className="h-8 mb-2 text-sm" + /> +
+ + {filtered.map(game => ( ))} + {filtered.length === 0 && ( +

无匹配项目

+ )}
-
- ))} -
- ) -} - -// ============ 带宽选择 ============ -function BandwidthSelect({ - value, - onChange, -}: { - value: string - onChange: (val: string) => void -}) { - const options = ["2M", "4M", "10M", "20M"] - - return ( -
- {options.map(bw => ( - - ))} -
- ) -} - -// ============ 时长选择 ============ -function DurationSelect({ - value, - onChange, -}: { - value: string - onChange: (val: string) => void -}) { - const options = [ - { label: "测试", value: "test" }, - { label: "天卡", value: "day" }, - { label: "月卡", value: "month" }, - ] - - return ( -
- {options.map(opt => ( - - ))} -
- ) -} - -// ============ 项目选择下拉 ============ -function ProjectSelect({ - value, - onChange, -}: { - value: string - onChange: (val: string) => void -}) { - const projects = ["请选择", "项目A", "项目B", "项目C"] - - return ( - + + + ) } @@ -270,126 +274,279 @@ export default function BuyStaticPage() { const poolTitle = state.pool?.title || "多拨池" + // 三个后端接口数据:游戏列表 / 套餐列表 / 线路列表 + const [games, setGames] = useState([]) + const [packages, setPackages] = useState(null) + const [region, setRegion] = useState(null) + const [regionLoading, setRegionLoading] = useState(false) + const [type, setType] = useState<"project" | "line">("project") - const [operator, setOperator] = useState("电信") - const [project, setProject] = useState("请选择") - const [region, setRegion] = useState("") + const [gameId, setGameId] = useState(0) + + const [selectedProvinces, setSelectedProvinces] = useState([]) + const [allProvince, setAllProvince] = useState(true) + + // 地区搜索关键字:输入即时过滤(不区分大小写,匹配城市名/运营商) + const [regionSearch, setRegionSearch] = useState("") + + // 已选城市(购物车模型):key 为 addrKey,value 为城市数据 + 分配数量 + const [selectedCities, setSelectedCities] = useState< + Record + >({}) + const [cityCounts, setCityCounts] = useState>({}) + + const [bandwidth, setBandwidth] = useState("") + const [packageInfoList, setPackageInfoList] = useState([]) + const [packageId, setPackageId] = useState("") + const [buyNum, setBuyNum] = useState(1) + const [account, setAccount] = useState("") const [pwd, setPwd] = useState("") - const [bandwidth, setBandwidth] = useState("2M") - const [duration, setDuration] = useState("test") const [payType, setPayType] = useState("100") const [submitting, setSubmitting] = useState(false) + // 初始加载:游戏列表 + 静态套餐(type=1),并生成随机账号密码 useEffect(() => { setAccount(randomChars(2) + randomDigits(4)) setPwd(randomDigits(3)) + + fetchShuidiGameList().then(res => { + if (res.success) setGames(res.data) + else toast.error(res.message) + }) + fetchShuidiPackages(1).then(res => { + if (res.success) setPackages(res.data) + else toast.error(res.message) + }) }, []) - // 支付窗口完成后自动跳转订单管理(与 buy.tsx 一致) + // 线路列表:跟随独享类型/项目变化重新拉取(与 juipnet select_game 逻辑一致) + const loadRegions = useCallback(async () => { + setRegionLoading(true) + const res = await fetchShuidiRegions({ + product_id: PRODUCT_ID, + is_unshar: type === "project" ? 0 : 1, + game_id: type === "project" ? gameId : "", + }) + if (res.success) { + setRegion(res.data) + const codes = Object.keys(res.data.province_surplus_outs) + setSelectedProvinces(codes) + setAllProvince(true) + setSelectedCities({}) + setCityCounts({}) + setRegionSearch("") + } else { + toast.error(res.message) + } + setRegionLoading(false) + }, [type, gameId]) + useEffect(() => { - const handler = (e: StorageEvent) => { - if (e.key !== PAY_PAID_KEY || !e.newValue) return - toast.success("支付成功") - clearPayPaid() - navigate("/admin/orders") + loadRegions() + }, [loadRegions]) + + // 切换项目独享/专线独享:重置套餐与分配选择(与 juipnet game()/line() 逻辑一致) + const handleTypeChange = (t: "project" | "line") => { + if (t === type) return + setType(t) + setBandwidth("") + setPackageInfoList([]) + setPackageId("") + setSelectedCities({}) + setCityCounts({}) + if (t === "line") setGameId(0) + } + + const provinceCodes = useMemo( + () => Object.keys(region?.province_surplus_outs ?? {}), + [region], + ) + + // 省市导航行:按拼音首字母 + 名称排序,附带字母索引 + const provinceRows = useMemo(() => { + if (!region) return [] + return provinceCodes + .map(code => ({ + code, + letter: provinceLetter( + region.province_surplus_outs[code]?.en_name ?? "", + ), + province: region.province_surplus_outs[code], + })) + .filter(row => row.province) + .sort( + (a, b) => + a.letter.localeCompare(b.letter) || + a.province.en_name.localeCompare(b.province.en_name, "zh"), + ) + }, [region, provinceCodes]) + + // 当前显示的城市列表:搜索时全局匹配(忽略省市筛选,匹配城市名/运营商); + // 否则按选中省市过滤 + const keyword = regionSearch.trim().toLowerCase() + const citys = useMemo(() => { + if (!region) return [] + if (keyword) { + return provinceCodes + .flatMap(code => region.province_surplus_outs[code]?.citys ?? []) + .filter(city => + `${city.name} ${ispLabel(city.isp_class)}` + .toLowerCase() + .includes(keyword), + ) } - window.addEventListener("storage", handler) - return () => window.removeEventListener("storage", handler) - }, [navigate]) + const selected = new Set(selectedProvinces) + return provinceCodes + .filter(code => allProvince || selected.has(code)) + .flatMap(code => region.province_surplus_outs[code]?.citys ?? []) + }, [region, provinceCodes, allProvince, selectedProvinces, keyword]) - const handleBack = () => navigate(-1) + // 左侧省市导航:搜索时按省市名过滤 + const visibleProvinceRows = useMemo( + () => + keyword + ? provinceRows.filter(r => + r.province.en_name.toLowerCase().includes(keyword), + ) + : provinceRows, + [provinceRows, keyword], + ) - // 确认支付:框架已就绪,套餐 ID(pool.packageId)待后端提供后即可真实下单 + const handleAllProvinceToggle = (checked: boolean) => { + setAllProvince(checked) + setSelectedProvinces(checked ? provinceCodes : []) + } + + // 全部状态下点击单个省市:自动切换为单选该省市,避免全选高亮过多 + const handleProvinceToggle = (code: string) => { + if (allProvince) { + setAllProvince(false) + setSelectedProvinces([code]) + return + } + const next = selectedProvinces.includes(code) + ? selectedProvinces.filter(c => c !== code) + : [...selectedProvinces, code] + setSelectedProvinces(next) + setAllProvince(next.length === provinceCodes.length) + } + + const applyRegionSearch = () => setRegionSearch(v => v.trim()) + + // ============ 城市选择(购物车模型) ============ + const toggleCity = (city: ShuidiCity) => { + const key = addrKey(city) + setSelectedCities(prev => { + const next = { ...prev } + if (next[key]) delete next[key] + else next[key] = city + return next + }) + setCityCounts(prev => { + const next = { ...prev } + if (next[key]) delete next[key] + else next[key] = 1 + return next + }) + } + + // 分配总数大于购买数量时自动上调购买数量 + const syncCityCounts = (next: Record) => { + setCityCounts(next) + const sum = Object.values(next).reduce((a, b) => a + b, 0) + if (sum > buyNum) setBuyNum(sum) + } + + const changeCityCount = (key: string, delta: number) => { + const city = selectedCities[key] + if (!city) return + const next = { + ...cityCounts, + [key]: Math.min(Math.max(1, (cityCounts[key] ?? 1) + delta), city.total), + } + syncCityCounts(next) + } + + const removeCity = (key: string) => { + setSelectedCities(prev => { + const next = { ...prev } + delete next[key] + return next + }) + setCityCounts(prev => { + const next = { ...prev } + delete next[key] + return next + }) + } + + // 带宽档位(test/2M/4M/10M/20M)来自套餐接口的 key + const bandwidthKeys = useMemo(() => { + const group = type === "project" ? packages?.game : packages?.line + return group ? Object.keys(group) : [] + }, [packages, type]) + + const handleBandwidthClick = (key: string) => { + setBandwidth(key) + setPackageId("") + const group = type === "project" ? packages?.game : packages?.line + setPackageInfoList(group?.[key] ?? []) + } + + const handleDurationClick = (item: ShuidiPackage) => { + setPackageId(item.Id) + } + + const selectedPackage = useMemo( + () => packageInfoList.find(p => p.Id === packageId), + [packageInfoList, packageId], + ) + + const totalPrice = Number(selectedPackage?.Price ?? 0) * buyNum + + const handleBuyNumChange = (value: number) => { + if (Number.isNaN(value) || value < 1) value = 1 + const sum = Object.values(cityCounts).reduce((a, b) => a + b, 0) + setBuyNum(Math.max(Math.floor(value), sum)) + } + + // 校验与 juipnet pay_page 一致;下单接口暂未提供,校验通过后提示联系客服 const handlePay = async () => { - const packageId = state.pool?.packageId + if ((region?.surplus_out_total ?? 0) < buyNum) { + toast.error("节点数量不足!") + return + } if (!packageId) { - toast.info("该产品暂未开放线上购买,请联系客服购买") + toast.error("请选择套餐!") return } - if (!account.trim() || !pwd.trim()) { - toast.error("账号和密码不能为空") + if (type === "project" && !gameId) { + toast.error("请选择项目!") return } - if (type === "project" && project === "请选择") { - toast.error("请选择项目") + if (!/^[0-9a-zA-Z]{4,10}$/.test(account.trim())) { + toast.error("账号为4-10位字母或数字!") return } - if (!region) { - toast.error("请选择地区") + if (!/^[0-9a-zA-Z]{1,10}$/.test(pwd.trim())) { + toast.error("密码为1-10位字母或数字!") return } - setSubmitting(true) try { - const oPayType = Number(payType) - const params: CreateOrderRequest = { - Account: account.trim(), - PackageId: packageId, - Pwd: pwd.trim(), - ConnectCount: 1, - CouponAmount: 0, - CouponId: 0, - UseAccountAmount: oPayType === 1, - OPayType: oPayType, - PayChannel: oPayType === 1 ? 70 : oPayType === 70 ? 30 : 50, - } - - const result = await createOrder(params) - if (!result.success) { - toast.error(result.message) - return - } - - const data = result.data - if (typeof data === "string") { - toast[data === "00" ? "success" : "error"]( - data === "00" ? "购买成功!" : data || "下单失败,请重试", - ) - return - } - - saveOrderPay({ - orderNo: data.OrderInfo.OrderNo, - amount: Number(data.OrderInfo.OtherPayAmount) || Number(getPrice()), - payChannel: oPayType === 1 ? 70 : oPayType === 70 ? 30 : 50, - payData: data.PayData, - }) - const win = window.open( - "/pay?mode=order", - "_blank", - "noopener,noreferrer", - ) - if (!win) { - navigate("/pay?mode=order") - return - } - toast.success("订单已生成,请在新窗口中完成支付") - } catch { - toast.error("网络错误,请重试") + toast.info("该产品暂未开放线上购买,请联系客服购买") } finally { setSubmitting(false) } } - // 根据类型计算价格 - const getPrice = () => { - const bwMap: Record = { - "2M": 10, - "4M": 20, - "10M": 40, - "20M": 80, - } - const durationMap: Record = { test: 0.1, day: 1, month: 10 } - const basePrice = bwMap[bandwidth] || 10 - const durationPrice = durationMap[duration] || 0.1 - return (basePrice * durationPrice).toFixed(2) - } - const [regionSearch, setRegionSearch] = useState("") + const handleBack = () => navigate(-1) + return ( -
+
{/* 标题 */}

@@ -399,7 +556,7 @@ export default function BuyStaticPage() { {/* Tab 切换 */}
- +
{/* 独享说明 */} @@ -425,21 +582,18 @@ export default function BuyStaticPage() { )}
- {/* 运营商 */} -
- - -
- - {/* 项目选择 */} + {/* 项目选择(可搜索下拉) */}
-
{/* 应付金额 */} @@ -540,7 +887,7 @@ export default function BuyStaticPage() { 应付金额: - ¥{getPrice()} + ¥{totalPrice.toFixed(2)}
diff --git a/src/lib/models/order.ts b/src/lib/models/order.ts index 501869d..2cddba4 100644 --- a/src/lib/models/order.ts +++ b/src/lib/models/order.ts @@ -45,6 +45,10 @@ export type CreateOrderRequest = { PackageId: number Pwd: string ConnectCount: number + // 订单类型:1新开 2批量新开 3续费 4批量续费 5退款 + OrderType: number + // 套餐单价(后端按 单价×连接数×账号数 计算总价) + Price: number // 优惠券减免金额 CouponAmount: number // 优惠券ID @@ -133,11 +137,13 @@ export const DEFAULT_FILTERS: OrderFilters = { eTime: "", } +// 与后端 Hncore.Pass.Vpn.Domain.OrderType 枚举保持一致 export const ORDER_TYPE_LABELS: Record = { 1: "新开", - 2: "续费", - 3: "批量新开", + 2: "批量新开", + 3: "续费", 4: "批量续费", + 5: "退款", } export const PACKAGE_OPTIONS = [ diff --git a/src/lib/models/shuidi.ts b/src/lib/models/shuidi.ts new file mode 100644 index 0000000..d1b289a --- /dev/null +++ b/src/lib/models/shuidi.ts @@ -0,0 +1,75 @@ +// ==================== 水滴静态:游戏列表 ==================== + +export type ShuidiGame = { + id: number + head?: number + name: string +} + +// ==================== 水滴静态:套餐列表 ==================== + +export type ShuidiPackage = { + Id: string + TenantId: string + ProductId: string + Name: string + PackageType: string + Title: string | null + Image: string + Status: string + Profile: string + Price: string + LinePrice: string + DayPrice: string + MinPrice: string + DayCount: string + OriginKey: string + OriginName: string + CreateTime: string + UpdateTime: string + DeleteTag: string + IsTest: string + package_name: string +} + +// 带宽档位(test / 2M / 4M / 10M / 20M)-> 该档位下的套餐(天卡/周卡/月卡...) +export type ShuidiPackageGroup = Record + +export type ShuidiPackageList = { + game: ShuidiPackageGroup + line: ShuidiPackageGroup +} + +// ==================== 水滴静态:线路列表 ==================== + +export type ShuidiLineLevel = { + total: number + line_level_en_name: string + province: number | string +} + +export type ShuidiCity = { + total: number + discount?: number + isp_class: number + name: string + province: string + rid: string + selected_num: number + city?: string | number + ping?: number +} + +export type ShuidiProvince = { + total: number + province_count: number + ping: number + line_levels: Record + en_name: string + citys: ShuidiCity[] +} + +export type ShuidiRegion = { + surplus_out_total?: number + province_surplus_outs: Record +} diff --git a/vite.config.ts b/vite.config.ts index 602e045..0e9b9fb 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -25,11 +25,28 @@ export default defineConfig({ target: "http://192.168.3.6:5000", bypass(req) { if (req.headers?.accept?.includes("text/html")) { - return "/index.html" + // 仅前端 SPA 路由回退 index.html;续费等后端老页面(rebuyindex/rebuy) + // 需要直达后端渲染 + const pathname = new URL(req.url ?? "", "http://localhost").pathname + const spaPaths = [ + "/product", + "/product/buy", + "/product/buy-static", + "/product/http", + "/product/routeros", + ] + if (spaPaths.includes(pathname)) { + return "/index.html" + } } }, }, "/script": "https://php-api.juip.com", + "/jinyou": { + target: "https://php-api.juip.com", + changeOrigin: true, + secure: false, + }, "/http": { target: "https://php-api.juip.com", changeOrigin: true,