diff --git a/new/wz-app/MIGRATION.md b/new/wz-app/MIGRATION.md index de1859c..64f412b 100644 --- a/new/wz-app/MIGRATION.md +++ b/new/wz-app/MIGRATION.md @@ -1,18 +1,37 @@ -# old C 端 → new/react-app 迁移清单 +# old C 端 → new/wz-app(Vite + React)迁移清单 + +> 权威路由注册见 [`src/App.tsx`](src/App.tsx)(`react-router`)。历史 uni 的 `pages.json` 仅作考古。 + +**说明(2026-04)**:堆叠子路由已从 `old/app/**/page.tsx` 全量迁入 `src/pages/**`(含带参路由 `useParams`)。**不再使用** `StackStubPage` / `ParamStubPage` 占位。 | 路由 | 来源(old) | 主要 wz-api | 状态 | |------|-------------|------------|------| -| `/` | `old/app/page.tsx` | `GET /api/app/streamers`、`GET /api/app/user/me` | 已像素对接 | -| `/login` | `old/app/login/page.tsx` | `POST /api/app/auth/login`、`/register`、`/user/me` | 已接 Bearer | -| `/moments` | `old/app/moments/page.tsx` | 本地演示数据 + 链到 `/live/:id` | 已迁入 | -| `/planet` | `old/app/planet/page.tsx` | 匹配/派对 UI 为本地 | 已迁入 | -| `/mall` | `old/app/mall/page.tsx` | 支付走前端 `pay()` 模拟 | 已迁入 | -| `/profile` | `old/app/profile/page.tsx` | `GET/POST` 认证同上 | 已迁入 | -| 其它 | — | 见下表子包 | `NotFound` 占位 | +| `/` | `old/app/page.tsx` | `GET /api/app/streamers`、`GET /api/app/user/me` | 已接入 | +| `/login` | `old/app/login/page.tsx` | `POST /api/app/auth/*`、`/user/me` | 已接 Bearer | +| `/moments` | `old/app/moments/page.tsx` | 见 hooks / 演示数据 | 已迁入 | +| `/planet` | `old/app/planet/page.tsx` | 本地 | 已迁入 | +| `/mall` | `old/app/mall/page.tsx` | 商城相关 API | 已迁入 | +| `/profile` | `old/app/profile/page.tsx` | 同上 | 已迁入 | +| `/orders`、`/messages`、`/settings`、`/profile/edit` | 对应 `old/app/*` | 见各页 | 已迁入 | +| 其余 `App.tsx` 独立堆叠路由 | 见下表与 `App.tsx` | 见 [`迁移-API对照表`](../../开发文档/玩值/迁移-API对照表.md) | **UI 已迁**;未接 API 处在页面或本表标 **TODO: API** | -**子包/深链**(以 `new/wz-app/src/pages.json` `subPackages` 为权威清单,对应 old 同路径页面,未迁路由一律落 `/*` 占位): +### 堆叠页组件速查(Vite) -- `subpkgs/stack/*`:pitch-deck、events-today、live、live-detail、cp、cp-detail、hotel、mall-*、orders、streamer-studio、recharge 等 -- 下一步:按业务优先级从 `old/app/**` 复制为 `src/pages/...` 并注册 `react-router` 路由,同时在对照表确认 ` /api/app/*` 或标 TODO +| 路由 | 组件 | +|------|------| +| `/cp`、`/cp/:id` | `cp/CpListPage`、`cp/CpDetailPage` | +| `/party/:id` | `PartyDetailPage` | +| `/course/:id` | `CourseDetailPage`(本地 `courseData` + `pay`) | +| `/guild`、`/guild/:id` | `guild/GuildListPage`、`guild/GuildDetailPage` | +| `/zone/:id` | `ZoneDetailPage` | +| `/star/:id` | `StarDetailPage` | +| `/chat/:id` | `ChatRoomPage`(`cp-*` 与普通群聊两态) | +| `/moments/:momentId` | `MomentsDetailPage` | +| `/moments/create` | `MomentsCreatePage` | +| `/mall/companion` 等子路径 | `mall/*Page` | +| `/mall/points/:game` | `mall/MallPointsGamePage` | +| `/streamer/audience`、`/streamer/studio` | `streamer/StreamerAudiencePage`、`StreamerStudioPage`(演示数据 / 本地态) | +| `/screen` | `ScreenPage`(C 端轻量壳,运营大屏见 wz-admin) | +| 其余优惠券、预订、帮助、今日活动、路演、充值、会员、足迹、装扮、服务与交易、酒店、陪练、创作者等 | 见 `src/pages` 下同名或目录 | -**完成度(主路径)**:五 Tab + 登录 + 请求层 ≈ **主里程碑 100%**;全站子页 ≈ 按上表子包行数续增。 +**完成度(主路径)**:五 Tab + 登录 + 直播 + 积分商城 + **计划内非 Tab 子页**均已挂真实页面;后续以 **接 wz-api 契约** 与动效/体验迭代为主。 diff --git a/new/wz-app/dist/index.html b/new/wz-app/dist/index.html index 1409270..4ecf822 100644 --- a/new/wz-app/dist/index.html +++ b/new/wz-app/dist/index.html @@ -11,8 +11,8 @@ rel="stylesheet" /> 玩值电竞 - WanZhi Esports - - + +
diff --git a/new/wz-app/package-lock.json b/new/wz-app/package-lock.json index a527b77..daec873 100644 --- a/new/wz-app/package-lock.json +++ b/new/wz-app/package-lock.json @@ -10,10 +10,12 @@ "dependencies": { "@radix-ui/react-dialog": "^1.1.15", "@radix-ui/react-slot": "^1.2.4", + "@radix-ui/react-switch": "^1.1.2", "@radix-ui/react-tabs": "^1.1.13", "@radix-ui/react-toast": "^1.2.15", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", + "framer-motion": "^12.23.24", "lucide-react": "^1.8.0", "react": "^19.2.5", "react-dom": "^19.2.5", @@ -1000,6 +1002,35 @@ } } }, + "node_modules/@radix-ui/react-switch": { + "version": "1.2.6", + "resolved": "https://registry.npmmirror.com/@radix-ui/react-switch/-/react-switch-1.2.6.tgz", + "integrity": "sha512-bByzr1+ep1zk4VubeEVViV592vu2lHE2BZY5OnzehZqOOgogN80+mNtCqPkhn2gklJqOpxWgPoYTSnhBCqpOXQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-previous": "1.1.1", + "@radix-ui/react-use-size": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-tabs": { "version": "1.1.13", "resolved": "https://registry.npmmirror.com/@radix-ui/react-tabs/-/react-tabs-1.1.13.tgz", @@ -1149,6 +1180,39 @@ } } }, + "node_modules/@radix-ui/react-use-previous": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/@radix-ui/react-use-previous/-/react-use-previous-1.1.1.tgz", + "integrity": "sha512-2dHfToCj/pzca2Ck724OZ5L0EVrr3eHRNsG/b3xQJLA2hZpVCS99bLAX+hm1IHXDEnzU6by5z/5MIY794/a8NQ==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-size": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/@radix-ui/react-use-size/-/react-use-size-1.1.1.tgz", + "integrity": "sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-visually-hidden": { "version": "1.2.3", "resolved": "https://registry.npmmirror.com/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.2.3.tgz", @@ -2720,6 +2784,33 @@ "dev": true, "license": "ISC" }, + "node_modules/framer-motion": { + "version": "12.38.0", + "resolved": "https://registry.npmmirror.com/framer-motion/-/framer-motion-12.38.0.tgz", + "integrity": "sha512-rFYkY/pigbcswl1XQSb7q424kSTQ8q6eAC+YUsSKooHQYuLdzdHjrt6uxUC+PRAO++q5IS7+TamgIw1AphxR+g==", + "license": "MIT", + "dependencies": { + "motion-dom": "^12.38.0", + "motion-utils": "^12.36.0", + "tslib": "^2.4.0" + }, + "peerDependencies": { + "@emotion/is-prop-valid": "*", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/is-prop-valid": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.3.tgz", @@ -3308,6 +3399,21 @@ "node": "*" } }, + "node_modules/motion-dom": { + "version": "12.38.0", + "resolved": "https://registry.npmmirror.com/motion-dom/-/motion-dom-12.38.0.tgz", + "integrity": "sha512-pdkHLD8QYRp8VfiNLb8xIBJis1byQ9gPT3Jnh2jqfFtAsWUA3dEepDlsWe/xMpO8McV+VdpKVcp+E+TGJEtOoA==", + "license": "MIT", + "dependencies": { + "motion-utils": "^12.36.0" + } + }, + "node_modules/motion-utils": { + "version": "12.36.0", + "resolved": "https://registry.npmmirror.com/motion-utils/-/motion-utils-12.36.0.tgz", + "integrity": "sha512-eHWisygbiwVvf6PZ1vhaHCLamvkSbPIeAYxWUuL3a2PD/TROgE7FvfHWTIH4vMl798QLfMw15nRqIaRDXTlYRg==", + "license": "MIT" + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz", diff --git a/new/wz-app/package.json b/new/wz-app/package.json index 7c23eeb..5f16834 100644 --- a/new/wz-app/package.json +++ b/new/wz-app/package.json @@ -12,10 +12,12 @@ "dependencies": { "@radix-ui/react-dialog": "^1.1.15", "@radix-ui/react-slot": "^1.2.4", + "@radix-ui/react-switch": "^1.1.2", "@radix-ui/react-tabs": "^1.1.13", "@radix-ui/react-toast": "^1.2.15", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", + "framer-motion": "^12.23.24", "lucide-react": "^1.8.0", "react": "^19.2.5", "react-dom": "^19.2.5", diff --git a/new/wz-app/src/App.tsx b/new/wz-app/src/App.tsx index ea005e6..9705047 100644 --- a/new/wz-app/src/App.tsx +++ b/new/wz-app/src/App.tsx @@ -13,9 +13,47 @@ import { ProfilePage } from "@/pages/ProfilePage" import { NotFoundPage } from "@/pages/NotFoundPage" import { LivePage } from "@/pages/LivePage" import { AuthSessionListener } from "@/components/auth/AuthSessionListener" -import { StackStubPage } from "@/pages/stack/StackStubPage" -import { ParamStubPage } from "@/pages/stack/ParamStubPage" import { LiveListPage } from "@/pages/stack/LiveListPage" +import { OrdersPage } from "@/pages/OrdersPage" +import { MessagesPage } from "@/pages/MessagesPage" +import { SettingsPage } from "@/pages/SettingsPage" +import { ProfileEditPage } from "@/pages/ProfileEditPage" +import { CouponsPage } from "@/pages/CouponsPage" +import { BookingPage } from "@/pages/BookingPage" +import { AccountHelpPage } from "@/pages/AccountHelpPage" +import { CustomerServicePage } from "@/pages/CustomerServicePage" +import { EventsTodayPage } from "@/pages/EventsTodayPage" +import { PitchDeckPage } from "@/pages/PitchDeckPage" +import { RechargePage } from "@/pages/RechargePage" +import { VipPage } from "@/pages/VipPage" +import { FootprintPage } from "@/pages/FootprintPage" +import { DressUpPage } from "@/pages/DressUpPage" +import { MallCompanionPage } from "@/pages/mall/MallCompanionPage" +import { MallGearPage } from "@/pages/mall/MallGearPage" +import { MallCoursesPage } from "@/pages/mall/MallCoursesPage" +import { MallPointsGamePage } from "@/pages/mall/MallPointsGamePage" +import { LevelingPage } from "@/pages/services/LevelingPage" +import { AccountServicesPage } from "@/pages/services/AccountServicesPage" +import { TransactionHistoryPage } from "@/pages/TransactionHistoryPage" +import { MyCoursesPage } from "@/pages/MyCoursesPage" +import { CreatorApplyPage } from "@/pages/CreatorApplyPage" +import { HotelPage } from "@/pages/HotelPage" +import { CoachListPage } from "@/pages/CoachListPage" +import { CoachDetailPage } from "@/pages/CoachDetailPage" +import { MomentsCreatePage } from "@/pages/MomentsCreatePage" +import { CpListPage } from "@/pages/cp/CpListPage" +import { CpDetailPage } from "@/pages/cp/CpDetailPage" +import { GuildListPage } from "@/pages/guild/GuildListPage" +import { GuildDetailPage } from "@/pages/guild/GuildDetailPage" +import { PartyDetailPage } from "@/pages/PartyDetailPage" +import { CourseDetailPage } from "@/pages/CourseDetailPage" +import { ZoneDetailPage } from "@/pages/ZoneDetailPage" +import { StarDetailPage } from "@/pages/StarDetailPage" +import { ChatRoomPage } from "@/pages/ChatRoomPage" +import { MomentsDetailPage } from "@/pages/MomentsDetailPage" +import { StreamerAudiencePage } from "@/pages/streamer/StreamerAudiencePage" +import { StreamerStudioPage } from "@/pages/streamer/StreamerStudioPage" +import { ScreenPage } from "@/pages/ScreenPage" /** 带底栏 Tab 的主区(与全屏/堆叠子页互斥) */ function TabLayout() { @@ -46,54 +84,49 @@ export default function App() { } /> }> - } /> + } /> } /> } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - - } - /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> }> } /> } /> diff --git a/new/wz-app/src/components/ui/switch.tsx b/new/wz-app/src/components/ui/switch.tsx new file mode 100644 index 0000000..759f581 --- /dev/null +++ b/new/wz-app/src/components/ui/switch.tsx @@ -0,0 +1,26 @@ +import * as React from "react" +import * as SwitchPrimitive from "@radix-ui/react-switch" + +import { cn } from "@/lib/utils" + +function Switch({ className, ...props }: React.ComponentProps) { + return ( + + + + ) +} + +export { Switch } diff --git a/new/wz-app/src/pages/AccountHelpPage.tsx b/new/wz-app/src/pages/AccountHelpPage.tsx new file mode 100644 index 0000000..465382f --- /dev/null +++ b/new/wz-app/src/pages/AccountHelpPage.tsx @@ -0,0 +1,69 @@ +import { useNavigate } from "react-router-dom" +import { ArrowLeft, Shield, Phone, HelpCircle, ChevronRight, Lock, Mail, FileText, MessageSquare } from "lucide-react" + +export function AccountHelpPage() { + const navigate = useNavigate() + + const sections = [ + { + title: "账号安全", + items: [ + { icon: Lock, label: "修改密码", desc: "定期修改密码保障账号安全" }, + { icon: Phone, label: "绑定手机", desc: "已绑定: 138****8888" }, + { icon: Mail, label: "绑定邮箱", desc: "未绑定" }, + { icon: Shield, label: "实名认证", desc: "已认证" }, + ], + }, + { + title: "帮助中心", + items: [ + { icon: HelpCircle, label: "常见问题", desc: "查看热门问题解答" }, + { icon: FileText, label: "用户协议", desc: "查看服务条款" }, + { icon: FileText, label: "隐私政策", desc: "了解隐私保护" }, + { icon: MessageSquare, label: "意见反馈", desc: "告诉我们您的建议" }, + ], + }, + ] + + return ( +
+
+ +

账号与帮助

+
+ +
+ {sections.map((section, sectionIndex) => ( +
+

{section.title}

+
+ {section.items.map((item, itemIndex) => ( + + ))} +
+
+ ))} + +
+

玩值电竞 v1.0.0

+

© 2025 玩值电竞 All Rights Reserved

+
+
+
+ ) +} diff --git a/new/wz-app/src/pages/BookingPage.tsx b/new/wz-app/src/pages/BookingPage.tsx new file mode 100644 index 0000000..aaa0af2 --- /dev/null +++ b/new/wz-app/src/pages/BookingPage.tsx @@ -0,0 +1,226 @@ +import { useState, Suspense } from "react" +import { useSearchParams, useNavigate, Link } from "react-router-dom" +import { ArrowLeft, Calendar, Clock, CreditCard, Shield, CheckCircle2 } from "lucide-react" +import { toast } from "@/hooks/use-toast" + +const bookingData: Record = { + "1-1": { starName: "GM-远洋", starAvatar: "/streamer-1.jpg", serviceName: "单局陪玩", price: 50, duration: "1局", type: "陪玩" }, + "1-2": { starName: "GM-远洋", starAvatar: "/streamer-1.jpg", serviceName: "5局套餐", price: 220, duration: "5局", type: "陪玩" }, + "1-3": { starName: "GM-远洋", starAvatar: "/streamer-1.jpg", serviceName: "打野进阶课", price: 299, duration: "8节课", type: "课程" }, + "1-4": { starName: "GM-远洋", starAvatar: "/streamer-1.jpg", serviceName: "拜师学艺", price: 1980, duration: "30天", type: "拜师" }, + "2-1": { starName: "小柠檬", starAvatar: "/streamer-2.jpg", serviceName: "单局陪玩", price: 80, duration: "1局", type: "陪玩" }, + "3-1": { starName: "魔兽老张", starAvatar: "/streamer-3.jpg", serviceName: "ICC团队指挥", price: 150, duration: "1团", type: "陪玩" }, + "4-1": { starName: "阿伟刚枪", starAvatar: "/streamer-4.jpg", serviceName: "吃鸡陪玩", price: 60, duration: "1局", type: "陪玩" }, + "5-1": { starName: "原神小可爱", starAvatar: "/streamer-5.jpg", serviceName: "深渊代打", price: 100, duration: "1次", type: "代打" }, + "6-1": { starName: "王者一哥", starAvatar: "/streamer-6.jpg", serviceName: "上分陪玩", price: 70, duration: "1局", type: "陪玩" }, +} + +function BookingContent() { + const [searchParams] = useSearchParams() + const navigate = useNavigate() + const starId = searchParams.get("starId") || "1" + const serviceId = searchParams.get("serviceId") || "1" + const bookingKey = `${starId}-${serviceId}` + const booking = bookingData[bookingKey] ?? bookingData["1-1"]! + + const [selectedDate, setSelectedDate] = useState("") + const [selectedTime, setSelectedTime] = useState("") + const [note, setNote] = useState("") + const [isSubmitting, setIsSubmitting] = useState(false) + const [isSuccess, setIsSuccess] = useState(false) + + const availableDates = Array.from({ length: 7 }, (_, i) => { + const date = new Date() + date.setDate(date.getDate() + i) + return { + value: date.toISOString().split("T")[0]!, + label: i === 0 ? "今天" : i === 1 ? "明天" : `${date.getMonth() + 1}/${date.getDate()}`, + } + }) + + const timeSlots = [ + "09:00", "10:00", "11:00", "14:00", "15:00", "16:00", "17:00", "18:00", "19:00", "20:00", "21:00", "22:00", + ] + + const handleSubmit = async () => { + if (!selectedDate || !selectedTime) { + toast({ title: "请选择预约时间" }) + return + } + setIsSubmitting(true) + await new Promise((resolve) => setTimeout(resolve, 1500)) + setIsSubmitting(false) + setIsSuccess(true) + setTimeout(() => { + void navigate("/profile") + }, 2000) + } + + if (isSuccess) { + return ( +
+
+
+ +
+
+

预约成功!

+

+ 已向 {booking.starName} 发送预约请求 +
+ 请等待确认 +

+
+ + 查看我的订单 + +
+
+ ) + } + + return ( +
+
+ + + +

确认预约

+
+ +
+
+
+ {booking.starName} +
+
+

{booking.starName}

+

{booking.serviceName}

+
+ {booking.duration} + + {booking.price} 玩值币 +
+
+
+
+ +
+

+ + 选择日期 +

+
+ {availableDates.map((date) => ( + + ))} +
+
+ +
+

+ + 选择时间 +

+
+ {timeSlots.map((time) => ( + + ))} +
+
+ +
+

备注说明

+