+ {/* Header - Minimized */}
+
+
+ {/* Video Container */}
+
+ {videos.map((video, index) => (
+
+ {/* Video Background */}
+
+
+
+ {/* Video Overlay */}
+
+
+ {/* Play/Pause Button - Minimized */}
+
setIsPlaying(!isPlaying)}
+ >
+ {!isPlaying && (
+
+ )}
+
+
+
+ {/* Right Side Actions - Minimized */}
+
+ {/* Like Button */}
+
+
+ {formatNumber(video.likes)}
+
+
+ {/* Comment Button */}
+
+
+ {formatNumber(video.comments)}
+
+
+ {/* Share Button */}
+
+
+ {formatNumber(video.shares)}
+
+
+ {/* Volume Button */}
+
+
+
+ {/* Bottom Info - Minimized */}
+
+
+
{video.venue}
+
{video.description}
+
+
+
+ {/* Progress Indicator - Minimized */}
+
+ {videos.map((_, idx) => (
+
+ ))}
+
+
+ ))}
+
+
+ {/* Swipe Hint - Minimized */}
+ {currentIndex === 0 && (
+
+ 上滑查看更多
+
+ )}
+
+ )
+}
diff --git a/client/limo-mobile-h5/app/layout.tsx b/client/limo-mobile-h5/app/layout.tsx
new file mode 100644
index 0000000..17b2ce8
--- /dev/null
+++ b/client/limo-mobile-h5/app/layout.tsx
@@ -0,0 +1,20 @@
+import type { Metadata } from 'next'
+import './globals.css'
+
+export const metadata: Metadata = {
+ title: 'v0 App',
+ description: 'Created with v0',
+ generator: 'v0.dev',
+}
+
+export default function RootLayout({
+ children,
+}: Readonly<{
+ children: React.ReactNode
+}>) {
+ return (
+
+ {children}
+
+ )
+}
diff --git a/client/limo-mobile-h5/app/m-space/page.tsx b/client/limo-mobile-h5/app/m-space/page.tsx
new file mode 100644
index 0000000..e60375f
--- /dev/null
+++ b/client/limo-mobile-h5/app/m-space/page.tsx
@@ -0,0 +1,257 @@
+"use client"
+
+import type React from "react"
+
+import { useState, useRef } from "react"
+import Image from "next/image"
+import { X, Heart, MessageCircle, Share, Play, Volume2, VolumeX } from "lucide-react"
+import Link from "next/link"
+
+interface VideoData {
+ id: number
+ venue: string
+ title: string
+ description: string
+ likes: number
+ comments: number
+ shares: number
+ videoUrl: string
+ thumbnail: string
+ isLiked: boolean
+}
+
+const mockVideos: VideoData[] = [
+ {
+ id: 1,
+ venue: "RONIN黄金篮球馆",
+ title: "精彩三分球集锦",
+ description: "今日比赛精彩瞬间,连续三分命中!#篮球 #精彩瞬间",
+ likes: 1234,
+ comments: 89,
+ shares: 45,
+ videoUrl: "/placeholder.svg?height=800&width=400&text=Basketball+Highlights",
+ thumbnail: "/placeholder.svg?height=800&width=400&text=Basketball+Game+1",
+ isLiked: false,
+ },
+ {
+ id: 2,
+ venue: "Panda惊怒熊猫运动俱乐部",
+ title: "激烈对抗瞬间",
+ description: "双方激烈对抗,精彩防守反击!#运动 #篮球对抗",
+ likes: 2156,
+ comments: 156,
+ shares: 78,
+ videoUrl: "/placeholder.svg?height=800&width=400&text=Basketball+Defense",
+ thumbnail: "/placeholder.svg?height=800&width=400&text=Basketball+Game+2",
+ isLiked: true,
+ },
+ {
+ id: 3,
+ venue: "星光篮球场",
+ title: "完美扣篮时刻",
+ description: "力量与技巧的完美结合,震撼扣篮!#扣篮 #力量",
+ likes: 3421,
+ comments: 234,
+ shares: 123,
+ videoUrl: "/placeholder.svg?height=800&width=400&text=Basketball+Dunk",
+ thumbnail: "/placeholder.svg?height=800&width=400&text=Basketball+Game+3",
+ isLiked: false,
+ },
+ {
+ id: 4,
+ venue: "蓝天体育馆",
+ title: "团队配合精彩",
+ description: "完美的团队配合,流畅的传球配合!#团队 #配合",
+ likes: 987,
+ comments: 67,
+ shares: 34,
+ videoUrl: "/placeholder.svg?height=800&width=400&text=Team+Play",
+ thumbnail: "/placeholder.svg?height=800&width=400&text=Basketball+Game+4",
+ isLiked: false,
+ },
+]
+
+export default function MSpacePage() {
+ const [currentIndex, setCurrentIndex] = useState(0)
+ const [videos, setVideos] = useState(mockVideos)
+ const [isPlaying, setIsPlaying] = useState(true)
+ const [isMuted, setIsMuted] = useState(false)
+ const containerRef = useRef(null)
+ const startY = useRef(0)
+ const currentY = useRef(0)
+
+ const handleTouchStart = (e: React.TouchEvent) => {
+ startY.current = e.touches[0].clientY
+ }
+
+ const handleTouchMove = (e: React.TouchEvent) => {
+ currentY.current = e.touches[0].clientY
+ }
+
+ const handleTouchEnd = () => {
+ const deltaY = startY.current - currentY.current
+ const threshold = 50
+
+ if (Math.abs(deltaY) > threshold) {
+ if (deltaY > 0 && currentIndex < videos.length - 1) {
+ // 向上滑动,下一个视频
+ setCurrentIndex(currentIndex + 1)
+ } else if (deltaY < 0 && currentIndex > 0) {
+ // 向下滑动,上一个视频
+ setCurrentIndex(currentIndex - 1)
+ }
+ }
+ }
+
+ const handleLike = (videoId: number) => {
+ setVideos(
+ videos.map((video) => {
+ if (video.id === videoId) {
+ return {
+ ...video,
+ isLiked: !video.isLiked,
+ likes: video.isLiked ? video.likes - 1 : video.likes + 1,
+ }
+ }
+ return video
+ }),
+ )
+ }
+
+ const formatNumber = (num: number) => {
+ if (num >= 1000) {
+ return (num / 1000).toFixed(1) + "k"
+ }
+ return num.toString()
+ }
+
+ const currentVideo = videos[currentIndex]
+
+ return (
+
+ {/* Header - Minimized */}
+
+
+ {/* Video Container */}
+
+ {videos.map((video, index) => (
+
+ {/* Video Background */}
+
+
+
+ {/* Video Overlay */}
+
+
+ {/* Play/Pause Button - Minimized */}
+
setIsPlaying(!isPlaying)}
+ >
+ {!isPlaying && (
+
+ )}
+
+
+
+ {/* Right Side Actions - Minimized */}
+
+ {/* Like Button */}
+
+
+ {formatNumber(video.likes)}
+
+
+ {/* Comment Button */}
+
+
+ {formatNumber(video.comments)}
+
+
+ {/* Share Button */}
+
+
+ {formatNumber(video.shares)}
+
+
+ {/* Volume Button */}
+
+
+
+ {/* Bottom Info - Minimized */}
+
+
+
{video.venue}
+
{video.description}
+
+
+
+ {/* Progress Indicator - Minimized */}
+
+ {videos.map((_, idx) => (
+
+ ))}
+
+
+ ))}
+
+
+ {/* Swipe Hint - Minimized */}
+ {currentIndex === 0 && (
+
+ 上滑查看更多
+
+ )}
+
+ )
+}
diff --git a/client/limo-mobile-h5/app/page.tsx b/client/limo-mobile-h5/app/page.tsx
new file mode 100644
index 0000000..eb9b00a
--- /dev/null
+++ b/client/limo-mobile-h5/app/page.tsx
@@ -0,0 +1,211 @@
+import Image from "next/image"
+import { MapPin, Play, Home, User, Clock, Zap } from "lucide-react"
+import { Button } from "@/components/ui/button"
+import Link from "next/link"
+
+export default function LimoMobile() {
+ return (
+
+ {/* Header with subtle gradient */}
+
+
+
+
+
+
+
+
+ {/* Navigation Tabs with enhanced styling */}
+
+
+
+ {/* Sports Categories with glassmorphism effect */}
+
+
+ {/* Location with enhanced styling */}
+
+
+
+ 杭州市文一西路未来科技城万利大厦0701
+ 切换
+
+
+
+ {/* Venue Cards with premium styling */}
+
+ {/* Distance Timeline */}
+
+
+
+ {/* First Venue */}
+
+
+
+
+
120km
+
+
+
+ 2分钟前更新
+
+
+
+
+
+
+
+
+ R
+
+
+
RONIN黄金篮球馆
+
杭州拱墅区黑马路124号(肯德基对面)
+
+
+
+
+
+
+
+
+
+
+
+ {/* Second Venue */}
+
+
+
+
+
140km
+
+
+
+ 5分钟前更新
+
+
+
+
+
+
+
+
+ P
+
+
+
Panda惊怒熊猫运动俱乐部
+
杭州拱墅区黑马路124号(肯德基对面)
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {/* Bottom Spacing for Fixed Navigation */}
+
+
+ {/* Bottom Navigation with glassmorphism */}
+
+
+
+
+
+
+
首页
+
+
+
+
+
+
我的
+
+
+
+
+ {/* Bottom Watermark with fade effect */}
+
+
+ )
+}
diff --git a/client/limo-mobile-h5/app/profile/page.tsx b/client/limo-mobile-h5/app/profile/page.tsx
new file mode 100644
index 0000000..388a015
--- /dev/null
+++ b/client/limo-mobile-h5/app/profile/page.tsx
@@ -0,0 +1,215 @@
+import {
+ ArrowLeft,
+ Settings,
+ Heart,
+ Clock,
+ MapPin,
+ Home,
+ User,
+ Crown,
+ Star,
+ ChevronRight,
+ Bell,
+ Shield,
+} from "lucide-react"
+import { Button } from "@/components/ui/button"
+import Link from "next/link"
+
+export default function ProfilePage() {
+ return (
+
+ {/* Header with glassmorphism */}
+
+
+ {/* User Profile Section with premium styling */}
+
+
+
+
+
+
ID: 123456789
+
+
+
+ 积分: 2,580
+
+
+
等级: LV.8
+
+
+
+
+
+
+ {/* Stats Cards */}
+
+
+ {/* Menu Items with premium styling */}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {/* Recent Activity with enhanced styling */}
+
+
+
+ 最近活动
+
+
+
+
+
+
观看了 RONIN黄金篮球馆
+
2小时前 · 观看时长 45分钟
+
+
+10积分
+
+
+
+
+
收藏了 Panda惊怒熊猫运动俱乐部
+
1天前
+
+
+5积分
+
+
+
+
+ {/* Bottom Spacing for Fixed Navigation */}
+
+
+ {/* Bottom Navigation with glassmorphism */}
+
+
+
+
+
+
+
首页
+
+
+
+
+
+
我的
+
+
+
+
+ )
+}
diff --git a/client/limo-mobile-h5/app/vip/page.tsx b/client/limo-mobile-h5/app/vip/page.tsx
new file mode 100644
index 0000000..6014b67
--- /dev/null
+++ b/client/limo-mobile-h5/app/vip/page.tsx
@@ -0,0 +1,317 @@
+"use client"
+
+import { ArrowLeft, Crown, Check, Star, Zap, Shield, Gift, Sparkles, Trophy, Users } from "lucide-react"
+import { Button } from "@/components/ui/button"
+import Link from "next/link"
+import { useState } from "react"
+
+export default function VipPage() {
+ const [selectedPlan, setSelectedPlan] = useState("monthly")
+
+ const plans = [
+ {
+ id: "daily",
+ name: "体验版",
+ duration: "1天",
+ price: 9.9,
+ originalPrice: 19.9,
+ discount: "限时5折",
+ popular: false,
+ badge: "试用",
+ color: "from-blue-400 to-blue-500",
+ },
+ {
+ id: "monthly",
+ name: "月度会员",
+ duration: "1个月",
+ price: 68,
+ originalPrice: 98,
+ discount: "7折优惠",
+ popular: true,
+ badge: "推荐",
+ color: "from-purple-400 to-purple-500",
+ },
+ {
+ id: "quarterly",
+ name: "季度会员",
+ duration: "3个月",
+ price: 168,
+ originalPrice: 294,
+ discount: "超值4.3折",
+ popular: false,
+ badge: "超值",
+ color: "from-pink-400 to-pink-500",
+ },
+ {
+ id: "yearly",
+ name: "年度会员",
+ duration: "1年",
+ price: 588,
+ originalPrice: 1176,
+ discount: "超值5折",
+ popular: false,
+ badge: "最划算",
+ color: "from-orange-400 to-orange-500",
+ },
+ ]
+
+ const privileges = [
+ {
+ icon: ,
+ title: "专属身份标识",
+ desc: "VIP专属标识,彰显尊贵身份",
+ color: "from-yellow-400 to-orange-500",
+ },
+ {
+ icon: ,
+ title: "优先预约权",
+ desc: "热门场馆优先预约,不再排队等待",
+ color: "from-blue-400 to-blue-500",
+ },
+ {
+ icon: ,
+ title: "专属折扣",
+ desc: "场馆预订享受VIP专属折扣优惠",
+ color: "from-green-400 to-green-500",
+ },
+ {
+ icon: ,
+ title: "免费取消",
+ desc: "预约后可免费取消,无违约金",
+ color: "from-purple-400 to-purple-500",
+ },
+ {
+ icon: ,
+ title: "专属客服",
+ desc: "7x24小时VIP专属客服服务",
+ color: "from-pink-400 to-pink-500",
+ },
+ {
+ icon: ,
+ title: "高清回放",
+ desc: "无限制观看高清比赛回放视频",
+ color: "from-indigo-400 to-indigo-500",
+ },
+ ]
+
+ const stats = [
+ { label: "活跃用户", value: "50万+", icon: },
+ { label: "满意度", value: "98%", icon: },
+ { label: "场馆覆盖", value: "1000+", icon: },
+ ]
+
+ return (
+
+ {/* Header with premium gradient */}
+
+
+ {/* VIP Banner with enhanced styling */}
+
+ {/* Background decorations */}
+
+
+
+
+
+
+
+
+
LIMO VIP会员
+
开启专属运动体验
+
+ {/* Stats */}
+
+ {stats.map((stat, index) => (
+
+
+ {stat.icon}
+
+
{stat.value}
+
{stat.label}
+
+ ))}
+
+
+
+
+ {/* Pricing Plans with premium styling */}
+
+
+
+
+
+ {plans.map((plan) => (
+
setSelectedPlan(plan.id)}
+ className={`relative border-2 rounded-3xl p-5 cursor-pointer transition-all duration-300 ${
+ selectedPlan === plan.id
+ ? "border-transparent bg-gradient-to-br from-[#05C7C7]/10 to-[#04B5B5]/10 shadow-xl scale-[1.02]"
+ : "border-gray-200/50 bg-white/50 hover:border-gray-300/50 hover:shadow-lg"
+ } ${plan.popular ? "ring-2 ring-yellow-400/50" : ""}`}
+ >
+ {plan.popular && (
+
+ )}
+
+
+
+
+
+
+
{plan.name}
+
{plan.duration}
+
+
+ ¥{plan.price}
+ ¥{plan.originalPrice}
+
+
+
+ {plan.discount}
+
+
+
+ {selectedPlan === plan.id && (
+
+ )}
+
+ ))}
+
+
+
+
+ {/* VIP Privileges with enhanced styling */}
+
+
+
+
+
+
+ VIP专属特权
+
+
+
+ {privileges.map((privilege, index) => (
+
+
+ {privilege.icon}
+
+
+
{privilege.title}
+
{privilege.desc}
+
+
+ ))}
+
+
+
+
+ {/* Testimonials */}
+
+
+
用户好评
+
+
+
+
+ 张
+
+
张先生
+
+ {[1, 2, 3, 4, 5].map((i) => (
+
+ ))}
+
+
+
"VIP服务真的很棒,预约场馆再也不用排队了!"
+
+
+
+
+ 李
+
+
李女士
+
+ {[1, 2, 3, 4, 5].map((i) => (
+
+ ))}
+
+
+
"专属客服响应很快,解决问题很及时,值得推荐!"
+
+
+
+
+
+ {/* Purchase Section with enhanced styling */}
+
+
+
+
选择套餐:
+
+
{plans.find((p) => p.id === selectedPlan)?.name}
+
+ ¥{plans.find((p) => p.id === selectedPlan)?.price}
+
+
+
+
+
+
+
+
+
+ 安全支付
+
+
+
+
+ 随时取消
+
+
+
7天无理由退款
+
+
+
开通即表示同意《VIP会员服务协议》
+
+
+
+ {/* Bottom Spacing */}
+
+
+ )
+}
diff --git a/client/limo-mobile-h5/app/zone/page.tsx b/client/limo-mobile-h5/app/zone/page.tsx
new file mode 100644
index 0000000..f58a24c
--- /dev/null
+++ b/client/limo-mobile-h5/app/zone/page.tsx
@@ -0,0 +1,250 @@
+"use client"
+
+import Image from "next/image"
+import {
+ ArrowLeft,
+ Play,
+ ChevronDown,
+ Clock,
+ MapPin,
+ Maximize2,
+ Users,
+ Eye,
+ Share2,
+ Heart,
+ MoreHorizontal,
+} from "lucide-react"
+import { Button } from "@/components/ui/button"
+import { useState } from "react"
+
+export default function ZoneVenuePage() {
+ const [activeTab, setActiveTab] = useState("highlights")
+
+ return (
+
+ {/* Header with premium background */}
+
+
+
+
+ {/* Header Controls */}
+
+
+ {/* Venue Info Card with glassmorphism */}
+
+
+
+
+
RONIN黄金篮球馆
+
+
+ 杭州拱墅区黑马路124号(肯德基对面)
+
+
+
+
+ 1,234 关注
+
+
+
+
+ 今日 856 观看
+
+
+
+
+
+
+
+ {/* Main Video Player with enhanced styling */}
+
+
+
+
+
+
+
+
+ 2.1k
+
+
+
+ 856
+
+
+
2分钟前更新
+
+
+
+
+
+ {/* Navigation Tabs with enhanced styling */}
+
+
+
+
+
+
+
+
+
+ {/* Court and Time Selection with premium styling */}
+
+
+
+
+ 今日 08:20 ~ 22:00
+
+
+
+
+ {/* Video Grid with enhanced styling */}
+
+ {/* Live Video */}
+
+
+ {/* Other Videos */}
+ {[1, 2, 3, 4, 5].map((index) => (
+
+ ))}
+
+
+ {/* Bottom Notice with enhanced styling */}
+
+
+
+ !
+
+
视频云端保存仅7天,请及时下载
+
+
+
+ {/* Bottom Actions - Fixed with glassmorphism */}
+
+
+
+
+
+ {/* Bottom Spacing for Fixed Actions */}
+
+
+ )
+}
diff --git a/client/limo-mobile-h5/components.json b/client/limo-mobile-h5/components.json
new file mode 100644
index 0000000..d9ef0ae
--- /dev/null
+++ b/client/limo-mobile-h5/components.json
@@ -0,0 +1,21 @@
+{
+ "$schema": "https://ui.shadcn.com/schema.json",
+ "style": "default",
+ "rsc": true,
+ "tsx": true,
+ "tailwind": {
+ "config": "tailwind.config.ts",
+ "css": "app/globals.css",
+ "baseColor": "neutral",
+ "cssVariables": true,
+ "prefix": ""
+ },
+ "aliases": {
+ "components": "@/components",
+ "utils": "@/lib/utils",
+ "ui": "@/components/ui",
+ "lib": "@/lib",
+ "hooks": "@/hooks"
+ },
+ "iconLibrary": "lucide"
+}
\ No newline at end of file
diff --git a/client/limo-mobile-h5/components/theme-provider.tsx b/client/limo-mobile-h5/components/theme-provider.tsx
new file mode 100644
index 0000000..55c2f6e
--- /dev/null
+++ b/client/limo-mobile-h5/components/theme-provider.tsx
@@ -0,0 +1,11 @@
+'use client'
+
+import * as React from 'react'
+import {
+ ThemeProvider as NextThemesProvider,
+ type ThemeProviderProps,
+} from 'next-themes'
+
+export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
+ return {children}
+}
diff --git a/client/limo-mobile-h5/components/ui/accordion.tsx b/client/limo-mobile-h5/components/ui/accordion.tsx
new file mode 100644
index 0000000..24c788c
--- /dev/null
+++ b/client/limo-mobile-h5/components/ui/accordion.tsx
@@ -0,0 +1,58 @@
+"use client"
+
+import * as React from "react"
+import * as AccordionPrimitive from "@radix-ui/react-accordion"
+import { ChevronDown } from "lucide-react"
+
+import { cn } from "@/lib/utils"
+
+const Accordion = AccordionPrimitive.Root
+
+const AccordionItem = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+AccordionItem.displayName = "AccordionItem"
+
+const AccordionTrigger = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, children, ...props }, ref) => (
+
+ svg]:rotate-180",
+ className
+ )}
+ {...props}
+ >
+ {children}
+
+
+
+))
+AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName
+
+const AccordionContent = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, children, ...props }, ref) => (
+
+ {children}
+
+))
+
+AccordionContent.displayName = AccordionPrimitive.Content.displayName
+
+export { Accordion, AccordionItem, AccordionTrigger, AccordionContent }
diff --git a/client/limo-mobile-h5/components/ui/alert-dialog.tsx b/client/limo-mobile-h5/components/ui/alert-dialog.tsx
new file mode 100644
index 0000000..25e7b47
--- /dev/null
+++ b/client/limo-mobile-h5/components/ui/alert-dialog.tsx
@@ -0,0 +1,141 @@
+"use client"
+
+import * as React from "react"
+import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog"
+
+import { cn } from "@/lib/utils"
+import { buttonVariants } from "@/components/ui/button"
+
+const AlertDialog = AlertDialogPrimitive.Root
+
+const AlertDialogTrigger = AlertDialogPrimitive.Trigger
+
+const AlertDialogPortal = AlertDialogPrimitive.Portal
+
+const AlertDialogOverlay = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName
+
+const AlertDialogContent = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+
+
+
+))
+AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName
+
+const AlertDialogHeader = ({
+ className,
+ ...props
+}: React.HTMLAttributes) => (
+
+)
+AlertDialogHeader.displayName = "AlertDialogHeader"
+
+const AlertDialogFooter = ({
+ className,
+ ...props
+}: React.HTMLAttributes) => (
+
+)
+AlertDialogFooter.displayName = "AlertDialogFooter"
+
+const AlertDialogTitle = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName
+
+const AlertDialogDescription = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+AlertDialogDescription.displayName =
+ AlertDialogPrimitive.Description.displayName
+
+const AlertDialogAction = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName
+
+const AlertDialogCancel = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName
+
+export {
+ AlertDialog,
+ AlertDialogPortal,
+ AlertDialogOverlay,
+ AlertDialogTrigger,
+ AlertDialogContent,
+ AlertDialogHeader,
+ AlertDialogFooter,
+ AlertDialogTitle,
+ AlertDialogDescription,
+ AlertDialogAction,
+ AlertDialogCancel,
+}
diff --git a/client/limo-mobile-h5/components/ui/alert.tsx b/client/limo-mobile-h5/components/ui/alert.tsx
new file mode 100644
index 0000000..41fa7e0
--- /dev/null
+++ b/client/limo-mobile-h5/components/ui/alert.tsx
@@ -0,0 +1,59 @@
+import * as React from "react"
+import { cva, type VariantProps } from "class-variance-authority"
+
+import { cn } from "@/lib/utils"
+
+const alertVariants = cva(
+ "relative w-full rounded-lg border p-4 [&>svg~*]:pl-7 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground",
+ {
+ variants: {
+ variant: {
+ default: "bg-background text-foreground",
+ destructive:
+ "border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive",
+ },
+ },
+ defaultVariants: {
+ variant: "default",
+ },
+ }
+)
+
+const Alert = React.forwardRef<
+ HTMLDivElement,
+ React.HTMLAttributes & VariantProps
+>(({ className, variant, ...props }, ref) => (
+
+))
+Alert.displayName = "Alert"
+
+const AlertTitle = React.forwardRef<
+ HTMLParagraphElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => (
+
+))
+AlertTitle.displayName = "AlertTitle"
+
+const AlertDescription = React.forwardRef<
+ HTMLParagraphElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => (
+
+))
+AlertDescription.displayName = "AlertDescription"
+
+export { Alert, AlertTitle, AlertDescription }
diff --git a/client/limo-mobile-h5/components/ui/aspect-ratio.tsx b/client/limo-mobile-h5/components/ui/aspect-ratio.tsx
new file mode 100644
index 0000000..d6a5226
--- /dev/null
+++ b/client/limo-mobile-h5/components/ui/aspect-ratio.tsx
@@ -0,0 +1,7 @@
+"use client"
+
+import * as AspectRatioPrimitive from "@radix-ui/react-aspect-ratio"
+
+const AspectRatio = AspectRatioPrimitive.Root
+
+export { AspectRatio }
diff --git a/client/limo-mobile-h5/components/ui/avatar.tsx b/client/limo-mobile-h5/components/ui/avatar.tsx
new file mode 100644
index 0000000..51e507b
--- /dev/null
+++ b/client/limo-mobile-h5/components/ui/avatar.tsx
@@ -0,0 +1,50 @@
+"use client"
+
+import * as React from "react"
+import * as AvatarPrimitive from "@radix-ui/react-avatar"
+
+import { cn } from "@/lib/utils"
+
+const Avatar = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+Avatar.displayName = AvatarPrimitive.Root.displayName
+
+const AvatarImage = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+AvatarImage.displayName = AvatarPrimitive.Image.displayName
+
+const AvatarFallback = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName
+
+export { Avatar, AvatarImage, AvatarFallback }
diff --git a/client/limo-mobile-h5/components/ui/badge.tsx b/client/limo-mobile-h5/components/ui/badge.tsx
new file mode 100644
index 0000000..f000e3e
--- /dev/null
+++ b/client/limo-mobile-h5/components/ui/badge.tsx
@@ -0,0 +1,36 @@
+import * as React from "react"
+import { cva, type VariantProps } from "class-variance-authority"
+
+import { cn } from "@/lib/utils"
+
+const badgeVariants = cva(
+ "inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
+ {
+ variants: {
+ variant: {
+ default:
+ "border-transparent bg-primary text-primary-foreground hover:bg-primary/80",
+ secondary:
+ "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
+ destructive:
+ "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80",
+ outline: "text-foreground",
+ },
+ },
+ defaultVariants: {
+ variant: "default",
+ },
+ }
+)
+
+export interface BadgeProps
+ extends React.HTMLAttributes,
+ VariantProps {}
+
+function Badge({ className, variant, ...props }: BadgeProps) {
+ return (
+
+ )
+}
+
+export { Badge, badgeVariants }
diff --git a/client/limo-mobile-h5/components/ui/breadcrumb.tsx b/client/limo-mobile-h5/components/ui/breadcrumb.tsx
new file mode 100644
index 0000000..60e6c96
--- /dev/null
+++ b/client/limo-mobile-h5/components/ui/breadcrumb.tsx
@@ -0,0 +1,115 @@
+import * as React from "react"
+import { Slot } from "@radix-ui/react-slot"
+import { ChevronRight, MoreHorizontal } from "lucide-react"
+
+import { cn } from "@/lib/utils"
+
+const Breadcrumb = React.forwardRef<
+ HTMLElement,
+ React.ComponentPropsWithoutRef<"nav"> & {
+ separator?: React.ReactNode
+ }
+>(({ ...props }, ref) => )
+Breadcrumb.displayName = "Breadcrumb"
+
+const BreadcrumbList = React.forwardRef<
+ HTMLOListElement,
+ React.ComponentPropsWithoutRef<"ol">
+>(({ className, ...props }, ref) => (
+
+))
+BreadcrumbList.displayName = "BreadcrumbList"
+
+const BreadcrumbItem = React.forwardRef<
+ HTMLLIElement,
+ React.ComponentPropsWithoutRef<"li">
+>(({ className, ...props }, ref) => (
+
+))
+BreadcrumbItem.displayName = "BreadcrumbItem"
+
+const BreadcrumbLink = React.forwardRef<
+ HTMLAnchorElement,
+ React.ComponentPropsWithoutRef<"a"> & {
+ asChild?: boolean
+ }
+>(({ asChild, className, ...props }, ref) => {
+ const Comp = asChild ? Slot : "a"
+
+ return (
+
+ )
+})
+BreadcrumbLink.displayName = "BreadcrumbLink"
+
+const BreadcrumbPage = React.forwardRef<
+ HTMLSpanElement,
+ React.ComponentPropsWithoutRef<"span">
+>(({ className, ...props }, ref) => (
+
+))
+BreadcrumbPage.displayName = "BreadcrumbPage"
+
+const BreadcrumbSeparator = ({
+ children,
+ className,
+ ...props
+}: React.ComponentProps<"li">) => (
+ svg]:w-3.5 [&>svg]:h-3.5", className)}
+ {...props}
+ >
+ {children ?? }
+
+)
+BreadcrumbSeparator.displayName = "BreadcrumbSeparator"
+
+const BreadcrumbEllipsis = ({
+ className,
+ ...props
+}: React.ComponentProps<"span">) => (
+
+
+ More
+
+)
+BreadcrumbEllipsis.displayName = "BreadcrumbElipssis"
+
+export {
+ Breadcrumb,
+ BreadcrumbList,
+ BreadcrumbItem,
+ BreadcrumbLink,
+ BreadcrumbPage,
+ BreadcrumbSeparator,
+ BreadcrumbEllipsis,
+}
diff --git a/client/limo-mobile-h5/components/ui/button.tsx b/client/limo-mobile-h5/components/ui/button.tsx
new file mode 100644
index 0000000..36496a2
--- /dev/null
+++ b/client/limo-mobile-h5/components/ui/button.tsx
@@ -0,0 +1,56 @@
+import * as React from "react"
+import { Slot } from "@radix-ui/react-slot"
+import { cva, type VariantProps } from "class-variance-authority"
+
+import { cn } from "@/lib/utils"
+
+const buttonVariants = cva(
+ "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
+ {
+ variants: {
+ variant: {
+ default: "bg-primary text-primary-foreground hover:bg-primary/90",
+ destructive:
+ "bg-destructive text-destructive-foreground hover:bg-destructive/90",
+ outline:
+ "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
+ secondary:
+ "bg-secondary text-secondary-foreground hover:bg-secondary/80",
+ ghost: "hover:bg-accent hover:text-accent-foreground",
+ link: "text-primary underline-offset-4 hover:underline",
+ },
+ size: {
+ default: "h-10 px-4 py-2",
+ sm: "h-9 rounded-md px-3",
+ lg: "h-11 rounded-md px-8",
+ icon: "h-10 w-10",
+ },
+ },
+ defaultVariants: {
+ variant: "default",
+ size: "default",
+ },
+ }
+)
+
+export interface ButtonProps
+ extends React.ButtonHTMLAttributes,
+ VariantProps {
+ asChild?: boolean
+}
+
+const Button = React.forwardRef(
+ ({ className, variant, size, asChild = false, ...props }, ref) => {
+ const Comp = asChild ? Slot : "button"
+ return (
+
+ )
+ }
+)
+Button.displayName = "Button"
+
+export { Button, buttonVariants }
diff --git a/client/limo-mobile-h5/components/ui/calendar.tsx b/client/limo-mobile-h5/components/ui/calendar.tsx
new file mode 100644
index 0000000..61d2b45
--- /dev/null
+++ b/client/limo-mobile-h5/components/ui/calendar.tsx
@@ -0,0 +1,66 @@
+"use client"
+
+import * as React from "react"
+import { ChevronLeft, ChevronRight } from "lucide-react"
+import { DayPicker } from "react-day-picker"
+
+import { cn } from "@/lib/utils"
+import { buttonVariants } from "@/components/ui/button"
+
+export type CalendarProps = React.ComponentProps
+
+function Calendar({
+ className,
+ classNames,
+ showOutsideDays = true,
+ ...props
+}: CalendarProps) {
+ return (
+ ,
+ IconRight: ({ ...props }) => ,
+ }}
+ {...props}
+ />
+ )
+}
+Calendar.displayName = "Calendar"
+
+export { Calendar }
diff --git a/client/limo-mobile-h5/components/ui/card.tsx b/client/limo-mobile-h5/components/ui/card.tsx
new file mode 100644
index 0000000..f62edea
--- /dev/null
+++ b/client/limo-mobile-h5/components/ui/card.tsx
@@ -0,0 +1,79 @@
+import * as React from "react"
+
+import { cn } from "@/lib/utils"
+
+const Card = React.forwardRef<
+ HTMLDivElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => (
+
+))
+Card.displayName = "Card"
+
+const CardHeader = React.forwardRef<
+ HTMLDivElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => (
+
+))
+CardHeader.displayName = "CardHeader"
+
+const CardTitle = React.forwardRef<
+ HTMLDivElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => (
+
+))
+CardTitle.displayName = "CardTitle"
+
+const CardDescription = React.forwardRef<
+ HTMLDivElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => (
+
+))
+CardDescription.displayName = "CardDescription"
+
+const CardContent = React.forwardRef<
+ HTMLDivElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => (
+
+))
+CardContent.displayName = "CardContent"
+
+const CardFooter = React.forwardRef<
+ HTMLDivElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => (
+
+))
+CardFooter.displayName = "CardFooter"
+
+export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }
diff --git a/client/limo-mobile-h5/components/ui/carousel.tsx b/client/limo-mobile-h5/components/ui/carousel.tsx
new file mode 100644
index 0000000..ec505d0
--- /dev/null
+++ b/client/limo-mobile-h5/components/ui/carousel.tsx
@@ -0,0 +1,262 @@
+"use client"
+
+import * as React from "react"
+import useEmblaCarousel, {
+ type UseEmblaCarouselType,
+} from "embla-carousel-react"
+import { ArrowLeft, ArrowRight } from "lucide-react"
+
+import { cn } from "@/lib/utils"
+import { Button } from "@/components/ui/button"
+
+type CarouselApi = UseEmblaCarouselType[1]
+type UseCarouselParameters = Parameters
+type CarouselOptions = UseCarouselParameters[0]
+type CarouselPlugin = UseCarouselParameters[1]
+
+type CarouselProps = {
+ opts?: CarouselOptions
+ plugins?: CarouselPlugin
+ orientation?: "horizontal" | "vertical"
+ setApi?: (api: CarouselApi) => void
+}
+
+type CarouselContextProps = {
+ carouselRef: ReturnType[0]
+ api: ReturnType[1]
+ scrollPrev: () => void
+ scrollNext: () => void
+ canScrollPrev: boolean
+ canScrollNext: boolean
+} & CarouselProps
+
+const CarouselContext = React.createContext(null)
+
+function useCarousel() {
+ const context = React.useContext(CarouselContext)
+
+ if (!context) {
+ throw new Error("useCarousel must be used within a ")
+ }
+
+ return context
+}
+
+const Carousel = React.forwardRef<
+ HTMLDivElement,
+ React.HTMLAttributes & CarouselProps
+>(
+ (
+ {
+ orientation = "horizontal",
+ opts,
+ setApi,
+ plugins,
+ className,
+ children,
+ ...props
+ },
+ ref
+ ) => {
+ const [carouselRef, api] = useEmblaCarousel(
+ {
+ ...opts,
+ axis: orientation === "horizontal" ? "x" : "y",
+ },
+ plugins
+ )
+ const [canScrollPrev, setCanScrollPrev] = React.useState(false)
+ const [canScrollNext, setCanScrollNext] = React.useState(false)
+
+ const onSelect = React.useCallback((api: CarouselApi) => {
+ if (!api) {
+ return
+ }
+
+ setCanScrollPrev(api.canScrollPrev())
+ setCanScrollNext(api.canScrollNext())
+ }, [])
+
+ const scrollPrev = React.useCallback(() => {
+ api?.scrollPrev()
+ }, [api])
+
+ const scrollNext = React.useCallback(() => {
+ api?.scrollNext()
+ }, [api])
+
+ const handleKeyDown = React.useCallback(
+ (event: React.KeyboardEvent) => {
+ if (event.key === "ArrowLeft") {
+ event.preventDefault()
+ scrollPrev()
+ } else if (event.key === "ArrowRight") {
+ event.preventDefault()
+ scrollNext()
+ }
+ },
+ [scrollPrev, scrollNext]
+ )
+
+ React.useEffect(() => {
+ if (!api || !setApi) {
+ return
+ }
+
+ setApi(api)
+ }, [api, setApi])
+
+ React.useEffect(() => {
+ if (!api) {
+ return
+ }
+
+ onSelect(api)
+ api.on("reInit", onSelect)
+ api.on("select", onSelect)
+
+ return () => {
+ api?.off("select", onSelect)
+ }
+ }, [api, onSelect])
+
+ return (
+
+
+ {children}
+
+
+ )
+ }
+)
+Carousel.displayName = "Carousel"
+
+const CarouselContent = React.forwardRef<
+ HTMLDivElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => {
+ const { carouselRef, orientation } = useCarousel()
+
+ return (
+
+ )
+})
+CarouselContent.displayName = "CarouselContent"
+
+const CarouselItem = React.forwardRef<
+ HTMLDivElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => {
+ const { orientation } = useCarousel()
+
+ return (
+
+ )
+})
+CarouselItem.displayName = "CarouselItem"
+
+const CarouselPrevious = React.forwardRef<
+ HTMLButtonElement,
+ React.ComponentProps
+>(({ className, variant = "outline", size = "icon", ...props }, ref) => {
+ const { orientation, scrollPrev, canScrollPrev } = useCarousel()
+
+ return (
+
+ )
+})
+CarouselPrevious.displayName = "CarouselPrevious"
+
+const CarouselNext = React.forwardRef<
+ HTMLButtonElement,
+ React.ComponentProps
+>(({ className, variant = "outline", size = "icon", ...props }, ref) => {
+ const { orientation, scrollNext, canScrollNext } = useCarousel()
+
+ return (
+
+ )
+})
+CarouselNext.displayName = "CarouselNext"
+
+export {
+ type CarouselApi,
+ Carousel,
+ CarouselContent,
+ CarouselItem,
+ CarouselPrevious,
+ CarouselNext,
+}
diff --git a/client/limo-mobile-h5/components/ui/chart.tsx b/client/limo-mobile-h5/components/ui/chart.tsx
new file mode 100644
index 0000000..8620baa
--- /dev/null
+++ b/client/limo-mobile-h5/components/ui/chart.tsx
@@ -0,0 +1,365 @@
+"use client"
+
+import * as React from "react"
+import * as RechartsPrimitive from "recharts"
+
+import { cn } from "@/lib/utils"
+
+// Format: { THEME_NAME: CSS_SELECTOR }
+const THEMES = { light: "", dark: ".dark" } as const
+
+export type ChartConfig = {
+ [k in string]: {
+ label?: React.ReactNode
+ icon?: React.ComponentType
+ } & (
+ | { color?: string; theme?: never }
+ | { color?: never; theme: Record }
+ )
+}
+
+type ChartContextProps = {
+ config: ChartConfig
+}
+
+const ChartContext = React.createContext(null)
+
+function useChart() {
+ const context = React.useContext(ChartContext)
+
+ if (!context) {
+ throw new Error("useChart must be used within a ")
+ }
+
+ return context
+}
+
+const ChartContainer = React.forwardRef<
+ HTMLDivElement,
+ React.ComponentProps<"div"> & {
+ config: ChartConfig
+ children: React.ComponentProps<
+ typeof RechartsPrimitive.ResponsiveContainer
+ >["children"]
+ }
+>(({ id, className, children, config, ...props }, ref) => {
+ const uniqueId = React.useId()
+ const chartId = `chart-${id || uniqueId.replace(/:/g, "")}`
+
+ return (
+
+
+
+
+ {children}
+
+
+
+ )
+})
+ChartContainer.displayName = "Chart"
+
+const ChartStyle = ({ id, config }: { id: string; config: ChartConfig }) => {
+ const colorConfig = Object.entries(config).filter(
+ ([_, config]) => config.theme || config.color
+ )
+
+ if (!colorConfig.length) {
+ return null
+ }
+
+ return (
+
\ No newline at end of file
diff --git a/client/src/components/index.ts b/client/src/components/index.ts
new file mode 100644
index 0000000..2053896
--- /dev/null
+++ b/client/src/components/index.ts
@@ -0,0 +1,4 @@
+/**
+ * 组件导出
+ */
+export { default as CommonButton } from './CommonButton/index.vue'
\ No newline at end of file
diff --git a/client/src/constants/index.ts b/client/src/constants/index.ts
new file mode 100644
index 0000000..ae0b61f
--- /dev/null
+++ b/client/src/constants/index.ts
@@ -0,0 +1,64 @@
+/**
+ * 应用常量
+ */
+
+// API配置
+export const API_CONFIG = {
+ BASE_URL: 'https://api.example.com',
+ TIMEOUT: 10000,
+ VERSION: 'v1'
+}
+
+// 存储键名
+export const STORAGE_KEYS = {
+ TOKEN: 'app_token',
+ USER_INFO: 'user_info',
+ SETTINGS: 'app_settings'
+}
+
+// 页面路径
+export const PAGE_PATHS = {
+ HOME: '/pages/index/index',
+ PROFILE: '/pages/profile/index',
+ SETTINGS: '/pages/settings/index'
+}
+
+// 状态码
+export const STATUS_CODE = {
+ SUCCESS: 200,
+ UNAUTHORIZED: 401,
+ NOT_FOUND: 404,
+ SERVER_ERROR: 500
+}
+
+// 消息类型
+export const MESSAGE_TYPE = {
+ SUCCESS: 'success',
+ ERROR: 'error',
+ WARNING: 'warning',
+ INFO: 'info'
+} as const
+
+// 平台类型
+export const PLATFORM_TYPE = {
+ WEAPP: 'weapp',
+ H5: 'h5',
+ ALIPAY: 'alipay',
+ TT: 'tt'
+} as const
+
+// 默认配置
+export const DEFAULT_CONFIG = {
+ PAGE_SIZE: 10,
+ MAX_UPLOAD_SIZE: 10 * 1024 * 1024, // 10MB
+ DEBOUNCE_DELAY: 300,
+ THROTTLE_DELAY: 500
+}
+
+// 正则表达式
+export const REGEX = {
+ PHONE: /^1[3-9]\d{9}$/,
+ EMAIL: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
+ ID_CARD: /^[1-9]\d{5}(18|19|20)\d{2}((0[1-9])|(1[0-2]))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$/,
+ URL: /^https?:\/\/.+/
+}
\ No newline at end of file
diff --git a/client/src/index.html b/client/src/index.html
new file mode 100644
index 0000000..c30e3ec
--- /dev/null
+++ b/client/src/index.html
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+ mini-app
+
+
+
+
+
+
diff --git a/client/src/pages/index/index.config.ts b/client/src/pages/index/index.config.ts
new file mode 100644
index 0000000..7b13af0
--- /dev/null
+++ b/client/src/pages/index/index.config.ts
@@ -0,0 +1,4 @@
+export default definePageConfig({
+ navigationBarTitleText: '首页',
+ navigationBarTextStyle: 'black'
+})
diff --git a/client/src/pages/index/index.scss b/client/src/pages/index/index.scss
new file mode 100644
index 0000000..803eeb0
--- /dev/null
+++ b/client/src/pages/index/index.scss
@@ -0,0 +1,1322 @@
+.limo-mobile {
+ min-height: 100vh;
+ background: #ffffff;
+ position: relative;
+
+ // Header with subtle gradient
+ .header {
+ background: linear-gradient(180deg, rgba(255, 255, 255, 0.95) 1%, rgba(248, 250, 252, 0.8) 100%);
+ padding: 88rpx 32rpx 32rpx 32rpx; /* 增加顶部padding给状态栏留空间 */
+ position: sticky;
+ top: 0;
+ z-index: 10;
+ /* backdrop-filter 在小程序中不支持,使用多层背景模拟 */
+ box-shadow: 0 2rpx 20rpx rgba(0, 0, 0, 0.02);
+ border-bottom: 2rpx solid rgba(229, 231, 235, 0.3);
+
+ // 使用伪元素模拟背景模糊
+ &::before {
+ content: '';
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background: linear-gradient(180deg, rgba(255, 255, 255, 0.8) 100%, rgba(248, 250, 252, 0.2) 0%);
+ z-index: -1;
+ }
+
+ .header-content {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ position: relative;
+ margin: 50rpx 0 80rpx 0;
+
+ .logo {
+ height: 100rpx;
+ width: 200rpx;
+ object-fit: contain;
+ display: block;
+ position: absolute;
+ left: 50%;
+ transform: translateX(-50%);
+ }
+
+ .energy-button {
+ background: linear-gradient(135deg, #05C7C7 0%, #04B5B5 100%);
+ width: 80rpx;
+ height: 80rpx;
+ border-radius: 24rpx;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ box-shadow: 0 8rpx 24rpx rgba(5, 199, 199, 0.2);
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
+
+ &:active {
+ transform: scale(0.95);
+ }
+
+ .energy-icon {
+ color: #ffffff;
+ font-size: 32rpx;
+ }
+ }
+ }
+
+ // Navigation Tabs with enhanced styling
+ .nav-tabs {
+ display: flex;
+ margin-top: 48rpx;
+ gap: 64rpx;
+
+ .tab-item {
+ position: relative;
+
+ .tab-text {
+ font-size: 32rpx;
+ font-weight: 600;
+ color: #9CA3AF;
+ transition: color 0.3s;
+ }
+
+ .tab-underline {
+ position: absolute;
+ bottom: -24rpx;
+ left: 0;
+ right: 0;
+ height: 4rpx;
+ background: linear-gradient(90deg, #05C7C7 0%, #04B5B5 100%);
+ border-radius: 2rpx;
+ }
+
+ &.active .tab-text {
+ color: #05C7C7;
+ }
+ }
+ }
+ }
+
+ // Sports Categories with glassmorphism effect
+ .sports-section {
+ background: rgba(255, 255, 255, 0.8);
+ /* backdrop-filter 在小程序中不支持,使用多层背景模拟 */
+ position: relative;
+ padding: 30rpx 32rpx;
+ margin-bottom: 32rpx;
+ border-bottom: 2rpx solid rgba(229, 231, 235, 0.3);
+
+ // 使用伪元素模拟背景模糊
+ &::before {
+ content: '';
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background: rgba(255, 255, 255, 0.2);
+ z-index: -1;
+ }
+
+ .sports-container {
+ display: flex;
+ gap: 40rpx;
+ overflow-x: auto;
+ scrollbar-width: none;
+ -ms-overflow-style: none;
+
+ /* 小程序不支持 webkit-scrollbar,已移除 */
+
+ .sport-item {
+ flex-shrink: 0;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 16rpx;
+
+ &.all-category {
+ .category-content {
+ background: #f2f2f2;
+ color: #6B7280;
+ border-radius: 32rpx;
+ font-size: 24rpx;
+ font-weight: 600;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ min-width: 100rpx;
+ height: 100rpx;
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
+
+ &:active {
+ transform: scale(0.95);
+ }
+
+ .category-text {
+ line-height: 1.2;
+ }
+ }
+
+ &.active {
+ .category-content {
+ background: linear-gradient(135deg, #05C7C7 0%, #04B5B5 100%);
+ color: #ffffff;
+ box-shadow: 0 8rpx 24rpx rgba(5, 199, 199, 0.2);
+ }
+ }
+ }
+
+ .sport-icon-container {
+ width: 100rpx;
+ height: 100rpx;
+ border-radius: 28rpx;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
+ background: #f2f2f2;
+
+ &:active {
+ transform: scale(0.95);
+ }
+
+ .sport-emoji {
+ font-size: 48rpx;
+ line-height: 1;
+ }
+ }
+
+ &.active {
+ .sport-icon-container {
+ box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.05);
+
+ &.basketball {
+ background: linear-gradient(135deg, #FEF0E5 0%, #FEE5E5 100%);
+ }
+
+ &.football {
+ background: linear-gradient(135deg, #E8F5E8 0%, #E0F2E0 100%);
+ }
+
+ &.tennis {
+ background: linear-gradient(135deg, #FFFBEB 0%, #FEF3C7 100%);
+ }
+ }
+ }
+
+ .sport-label {
+ font-size: 24rpx;
+ color: #6B7280;
+ font-weight: 500;
+ }
+ }
+ }
+ }
+
+ // Location with enhanced styling
+ .location-bar {
+ padding: 0 32rpx 48rpx;
+
+ .location-content {
+ display: flex;
+ align-items: center;
+ background: rgba(249, 250, 251, 0.8);
+ border-radius: 24rpx;
+ padding: 24rpx;
+ font-size: 28rpx;
+
+ .location-pin {
+ color: #05C7C7;
+ margin-right: 16rpx;
+ font-size: 32rpx;
+ }
+
+ .location-text {
+ flex: 1;
+ color: #6B7280;
+ font-size: 24rpx;
+ }
+
+ .location-switch {
+ color: #05C7C7;
+ font-size: 24rpx;
+ font-weight: 500;
+ }
+ }
+ }
+
+ // Venue Cards with premium styling
+ .venue-section {
+ padding: 0 32rpx;
+ position: relative;
+
+ // Timeline
+ .timeline-container {
+ position: absolute;
+ left: 80rpx;
+ top: 0;
+ bottom: 0;
+ width: 2rpx;
+
+ .timeline-line {
+ position: absolute;
+ top: 0;
+ bottom: 0;
+ width: 2rpx;
+ background: linear-gradient(180deg, rgba(5, 199, 199, 0.3) 0%, #E5E7EB 30%, #E5E7EB 100%);
+ }
+ }
+
+ .venue-card {
+ position: relative;
+ margin-bottom: 80rpx;
+
+ .timeline-dot {
+ position: absolute;
+ left: 38rpx;
+ top: 10rpx;
+ width: 24rpx;
+ height: 24rpx;
+ border-radius: 50%;
+ border: 4rpx solid #ffffff;
+ box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.1);
+ background: linear-gradient(135deg, #D1D5DB 0%, #9CA3AF 100%);
+ z-index: 2;
+
+ &.active {
+ background: linear-gradient(135deg, #FBBF24 0%, #F59E0B 100%);
+ box-shadow: 0 0 0 4rpx #FBBF24, 0 4rpx 16rpx rgba(0, 0, 0, 0.1);
+ }
+ }
+
+ .venue-meta {
+ margin-left: 96rpx;
+ margin-bottom: 24rpx;
+ display: flex;
+ align-items: center;
+ gap: 16rpx;
+
+ .distance {
+ font-size: 24rpx;
+ color: #9CA3AF;
+ font-weight: 500;
+ }
+
+ .separator {
+ width: 8rpx;
+ height: 8rpx;
+ background: #D1D5DB;
+ border-radius: 50%;
+ }
+
+ .update-time {
+ display: flex;
+ align-items: center;
+ gap: 8rpx;
+
+ .clock-icon {
+ font-size: 24rpx;
+ }
+
+ .time-text {
+ font-size: 24rpx;
+ color: #9CA3AF;
+ }
+ }
+ }
+
+ .venue-content {
+ margin-left: 96rpx;
+ background: linear-gradient(135deg, #ffffff 0%, rgba(248, 250, 252, 0.5) 100%);
+ border-radius: 48rpx;
+ overflow: hidden;
+ box-shadow: 0 16rpx 48rpx rgba(0, 0, 0, 0.08);
+ border: 2rpx solid rgba(229, 231, 235, 0.5);
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
+ position: relative;
+
+ // 模拟 backdrop-blur 效果
+ &::before {
+ content: '';
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background: rgba(255, 255, 255, 0.4);
+ border-radius: 48rpx;
+ z-index: -1;
+ }
+
+ &:active {
+ transform: translateY(-4rpx);
+ box-shadow: 0 24rpx 64rpx rgba(0, 0, 0, 0.12);
+ }
+
+ .venue-image {
+ position: relative;
+ width: 100%;
+ height: 384rpx;
+ overflow: hidden;
+
+ .image-placeholder {
+ width: 100%;
+ height: 100%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ background: linear-gradient(135deg, #374151 0%, #1F2937 100%);
+
+ &.ronin-bg {
+ background: linear-gradient(135deg, blue 0%, #DC2626 100%);
+ }
+
+ &.panda-bg {
+ background: linear-gradient(135deg, #3B82F6 0%, #2563EB 100%);
+ }
+
+ .placeholder-text {
+ color: #ffffff;
+ font-size: 36rpx;
+ font-weight: 600;
+ text-align: center;
+ }
+ }
+
+ .hot-badge {
+ position: absolute;
+ top: 24rpx;
+ left: 24rpx;
+ background: rgba(0, 0, 0, 0.2);
+ /* backdrop-filter 在小程序中不支持,已移除 */
+ color: #ffffff;
+ padding: 8rpx 16rpx;
+ border-radius: 16rpx;
+ font-size: 24rpx;
+ font-weight: 500;
+
+ .hot-text {
+ font-size: 24rpx;
+ }
+ }
+
+ .live-badge {
+ position: absolute;
+ top: 24rpx;
+ left: 24rpx;
+ background: rgba(239, 68, 68, 0.9);
+ /* backdrop-filter 在小程序中不支持,已移除 */
+ color: #ffffff;
+ padding: 8rpx 16rpx;
+ border-radius: 16rpx;
+ font-size: 24rpx;
+ font-weight: 500;
+ display: flex;
+ align-items: center;
+ gap: 8rpx;
+
+ .live-dot {
+ width: 12rpx;
+ height: 12rpx;
+ background: #ffffff;
+ border-radius: 50%;
+ animation: pulse 1.5s infinite;
+ }
+
+ .live-text {
+ font-size: 24rpx;
+ }
+ }
+
+ .status-dot {
+ position: absolute;
+ top: 24rpx;
+ right: 24rpx;
+ width: 16rpx;
+ height: 16rpx;
+ border-radius: 50%;
+ background: #10B981;
+
+ &.online {
+ animation: pulse 2s infinite;
+ }
+ }
+
+ .play-overlay {
+ display: none;
+
+ .play-button {
+ width: 128rpx;
+ height: 128rpx;
+ background: rgba(255, 255, 255, 0.95);
+ /* backdrop-filter 在小程序中不支持,已移除 */
+ border-radius: 50%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ box-shadow: 0 16rpx 48rpx rgba(0, 0, 0, 0.2);
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
+ position: relative;
+
+ // 模拟 backdrop-blur 效果
+ &::before {
+ content: '';
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background: rgba(255, 255, 255, 0.5);
+ border-radius: 50%;
+ z-index: -1;
+ }
+
+ &:active {
+ transform: scale(1.1);
+ }
+
+ .play-icon {
+ color: #374151;
+ font-size: 48rpx;
+ margin-left: 4rpx;
+ }
+ }
+ }
+ }
+
+ .venue-info {
+ padding: 40rpx;
+
+ .venue-header {
+ display: flex;
+ align-items: center;
+ margin-bottom: 0;
+
+ .venue-logo {
+ width: 96rpx;
+ height: 96rpx;
+ border-radius: 24rpx;
+ margin-right: 24rpx;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.1);
+
+ &.ronin-logo {
+ background: transparent;
+ }
+
+ &.panda-logo {
+ background: linear-gradient(135deg, #374151 0%, #1F2937 100%);
+ }
+
+ .logo-text {
+ color: #ffffff;
+ font-size: 36rpx;
+ font-weight: bold;
+ }
+
+ .venue-logo-image {
+ width: 100%;
+ height: 100%;
+ border-radius: 24rpx;
+ object-fit: cover;
+ }
+ }
+
+ .venue-details {
+ flex: 1;
+
+ .venue-name {
+ display: block;
+ font-size: 32rpx;
+ font-weight: bold;
+ color: #111827;
+ margin-bottom: 8rpx;
+ line-height: 1.2;
+ }
+
+ .venue-address {
+ display: block;
+ font-size: 24rpx;
+ color: #6B7280;
+ line-height: 1.4;
+ }
+ }
+ }
+
+ .venue-action {
+ display: flex;
+ justify-content: flex-end;
+
+ .zone-button {
+ background: linear-gradient(135deg, #FB923C 0%, #F97316 100%);
+ color: #ffffff;
+ padding: 16rpx 32rpx;
+ border-radius: 24rpx;
+ font-size: 24rpx;
+ font-weight: 600;
+ box-shadow: 0 8rpx 24rpx rgba(251, 146, 60, 0.3);
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
+ position: relative;
+ z-index: 10;
+ cursor: pointer;
+
+ &:active {
+ transform: scale(0.95);
+ background: linear-gradient(135deg, #F97316 0%, #EA580C 100%);
+ }
+
+ .button-text {
+ color: #ffffff;
+ pointer-events: none;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ // Bottom Spacing for Fixed Navigation
+ .bottom-spacing {
+ height: 192rpx;
+ }
+
+ // Bottom Navigation with glassmorphism
+ .bottom-nav {
+ position: fixed;
+ z-index: 10;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ background: linear-gradient(180deg, rgba(255, 255, 255, 0.95) 0%, rgba(248, 250, 252, 1) 100%);
+ /* backdrop-filter 在小程序中不支持,使用多层背景模拟 */
+ box-shadow: 0 -2rpx 20rpx rgba(0, 0, 0, 0.08);
+ border-top: 2rpx solid rgba(229, 231, 235, 0.3);
+ padding: 24rpx 32rpx;
+ display: flex;
+ justify-content: space-around;
+
+ // 使用伪元素模拟背景模糊
+ &::before {
+ content: '';
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background: rgba(255, 255, 255, 0.8);
+ z-index: -1;
+ }
+
+ .nav-item {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ padding: 16rpx;
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
+
+ &:active {
+ transform: scale(0.95);
+ }
+
+ .nav-icon-container {
+ width: 64rpx;
+ height: 64rpx;
+ border-radius: 24rpx;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ background: #F3F4F6;
+ margin-bottom: 8rpx;
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
+
+ &.home-active {
+ background: linear-gradient(135deg, #05C7C7 0%, #04B5B5 100%);
+ box-shadow: 0 8rpx 24rpx rgba(5, 199, 199, 0.3);
+
+ .nav-icon {
+ color: #ffffff;
+ }
+
+ .nav-icon-svg {
+ filter: brightness(0) saturate(100%) invert(100%);
+ }
+ }
+
+ &.profile-active {
+ background: linear-gradient(135deg, #05C7C7 0%, #04B5B5 100%);
+ box-shadow: 0 8rpx 24rpx rgba(5, 199, 199, 0.3);
+
+ .nav-icon-svg {
+ filter: brightness(0) saturate(100%) invert(100%);
+ }
+ }
+
+ .nav-icon {
+ font-size: 32rpx;
+ color: #9CA3AF;
+ }
+
+ .nav-icon-svg {
+ width: 32rpx;
+ height: 32rpx;
+ filter: brightness(0) saturate(100%) invert(69%) sepia(5%) saturate(447%) hue-rotate(175deg) brightness(94%) contrast(88%);
+ }
+ }
+
+ .nav-label {
+ font-size: 24rpx;
+ color: #9CA3AF;
+
+ &.active {
+ color: #05C7C7;
+ font-weight: 600;
+ }
+ }
+ }
+ }
+
+ // Bottom Watermark with fade effect
+ .watermark {
+ position: fixed;
+ bottom: 160rpx;
+ left: 50%;
+ transform: translateX(-50%);
+
+ .watermark-text {
+ color: #D1D5DB;
+ font-size: 24rpx;
+ font-weight: 300;
+ letter-spacing: 2rpx;
+ opacity: 0.6;
+ font-style: italic;
+ }
+ }
+}
+
+// Profile Content Styles
+.profile-content {
+ min-height: 100vh;
+ background: #F9FAFB;
+
+ // Profile Section
+ .profile-section {
+ padding: 188rpx 0 24rpx 0x;
+
+ .profile-card {
+ background: #ffffff;
+ padding: 120rpx 32rpx 32rpx 32rpx;
+ margin-bottom: 30rpx;
+
+ .profile-header-info {
+ display: flex;
+ align-items: center;
+ margin-top: 20rpx;
+ margin-bottom: 52rpx;
+
+ .avatar-container {
+ position: relative;
+ margin-right: 32rpx;
+
+ .avatar {
+ width: 120rpx;
+ height: 120rpx;
+ background: linear-gradient(135deg, #05C7C7 0%, #04B5B5 100%);
+ border-radius: 32rpx;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+
+ .avatar-initial {
+ font-size: 48rpx;
+ color: #ffffff;
+ font-weight: 600;
+ }
+ }
+
+ .online-status {
+ position: absolute;
+ bottom: -8rpx;
+ right: -8rpx;
+ width: 48rpx;
+ height: 48rpx;
+ background: #10B981;
+ border-radius: 50%;
+ border: 4rpx solid #ffffff;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+
+ &::after {
+ content: '';
+ width: 16rpx;
+ height: 16rpx;
+ background: #ffffff;
+ border-radius: 50%;
+ }
+ }
+ }
+
+ .profile-info {
+ flex: 1;
+
+ .profile-name-row {
+ display: flex;
+ align-items: center;
+ margin-bottom: 0;
+ line-height: 1.2;
+
+ .profile-name {
+ font-size: 36rpx;
+ font-weight: bold;
+ color: #111827;
+ margin-right: 12rpx;
+ }
+
+ .edit-icon {
+ margin-right: 12rpx;
+ padding: 4rpx;
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
+
+ &:active {
+ opacity: 0.6;
+ transform: scale(0.9);
+ }
+
+ .edit-text {
+ font-size: 28rpx;
+ color: #9CA3AF;
+ }
+ }
+
+ .vip-badge {
+ background: linear-gradient(135deg, #FBBF24 0%, #F59E0B 100%);
+ padding: 4rpx 12rpx;
+ border-radius: 12rpx;
+
+ .vip-text {
+ font-size: 18rpx;
+ color: #ffffff;
+ font-weight: 600;
+ }
+ }
+ }
+
+ .profile-id {
+ font-size: 22rpx;
+ color: #6B7280;
+ margin-bottom: 12rpx;
+ }
+
+ .profile-stats {
+ display: flex;
+ align-items: center;
+
+ .stat-divider {
+ width: 6rpx;
+ height: 6rpx;
+ background: #D1D5DB;
+ border-radius: 50%;
+ margin: 0 20rpx;
+ }
+
+ .stat-text {
+ font-size: 20rpx;
+ color: #6B7280;
+ }
+ }
+ }
+
+
+ }
+
+ .member-card-button {
+ width: 100%;
+ height: 200rpx;
+ position: relative;
+ border-radius: 24rpx;
+ overflow: hidden;
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
+ border: 2rpx solid #F59E0B;
+
+ &:active {
+ transform: scale(0.98);
+ }
+
+ .card-background {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background: linear-gradient(135deg, #FBBF24 0%, #F59E0B 50%, #D97706 100%);
+
+ .card-pattern {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background-image:
+ radial-gradient(circle at 20% 20%, rgba(255, 255, 255, 0.1) 0%, transparent 50%),
+ radial-gradient(circle at 80% 80%, rgba(255, 255, 255, 0.05) 0%, transparent 50%),
+ radial-gradient(circle at 40% 40%, rgba(255, 255, 255, 0.03) 0%, transparent 50%);
+ }
+
+ .card-shine {
+ position: absolute;
+ top: -50%;
+ left: -50%;
+ right: -50%;
+ bottom: -50%;
+ background: linear-gradient(45deg, transparent 40%, rgba(255, 255, 255, 0.05) 50%, transparent 60%);
+ animation: cardShine 5s infinite;
+ }
+ }
+
+ .card-content {
+ position: relative;
+ z-index: 2;
+ height: 100%;
+ padding: 24rpx;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+
+ .card-left {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ justify-content: space-between;
+ height: 100%;
+
+ .card-header {
+ display: flex;
+ align-items: baseline;
+ gap: 12rpx;
+
+ .card-title {
+ font-size: 32rpx;
+ font-weight: bold;
+ color: #ffffff;
+ text-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.2);
+ }
+
+ .card-subtitle {
+ font-size: 20rpx;
+ color: rgba(255, 255, 255, 0.9);
+ font-weight: 500;
+ }
+ }
+
+ .card-info {
+ display: flex;
+ flex-direction: column;
+ gap: 8rpx;
+
+ .member-level {
+ font-size: 24rpx;
+ color: #ffffff;
+ font-weight: 600;
+ text-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.2);
+ }
+
+ .member-expiry {
+ font-size: 20rpx;
+ color: rgba(255, 255, 255, 0.8);
+ font-weight: 500;
+ }
+ }
+ }
+
+ .card-arrow {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 48rpx;
+ height: 48rpx;
+ background: rgba(255, 255, 255, 0.2);
+ border-radius: 50%;
+ margin-left: 16rpx;
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
+
+ .arrow-icon {
+ font-size: 28rpx;
+ color: #ffffff;
+ font-weight: 300;
+ text-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.2);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ // Stats Section
+ .stats-section {
+ padding: 0 24rpx 24rpx 24rpx;
+
+ .stats-grid {
+ display: grid;
+ grid-template-columns: repeat(3, 1fr);
+ gap: 24rpx;
+
+ .stat-card {
+ background: #ffffff;
+ border-radius: 24rpx;
+ padding: 24rpx;
+ text-align: center;
+ border: 2rpx solid #F3F4F6;
+
+ .stat-number {
+ font-size: 48rpx;
+ font-weight: bold;
+ color: #111827;
+ display: block;
+ }
+
+ .stat-label {
+ font-size: 24rpx;
+ color: #6B7280;
+ margin-top: -5rpx;
+ display: block;
+ }
+ }
+ }
+ }
+
+ // Menu Section
+ .menu-section {
+ padding: 0 24rpx 24rpx 24rpx;
+
+ .menu-card {
+ background: #ffffff;
+ border-radius: 32rpx;
+ border: 2rpx solid #F3F4F6;
+ overflow: hidden;
+
+ .menu-item {
+ display: flex;
+ align-items: center;
+ padding: 32rpx;
+ border-bottom: 2rpx solid #F3F4F6;
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
+
+ &:last-child {
+ border-bottom: none;
+ }
+
+ &:active {
+ background: #F9FAFB;
+ }
+
+ .menu-content {
+ flex: 1;
+
+ .menu-title {
+ font-size: 28rpx;
+ font-weight: 500;
+ color: #111827;
+ }
+
+ .vip-subtitle {
+ margin-top: 6rpx;
+
+ .vip-subtitle-text {
+ background: linear-gradient(135deg, #FBBF24 0%, #F59E0B 100%);
+ color: #ffffff;
+ padding: 4rpx 16rpx;
+ border-radius: 16rpx;
+ font-size: 20rpx;
+ font-weight: 500;
+ display: inline-block;
+ }
+ }
+ }
+
+ .menu-right {
+ display: flex;
+ align-items: center;
+
+ .badge {
+ background: #EF4444;
+ color: #ffffff;
+ padding: 4rpx 12rpx;
+ border-radius: 16rpx;
+ margin-right: 12rpx;
+
+ .badge-text {
+ font-size: 18rpx;
+ font-weight: 500;
+ }
+ }
+
+ .notification-dot {
+ width: 12rpx;
+ height: 12rpx;
+ background: #EF4444;
+ border-radius: 50%;
+ margin-right: 12rpx;
+ }
+ }
+
+ .menu-arrow {
+ font-size: 28rpx;
+ color: #9CA3AF;
+ font-weight: 300;
+ }
+
+ &.vip-item {
+ .menu-arrow {
+ color: #FBBF24;
+ }
+
+ &:active {
+ background: rgba(254, 243, 199, 0.3);
+ }
+ }
+ }
+ }
+ }
+
+ // Activity Section
+ .activity-section {
+ padding: 0 24rpx 24rpx 24rpx;
+
+ .activity-card {
+ background: #ffffff;
+ border-radius: 32rpx;
+ padding: 32rpx;
+ border: 2rpx solid #F3F4F6;
+
+ .activity-header {
+ display: flex;
+ align-items: center;
+ margin-bottom: 24rpx;
+
+ .activity-indicator {
+ width: 6rpx;
+ height: 32rpx;
+ background: linear-gradient(135deg, #05C7C7 0%, #04B5B5 100%);
+ border-radius: 3rpx;
+ margin-right: 16rpx;
+ }
+
+ .activity-title {
+ font-size: 28rpx;
+ font-weight: bold;
+ color: #111827;
+ }
+ }
+
+ .activity-list {
+ .activity-item {
+ display: flex;
+ align-items: flex-start;
+ padding: 20rpx;
+ background: #F9FAFB;
+ border-radius: 24rpx;
+ margin-bottom: 12rpx;
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
+
+ &:last-child {
+ margin-bottom: 0;
+ }
+
+ &:active {
+ background: #F3F4F6;
+ }
+
+ .activity-content {
+ flex: 1;
+
+ .activity-title-text {
+ font-size: 24rpx;
+ font-weight: 500;
+ color: #111827;
+ display: block;
+ margin-bottom: 6rpx;
+ line-height: 1.4;
+ }
+
+ .activity-subtitle {
+ font-size: 20rpx;
+ color: #6B7280;
+ line-height: 1.3;
+ }
+ }
+
+ .activity-points {
+ font-size: 20rpx;
+ color: #05C7C7;
+ font-weight: 600;
+ background: #F0FDFD;
+ padding: 6rpx 12rpx;
+ border-radius: 12rpx;
+ border: 1rpx solid #CCFBF1;
+ white-space: nowrap;
+ margin-top: 2rpx;
+ }
+ }
+ }
+ }
+ }
+}
+
+// Animations
+@keyframes pulse {
+ 0%, 100% {
+ opacity: 1;
+ }
+ 50% {
+ opacity: 0.5;
+ }
+}
+
+@keyframes cardShine {
+ 0% {
+ transform: translateX(-100%) translateY(-100%) rotate(45deg);
+ }
+ 50% {
+ transform: translateX(50%) translateY(50%) rotate(45deg);
+ }
+ 100% {
+ transform: translateX(200%) translateY(200%) rotate(45deg);
+ }
+}
+
+// Responsive Design
+@media (max-width: 480px) {
+ .limo-mobile {
+ .header {
+ padding: 68rpx 24rpx 24rpx 24rpx; /* 调整小屏幕的状态栏空间 */
+
+ .header-content {
+ .logo {
+ height: 56rpx;
+ }
+
+ .energy-button {
+ width: 72rpx;
+ height: 72rpx;
+ border-radius: 20rpx;
+
+ .energy-icon {
+ font-size: 28rpx;
+ }
+ }
+ }
+
+ .nav-tabs {
+ margin-top: 40rpx;
+ gap: 48rpx;
+
+ .tab-item .tab-text {
+ font-size: 28rpx;
+ }
+ }
+ }
+
+ .sports-section {
+ padding: 30rpx 24rpx;
+
+ .sports-container {
+ gap: 32rpx;
+
+ .sport-item {
+ &.all-category .category-content {
+ padding: 20rpx 24rpx;
+ min-width: 120rpx;
+ font-size: 24rpx;
+ }
+
+ .sport-icon-container {
+ width: 96rpx;
+ height: 96rpx;
+ border-radius: 28rpx;
+
+ .sport-emoji {
+ font-size: 40rpx;
+ }
+ }
+
+ .sport-label {
+ font-size: 22rpx;
+ }
+ }
+ }
+ }
+
+ .location-bar {
+ padding: 0 24rpx 40rpx;
+
+ .location-content {
+ padding: 20rpx;
+ font-size: 26rpx;
+
+ .location-switch {
+ font-size: 22rpx;
+ }
+ }
+ }
+
+ .venue-section {
+ padding: 0 24rpx;
+
+ .venue-card {
+ margin-bottom: 64rpx;
+
+ .venue-meta {
+ margin-left: 72rpx;
+ }
+
+ .venue-content {
+ margin-left: 72rpx;
+
+ .venue-image {
+ height: 320rpx;
+
+ .image-placeholder .placeholder-text {
+ font-size: 32rpx;
+ }
+ }
+
+ .venue-info {
+ padding: 32rpx;
+
+ .venue-header {
+ .venue-logo {
+ width: 80rpx;
+ height: 80rpx;
+ border-radius: 20rpx;
+
+ .logo-text {
+ font-size: 32rpx;
+ }
+ }
+
+ .venue-details {
+ .venue-name {
+ font-size: 28rpx;
+ }
+
+ .venue-address {
+ font-size: 22rpx;
+ }
+ }
+ }
+
+ .venue-action .zone-button {
+ padding: 16rpx 32rpx;
+ font-size: 24rpx;
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/client/src/pages/index/index.vue b/client/src/pages/index/index.vue
new file mode 100644
index 0000000..bc36d37
--- /dev/null
+++ b/client/src/pages/index/index.vue
@@ -0,0 +1,442 @@
+
+
+
+
+
+
+
+
+
+
+
+
+ 全部
+ 项目
+
+
+
+
+ 🏀
+
+ 篮球
+
+
+
+ 足球
+
+
+
+ 🎾
+
+ 网球
+
+
+
+
+
+
+
+ 📍
+ 杭州市文一西路未来科技城万利大厦0701
+ 切换
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 120km
+
+
+ 🕐
+ 2分钟前更新
+
+
+
+
+
+ RONIN篮球馆
+
+
+
+ ▶
+
+
+
+
+ 直播中
+
+
+
+
+
+
+ 进入ZONE
+
+
+
+
+
+
+
+
+
+
+ 140km
+
+
+ 🕐
+ 5分钟前更新
+
+
+
+
+
+ Panda运动俱乐部
+
+
+
+ ▶
+
+
+
+
+ 直播中
+
+
+
+
+
+
+ 进入ZONE
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ VIP黄金会员
+ 有效期至 2025.12.31
+
+
+
+ ›
+
+
+
+
+
+
+
+
+
+
+ 12
+ 观看场次
+
+
+ 5
+ 收藏场馆
+
+
+ 28
+ 观看时长(h)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 观看了 RONIN黄金篮球馆
+ 2小时前 · 观看时长 45分钟
+
+ +10
+
+
+
+ 收藏了 Panda惊怒熊猫运动俱乐部
+ 1天前
+
+ +5
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 首页
+
+
+
+
+
+ 我的
+
+
+
+
+
+
diff --git a/client/src/pages/vip/index.config.ts b/client/src/pages/vip/index.config.ts
new file mode 100644
index 0000000..ab32f7f
--- /dev/null
+++ b/client/src/pages/vip/index.config.ts
@@ -0,0 +1,4 @@
+export default definePageConfig({
+ navigationBarTitleText: 'VIP会员',
+ navigationStyle: 'custom'
+})
\ No newline at end of file
diff --git a/client/src/pages/vip/index.scss b/client/src/pages/vip/index.scss
new file mode 100644
index 0000000..c20b4f6
--- /dev/null
+++ b/client/src/pages/vip/index.scss
@@ -0,0 +1,768 @@
+.vip-page {
+ min-height: 100vh;
+ background: linear-gradient(135deg, #fef7cd 0%, #fed7aa 50%, #fecaca 100%);
+
+ // Header
+ .vip-header {
+ background: linear-gradient(135deg, #FBBF24 0%, #F59E0B 50%, #EF4444 100%);
+ padding: 88rpx 32rpx 32rpx 32rpx;
+ position: sticky;
+ top: 0;
+ z-index: 10;
+ box-shadow: 0 8rpx 32rpx rgba(251, 191, 36, 0.4);
+
+ .header-content {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+
+ .back-button {
+ width: 80rpx;
+ height: 80rpx;
+ background: rgba(255, 255, 255, 0.2);
+ border-radius: 32rpx;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
+
+ &:active {
+ transform: scale(0.95);
+ }
+
+ .back-icon {
+ width: 40rpx;
+ height: 40rpx;
+ filter: brightness(0) saturate(100%) invert(100%);
+ }
+ }
+
+ .header-title {
+ font-size: 36rpx;
+ font-weight: bold;
+ color: #ffffff;
+ text-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.2);
+ }
+
+ .placeholder {
+ width: 80rpx;
+ height: 80rpx;
+ }
+ }
+ }
+
+ // VIP Banner
+ .vip-banner {
+ background: linear-gradient(135deg, #FBBF24 0%, #F59E0B 50%, #EF4444 100%);
+ padding: 0 32rpx 96rpx 32rpx;
+ position: relative;
+ overflow: hidden;
+
+ // Background decorations
+ .decoration {
+ position: absolute;
+ border-radius: 50%;
+ background: rgba(255, 255, 255, 0.1);
+
+ &.decoration-1 {
+ width: 256rpx;
+ height: 256rpx;
+ top: 0;
+ left: 0;
+ transform: translate(-128rpx, -128rpx);
+ }
+
+ &.decoration-2 {
+ width: 192rpx;
+ height: 192rpx;
+ top: 160rpx;
+ right: 0;
+ transform: translateX(96rpx);
+ }
+
+ &.decoration-3 {
+ width: 320rpx;
+ height: 320rpx;
+ bottom: 0;
+ left: 50%;
+ transform: translate(160rpx, 160rpx);
+ background: rgba(255, 255, 255, 0.05);
+ }
+ }
+
+ .banner-content {
+ text-align: center;
+ position: relative;
+ z-index: 10;
+
+ .crown-container {
+ width: 192rpx;
+ height: 192rpx;
+ background: rgba(255, 255, 255, 0.2);
+ border-radius: 50%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ margin: 0 auto 32rpx;
+ box-shadow: 0 16rpx 64rpx rgba(0, 0, 0, 0.2);
+
+ .crown-icon {
+ font-size: 96rpx;
+ }
+ }
+
+ .banner-title {
+ font-size: 56rpx;
+ font-weight: bold;
+ color: #ffffff;
+ display: block;
+ margin-bottom: 16rpx;
+ text-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.2);
+ }
+
+ .banner-subtitle {
+ font-size: 28rpx;
+ color: rgba(255, 255, 255, 0.9);
+ display: block;
+ margin-bottom: 48rpx;
+ text-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.2);
+ }
+
+ .stats-container {
+ display: flex;
+ justify-content: center;
+ gap: 64rpx;
+
+ .stat-item {
+ text-align: center;
+
+ .stat-icon-container {
+ width: 80rpx;
+ height: 80rpx;
+ background: rgba(255, 255, 255, 0.2);
+ border-radius: 24rpx;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ margin: 0 auto 8rpx;
+
+ .stat-icon {
+ font-size: 40rpx;
+ }
+ }
+
+ .stat-value {
+ font-size: 28rpx;
+ font-weight: bold;
+ color: #ffffff;
+ display: block;
+ margin-bottom: 4rpx;
+ text-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.2);
+ }
+
+ .stat-label {
+ font-size: 20rpx;
+ color: rgba(255, 255, 255, 0.8);
+ text-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.2);
+ }
+ }
+ }
+ }
+ }
+
+ // Pricing Section
+ .pricing-section {
+ padding: 0 32rpx;
+ margin-top: -64rpx;
+ position: relative;
+ z-index: 10;
+
+ .pricing-card {
+ background: rgba(255, 255, 255, 0.9);
+ border-radius: 48rpx;
+ padding: 48rpx;
+ box-shadow: 0 32rpx 96rpx rgba(0, 0, 0, 0.1);
+ border: 2rpx solid rgba(255, 255, 255, 0.5);
+
+ // 模拟 backdrop-blur 效果
+ &::before {
+ content: '';
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background: rgba(255, 255, 255, 0.4);
+ border-radius: 48rpx;
+ z-index: -1;
+ }
+
+ position: relative;
+
+ .pricing-header {
+ text-align: center;
+ margin-bottom: 48rpx;
+
+ .pricing-title {
+ font-size: 36rpx;
+ font-weight: bold;
+ color: #111827;
+ display: block;
+ margin-bottom: 16rpx;
+ }
+
+ .pricing-subtitle {
+ font-size: 24rpx;
+ color: #6B7280;
+ }
+ }
+
+ .plans-grid {
+ display: grid;
+ grid-template-columns: repeat(2, 1fr);
+ gap: 32rpx;
+
+ .plan-item {
+ position: relative;
+ border: 4rpx solid rgba(229, 231, 235, 0.5);
+ border-radius: 48rpx;
+ padding: 40rpx;
+ text-align: center;
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
+ background: rgba(255, 255, 255, 0.5);
+
+ &:active {
+ transform: scale(0.98);
+ }
+
+ &.selected {
+ border-color: transparent;
+ background: linear-gradient(135deg, rgba(5, 199, 199, 0.1) 0%, rgba(4, 181, 181, 0.1) 100%);
+ box-shadow: 0 24rpx 64rpx rgba(0, 0, 0, 0.12);
+ transform: scale(1.02);
+ }
+
+ &.popular {
+ border-color: rgba(251, 191, 36, 0.5);
+ }
+
+ .popular-badge {
+ position: absolute;
+ top: -24rpx;
+ left: 50%;
+ transform: translateX(-50%);
+ background: linear-gradient(135deg, #FBBF24 0%, #F59E0B 100%);
+ color: #ffffff;
+ padding: 8rpx 32rpx;
+ border-radius: 24rpx;
+ box-shadow: 0 8rpx 24rpx rgba(251, 191, 36, 0.3);
+
+ .popular-text {
+ font-size: 20rpx;
+ font-weight: bold;
+ }
+ }
+
+ .plan-content {
+ .plan-icon-container {
+ width: 96rpx;
+ height: 96rpx;
+ border-radius: 32rpx;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ margin: 0 auto 24rpx;
+ box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.1);
+
+ &.blue-gradient {
+ background: linear-gradient(135deg, #3B82F6 0%, #2563EB 100%);
+ }
+
+ &.purple-gradient {
+ background: linear-gradient(135deg, #8B5CF6 0%, #7C3AED 100%);
+ }
+
+ &.pink-gradient {
+ background: linear-gradient(135deg, #EC4899 0%, #DB2777 100%);
+ }
+
+ &.orange-gradient {
+ background: linear-gradient(135deg, #F97316 0%, #EA580C 100%);
+ }
+
+ .plan-icon {
+ font-size: 48rpx;
+ }
+ }
+
+ .plan-name {
+ font-size: 28rpx;
+ font-weight: bold;
+ color: #111827;
+ display: block;
+ margin-bottom: 8rpx;
+ }
+
+ .plan-duration {
+ font-size: 20rpx;
+ color: #6B7280;
+ display: block;
+ margin-bottom: 24rpx;
+ }
+
+ .price-container {
+ margin-bottom: 24rpx;
+
+ .price {
+ font-size: 48rpx;
+ font-weight: bold;
+ color: #05C7C7;
+ margin-right: 16rpx;
+ }
+
+ .original-price {
+ font-size: 20rpx;
+ color: #9CA3AF;
+ text-decoration: line-through;
+ }
+ }
+
+ .discount-badge {
+ background: linear-gradient(135deg, #FEF2F2 0%, #FECACA 100%);
+ color: #DC2626;
+ padding: 8rpx 24rpx;
+ border-radius: 24rpx;
+ border: 2rpx solid rgba(220, 38, 38, 0.2);
+
+ .discount-text {
+ font-size: 20rpx;
+ font-weight: bold;
+ }
+ }
+ }
+
+ .selected-indicator {
+ position: absolute;
+ top: 24rpx;
+ right: 24rpx;
+ width: 48rpx;
+ height: 48rpx;
+ background: linear-gradient(135deg, #05C7C7 0%, #04B5B5 100%);
+ border-radius: 50%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ box-shadow: 0 8rpx 24rpx rgba(5, 199, 199, 0.3);
+
+ .check-icon {
+ font-size: 32rpx;
+ color: #ffffff;
+ font-weight: bold;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ // Privileges Section
+ .privileges-section {
+ padding: 64rpx 32rpx 0;
+
+ .privileges-card {
+ background: rgba(255, 255, 255, 0.9);
+ border-radius: 48rpx;
+ padding: 48rpx;
+ box-shadow: 0 32rpx 96rpx rgba(0, 0, 0, 0.1);
+ border: 2rpx solid rgba(255, 255, 255, 0.5);
+ position: relative;
+
+ // 模拟 backdrop-blur 效果
+ &::before {
+ content: '';
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background: rgba(255, 255, 255, 0.4);
+ border-radius: 48rpx;
+ z-index: -1;
+ }
+
+ .privileges-header {
+ .privileges-title-container {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ margin-bottom: 48rpx;
+
+ .privileges-icon-container {
+ width: 64rpx;
+ height: 64rpx;
+ background: linear-gradient(135deg, #FBBF24 0%, #F59E0B 100%);
+ border-radius: 32rpx;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ margin-right: 24rpx;
+ box-shadow: 0 8rpx 24rpx rgba(251, 191, 36, 0.3);
+
+ .privileges-icon {
+ font-size: 40rpx;
+ }
+ }
+
+ .privileges-title {
+ font-size: 36rpx;
+ font-weight: bold;
+ color: #111827;
+ }
+ }
+ }
+
+ .privileges-list {
+ .privilege-item {
+ display: flex;
+ align-items: flex-start;
+ padding: 32rpx;
+ background: linear-gradient(135deg, rgba(249, 250, 251, 0.5) 0%, rgba(255, 255, 255, 0.5) 100%);
+ border-radius: 32rpx;
+ border: 2rpx solid rgba(243, 244, 246, 0.5);
+ margin-bottom: 32rpx;
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
+
+ &:last-child {
+ margin-bottom: 0;
+ }
+
+ &:active {
+ transform: scale(0.98);
+ box-shadow: 0 16rpx 48rpx rgba(0, 0, 0, 0.08);
+ }
+
+ .privilege-icon-container {
+ width: 96rpx;
+ height: 96rpx;
+ border-radius: 32rpx;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ margin-right: 32rpx;
+ flex-shrink: 0;
+ box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.1);
+
+ &.yellow-gradient {
+ background: linear-gradient(135deg, #FBBF24 0%, #F59E0B 100%);
+ }
+
+ &.blue-gradient {
+ background: linear-gradient(135deg, #3B82F6 0%, #2563EB 100%);
+ }
+
+ &.green-gradient {
+ background: linear-gradient(135deg, #10B981 0%, #059669 100%);
+ }
+
+ &.purple-gradient {
+ background: linear-gradient(135deg, #8B5CF6 0%, #7C3AED 100%);
+ }
+
+ &.pink-gradient {
+ background: linear-gradient(135deg, #EC4899 0%, #DB2777 100%);
+ }
+
+ &.indigo-gradient {
+ background: linear-gradient(135deg, #6366F1 0%, #4F46E5 100%);
+ }
+
+ .privilege-icon {
+ font-size: 48rpx;
+ }
+ }
+
+ .privilege-content {
+ flex: 1;
+
+ .privilege-title {
+ font-size: 28rpx;
+ font-weight: bold;
+ color: #111827;
+ display: block;
+ margin-bottom: 8rpx;
+ }
+
+ .privilege-desc {
+ font-size: 24rpx;
+ color: #6B7280;
+ line-height: 1.5;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ // Testimonials Section
+ .testimonials-section {
+ padding: 64rpx 32rpx 0;
+
+ .testimonials-card {
+ background: rgba(255, 255, 255, 0.9);
+ border-radius: 48rpx;
+ padding: 48rpx;
+ box-shadow: 0 32rpx 96rpx rgba(0, 0, 0, 0.1);
+ border: 2rpx solid rgba(255, 255, 255, 0.5);
+ position: relative;
+
+ // 模拟 backdrop-blur 效果
+ &::before {
+ content: '';
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background: rgba(255, 255, 255, 0.4);
+ border-radius: 48rpx;
+ z-index: -1;
+ }
+
+ .testimonials-title {
+ font-size: 32rpx;
+ font-weight: bold;
+ color: #111827;
+ text-align: center;
+ margin-bottom: 32rpx;
+ }
+
+ .testimonials-list {
+ .testimonial-item {
+ padding: 32rpx;
+ border-radius: 32rpx;
+ border: 2rpx solid;
+ margin-bottom: 32rpx;
+
+ &:last-child {
+ margin-bottom: 0;
+ }
+
+ &.blue {
+ background: linear-gradient(135deg, #EFF6FF 0%, #DBEAFE 100%);
+ border-color: rgba(59, 130, 246, 0.2);
+ }
+
+ &.green {
+ background: linear-gradient(135deg, #F0FDF4 0%, #DCFCE7 100%);
+ border-color: rgba(16, 185, 129, 0.2);
+ }
+
+ .testimonial-header {
+ display: flex;
+ align-items: center;
+ margin-bottom: 16rpx;
+
+ .user-avatar {
+ width: 64rpx;
+ height: 64rpx;
+ border-radius: 50%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ margin-right: 16rpx;
+
+ &.blue-avatar {
+ background: linear-gradient(135deg, #3B82F6 0%, #2563EB 100%);
+ }
+
+ &.green-avatar {
+ background: linear-gradient(135deg, #10B981 0%, #059669 100%);
+ }
+
+ .avatar-text {
+ font-size: 24rpx;
+ font-weight: bold;
+ color: #ffffff;
+ }
+ }
+
+ .user-name {
+ font-size: 26rpx;
+ font-weight: 600;
+ color: #111827;
+ margin-right: 16rpx;
+ }
+
+ .rating {
+ display: flex;
+ gap: 4rpx;
+
+ .star {
+ font-size: 32rpx;
+ }
+ }
+ }
+
+ .testimonial-text {
+ font-size: 24rpx;
+ color: #6B7280;
+ line-height: 1.5;
+ }
+ }
+ }
+ }
+ }
+
+ // Purchase Section
+ .purchase-section {
+ padding: 64rpx 32rpx 0;
+
+ .purchase-card {
+ background: rgba(255, 255, 255, 0.9);
+ border-radius: 48rpx;
+ padding: 48rpx;
+ box-shadow: 0 32rpx 96rpx rgba(0, 0, 0, 0.1);
+ border: 2rpx solid rgba(255, 255, 255, 0.5);
+ position: relative;
+
+ // 模拟 backdrop-blur 效果
+ &::before {
+ content: '';
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background: rgba(255, 255, 255, 0.4);
+ border-radius: 48rpx;
+ z-index: -1;
+ }
+
+ .selected-plan-info {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 32rpx;
+ background: linear-gradient(135deg, #F9FAFB 0%, #FFFFFF 100%);
+ border-radius: 32rpx;
+ border: 2rpx solid rgba(243, 244, 246, 0.5);
+ margin-bottom: 48rpx;
+
+ .selected-label {
+ font-size: 26rpx;
+ font-weight: 500;
+ color: #6B7280;
+ }
+
+ .selected-details {
+ text-align: right;
+
+ .selected-name {
+ font-size: 26rpx;
+ font-weight: bold;
+ color: #111827;
+ display: block;
+ margin-bottom: 4rpx;
+ }
+
+ .selected-price {
+ font-size: 36rpx;
+ font-weight: bold;
+ color: #05C7C7;
+ }
+ }
+ }
+
+ .purchase-button {
+ width: 100%;
+ background: linear-gradient(135deg, #FBBF24 0%, #F59E0B 50%, #EF4444 100%);
+ padding: 40rpx;
+ border-radius: 32rpx;
+ text-align: center;
+ box-shadow: 0 16rpx 64rpx rgba(251, 146, 60, 0.4);
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
+ position: relative;
+ overflow: hidden;
+ margin-bottom: 32rpx;
+
+ &:active {
+ transform: scale(0.98);
+ }
+
+ // 悬停光泽效果
+ &::before {
+ content: '';
+ position: absolute;
+ top: 0;
+ left: -100%;
+ width: 100%;
+ height: 100%;
+ background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
+ transition: left 0.5s;
+ }
+
+ &:active::before {
+ left: 100%;
+ }
+
+ .purchase-icon {
+ font-size: 48rpx;
+ margin-right: 16rpx;
+ }
+
+ .purchase-text {
+ font-size: 28rpx;
+ font-weight: bold;
+ color: #ffffff;
+ text-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.2);
+ }
+ }
+
+ .purchase-features {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: 32rpx;
+ margin-bottom: 32rpx;
+
+ .feature-item {
+ display: flex;
+ align-items: center;
+ gap: 8rpx;
+
+ .feature-icon {
+ font-size: 24rpx;
+ }
+
+ .feature-text {
+ font-size: 24rpx;
+ color: #9CA3AF;
+ }
+ }
+
+ .feature-divider {
+ width: 8rpx;
+ height: 8rpx;
+ background: #D1D5DB;
+ border-radius: 50%;
+ }
+
+ .feature-text {
+ font-size: 24rpx;
+ color: #9CA3AF;
+ }
+ }
+
+ .agreement-text {
+ font-size: 24rpx;
+ color: #9CA3AF;
+ text-align: center;
+ }
+ }
+ }
+
+ // Bottom Spacing
+ .bottom-spacing {
+ height: 64rpx;
+ }
+}
\ No newline at end of file
diff --git a/client/src/pages/vip/index.vue b/client/src/pages/vip/index.vue
new file mode 100644
index 0000000..f33b3a1
--- /dev/null
+++ b/client/src/pages/vip/index.vue
@@ -0,0 +1,326 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 👑
+
+ LIMO VIP会员
+ 开启专属运动体验
+
+
+
+
+
+ 👥
+
+ 50万+
+ 活跃用户
+
+
+
+ ⭐
+
+ 98%
+ 满意度
+
+
+
+ 🏆
+
+ 1000+
+ 场馆覆盖
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ✨ 最受欢迎
+
+
+
+
+ 👑
+
+
+ {{ plan.name }}
+ {{ plan.duration }}
+
+
+ ¥{{ plan.price }}
+ ¥{{ plan.originalPrice }}
+
+
+
+ {{ plan.discount }}
+
+
+
+
+ ✓
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ privilege.icon }}
+
+
+ {{ privilege.title }}
+ {{ privilege.desc }}
+
+
+
+
+
+
+
+
+
+ 用户好评
+
+
+
+ "VIP服务真的很棒,预约场馆再也不用排队了!"
+
+
+
+ "专属客服响应很快,解决问题很及时,值得推荐!"
+
+
+
+
+
+
+
+
+
+ 选择套餐:
+
+ {{ selectedPlanInfo.name }}
+ ¥{{ selectedPlanInfo.price }}
+
+
+
+
+ 👑
+ 立即开通VIP会员
+
+
+
+
+ 🛡️
+ 安全支付
+
+
+
+ ✓
+ 随时取消
+
+
+ 7天无理由退款
+
+
+ 开通即表示同意《VIP会员服务协议》
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/client/src/pages/zone/index.config.ts b/client/src/pages/zone/index.config.ts
new file mode 100644
index 0000000..3122920
--- /dev/null
+++ b/client/src/pages/zone/index.config.ts
@@ -0,0 +1,5 @@
+export default definePageConfig({
+ navigationBarTitleText: '场馆ZONE',
+ navigationBarTextStyle: 'black',
+ navigationStyle: 'custom'
+})
\ No newline at end of file
diff --git a/client/src/pages/zone/index.scss b/client/src/pages/zone/index.scss
new file mode 100644
index 0000000..ffa1704
--- /dev/null
+++ b/client/src/pages/zone/index.scss
@@ -0,0 +1,790 @@
+.zone-venue {
+ min-height: 100vh;
+ background: linear-gradient(135deg, #f9fafb 0%, #ffffff 100%);
+ position: relative;
+
+ // Hero Header with Premium Background
+ .hero-header {
+ position: relative;
+ height: 400rpx;
+ overflow: hidden;
+
+ .hero-bg {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background: linear-gradient(135deg, #1f2937 0%, #3b82f6 50%, #ef4444 100%);
+ z-index: 1;
+ }
+
+ .hero-overlay {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background: rgba(0, 0, 0, 0.4);
+ z-index: 2;
+ }
+
+ .hero-gradient {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background: linear-gradient(180deg, rgba(0, 0, 0, 0.2) 0%, transparent 40%, rgba(0, 0, 0, 0.6) 100%);
+ z-index: 3;
+ }
+
+ // Header Controls
+ .header-controls {
+ position: relative;
+ z-index: 10;
+ padding: 60rpx 24rpx 24rpx;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+
+ .control-btn {
+ width: 64rpx;
+ height: 64rpx;
+ background: rgba(0, 0, 0, 0.3);
+ border-radius: 24rpx;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
+
+ &:active {
+ transform: scale(0.95);
+ }
+
+ .control-icon {
+ width: 32rpx;
+ height: 32rpx;
+ filter: brightness(0) saturate(100%) invert(100%);
+ }
+ }
+
+ .control-group {
+ display: flex;
+ gap: 24rpx;
+ }
+ }
+
+ // Venue Info Card with Glassmorphism
+ .venue-info-card {
+ position: absolute;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ z-index: 10;
+ background: rgba(255, 255, 255, 0.9);
+ border-radius: 48rpx 48rpx 0 0;
+ border-top: 2rpx solid rgba(255, 255, 255, 0.2);
+
+ // 模拟 backdrop-blur
+ &::before {
+ content: '';
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background: rgba(255, 255, 255, 0.4);
+ border-radius: 48rpx 48rpx 0 0;
+ z-index: -1;
+ }
+
+ .venue-card-inner {
+ padding: 32rpx 32rpx 72rpx 32rpx;
+ display: flex;
+ align-items: center;
+ gap: 24rpx;
+
+ .venue-avatar-container {
+ position: relative;
+
+ .venue-avatar {
+ width: 120rpx;
+ height: 120rpx;
+ border-radius: 32rpx;
+ object-fit: cover;
+ box-shadow: 0 12rpx 36rpx rgba(0, 0, 0, 0.15);
+ }
+
+ .online-indicator {
+ position: absolute;
+ top: -6rpx;
+ right: -6rpx;
+ width: 36rpx;
+ height: 36rpx;
+ background: #10b981;
+ border-radius: 50%;
+ border: 3rpx solid #ffffff;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+
+ .online-dot {
+ width: 12rpx;
+ height: 12rpx;
+ background: #ffffff;
+ border-radius: 50%;
+ animation: pulse 2s infinite;
+ }
+ }
+ }
+
+ .venue-details {
+ flex: 1;
+
+ .venue-title {
+ font-size: 36rpx;
+ font-weight: bold;
+ color: #111827;
+ margin-bottom: 6rpx;
+ display: block;
+ }
+
+ .venue-address {
+ display: flex;
+ align-items: center;
+ margin-bottom: 12rpx;
+
+ .address-icon {
+ color: #05c7c7;
+ font-size: 24rpx;
+ margin-right: 6rpx;
+ }
+
+ .address-text {
+ font-size: 20rpx;
+ color: #6b7280;
+ }
+ }
+ }
+
+ .follow-button {
+ background: linear-gradient(135deg, #05c7c7 0%, #04b5b5 100%);
+ border-radius: 20rpx;
+ padding: 12rpx 24rpx;
+ box-shadow: 0 8rpx 24rpx rgba(5, 199, 199, 0.3);
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
+
+ &:active {
+ transform: scale(0.95);
+ }
+
+ .follow-text {
+ font-size: 24rpx;
+ color: #ffffff;
+ font-weight: 600;
+ }
+ }
+
+ .venue-stats {
+ display: flex;
+ align-items: center;
+ gap: 32rpx;
+
+ .stat-item {
+ display: flex;
+ align-items: center;
+ gap: 8rpx;
+
+ .stat-icon {
+ font-size: 24rpx;
+ }
+
+ .stat-text {
+ font-size: 24rpx;
+ color: #6b7280;
+ }
+ }
+
+ .stat-divider {
+ width: 8rpx;
+ height: 8rpx;
+ background: #d1d5db;
+ border-radius: 50%;
+ }
+ }
+ }
+ }
+ }
+
+ // Main Video Player
+ .main-video-section {
+ padding: 48rpx 24rpx 0;
+
+ border-radius: 40rpx 40rpx 0 0;
+ position: relative;
+ z-index: 10;
+ margin-top: -40rpx;
+ background: #fff;
+
+ .video-player-card {
+ background: rgba(255, 255, 255, 0.8);
+ border-radius: 32rpx;
+ overflow: hidden;
+ box-shadow: 0 24rpx 72rpx rgba(0, 0, 0, 0.08);
+ border: 2rpx solid rgba(229, 231, 235, 0.5);
+
+ // 模拟 backdrop-blur
+ &::before {
+ content: '';
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background: rgba(255, 255, 255, 0.2);
+ border-radius: 32rpx;
+ z-index: -1;
+ }
+
+ .video-container {
+ position: relative;
+ height: 400rpx;
+ overflow: hidden;
+
+ .video-placeholder {
+ width: 100%;
+ height: 100%;
+ background: linear-gradient(135deg, #374151 0%, #1f2937 100%);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+
+ .placeholder-text {
+ color: #ffffff;
+ font-size: 28rpx;
+ font-weight: 600;
+ }
+ }
+
+ .video-play-overlay {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background: rgba(0, 0, 0, 0.2);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+
+ .play-button-large {
+ width: 120rpx;
+ height: 120rpx;
+ background: rgba(255, 255, 255, 0.9);
+ border-radius: 50%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ box-shadow: 0 24rpx 72rpx rgba(0, 0, 0, 0.2);
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
+
+ &:active {
+ transform: scale(1.1);
+ }
+
+ .play-icon {
+ font-size: 48rpx;
+ color: #374151;
+ margin-left: 6rpx;
+ }
+ }
+ }
+
+ .video-fullscreen-btn {
+ position: absolute;
+ bottom: 24rpx;
+ right: 24rpx;
+ background: rgba(0, 0, 0, 0.3);
+ border-radius: 16rpx;
+ padding: 12rpx;
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
+
+ &:active {
+ transform: scale(1.05);
+ }
+
+ .fullscreen-icon {
+ font-size: 24rpx;
+ color: #ffffff;
+ }
+ }
+
+ .video-dots {
+ position: absolute;
+ bottom: 24rpx;
+ left: 24rpx;
+ display: flex;
+ gap: 12rpx;
+
+ .dot {
+ width: 12rpx;
+ height: 12rpx;
+ background: rgba(255, 255, 255, 0.5);
+ border-radius: 50%;
+
+ &.active {
+ background: #ffffff;
+ }
+ }
+ }
+
+ .live-indicator {
+ position: absolute;
+ top: 24rpx;
+ left: 24rpx;
+ background: rgba(239, 68, 68, 0.9);
+ border-radius: 16rpx;
+ padding: 6rpx 16rpx;
+ display: flex;
+ align-items: center;
+ gap: 12rpx;
+
+ .live-dot {
+ width: 12rpx;
+ height: 12rpx;
+ background: #ffffff;
+ border-radius: 50%;
+ animation: pulse 1.5s infinite;
+ }
+
+ .live-text {
+ font-size: 22rpx;
+ color: #ffffff;
+ font-weight: 600;
+ }
+ }
+ }
+
+ .video-actions {
+ padding: 24rpx;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+
+ .action-group {
+ display: flex;
+ align-items: center;
+ gap: 36rpx;
+
+ .action-item {
+ display: flex;
+ align-items: center;
+ gap: 12rpx;
+
+ .action-icon {
+ font-size: 24rpx;
+ }
+
+ .action-text {
+ font-size: 22rpx;
+ color: #6b7280;
+ font-weight: 500;
+ }
+ }
+ }
+
+ .update-time {
+ font-size: 20rpx;
+ color: #6b7280;
+ }
+ }
+ }
+ }
+
+ // Navigation Tabs
+ .nav-tabs-section {
+ padding: 48rpx 24rpx 0;
+
+ .tab-container {
+ background: rgba(255, 255, 255, 0.8);
+ border-radius: 24rpx;
+ padding: 6rpx;
+ box-shadow: 0 12rpx 36rpx rgba(0, 0, 0, 0.05);
+ border: 2rpx solid rgba(229, 231, 235, 0.3);
+
+ .tab-pills {
+ display: flex;
+
+ .tab-pill {
+ flex: 1;
+ padding: 18rpx 24rpx;
+ border-radius: 18rpx;
+ text-align: center;
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
+
+ &.active {
+ background: linear-gradient(135deg, #05c7c7 0%, #04b5b5 100%);
+ box-shadow: 0 12rpx 36rpx rgba(5, 199, 199, 0.2);
+
+ .tab-text {
+ color: #ffffff;
+ }
+ }
+
+ .tab-text {
+ font-size: 28rpx;
+ font-weight: 600;
+ color: #6b7280;
+ }
+ }
+ }
+ }
+ }
+
+ // Court and Time Selection
+ .selection-section {
+ padding: 36rpx 24rpx 0;
+ display: flex;
+ gap: 16rpx;
+
+ .selection-item {
+ flex: 1;
+ display: flex;
+ align-items: center;
+ gap: 16rpx;
+ background: rgba(255, 255, 255, 0.8);
+ border-radius: 24rpx;
+ padding: 24rpx;
+ box-shadow: 0 12rpx 36rpx rgba(0, 0, 0, 0.05);
+ border: 2rpx solid rgba(229, 231, 235, 0.3);
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
+
+ &:active {
+ transform: scale(1.02);
+ }
+
+ .selection-indicator {
+ width: 24rpx;
+ height: 24rpx;
+ background: linear-gradient(135deg, #fb923c 0%, #f97316 100%);
+ border-radius: 50%;
+ box-shadow: 0 6rpx 18rpx rgba(251, 146, 60, 0.2);
+ }
+
+ .selection-icon {
+ font-size: 24rpx;
+ color: #6b7280;
+ }
+
+ .selection-text {
+ flex: 1;
+ font-size: 22rpx;
+ font-weight: 600;
+ color: #111827;
+ }
+
+ .selection-arrow {
+ font-size: 20rpx;
+ color: #6b7280;
+ }
+ }
+ }
+
+ // Video Grid
+ .video-grid-section {
+ padding: 36rpx 24rpx 0;
+
+ .video-grid {
+ display: grid;
+ grid-template-columns: repeat(2, 1fr);
+ gap: 24rpx;
+
+ .video-grid-item {
+ .grid-video-card {
+ background: rgba(255, 255, 255, 0.8);
+ border-radius: 32rpx;
+ overflow: hidden;
+ box-shadow: 0 12rpx 36rpx rgba(0, 0, 0, 0.08);
+ border: 2rpx solid rgba(229, 231, 235, 0.3);
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
+
+ &:active {
+ transform: scale(1.02);
+ box-shadow: 0 18rpx 48rpx rgba(0, 0, 0, 0.12);
+ }
+
+ .grid-video-container {
+ position: relative;
+ height: 200rpx;
+ overflow: hidden;
+
+ .grid-video-placeholder {
+ width: 100%;
+ height: 100%;
+ background: linear-gradient(135deg, #374151 0%, #1f2937 100%);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+
+ .grid-placeholder-text {
+ color: #ffffff;
+ font-size: 22rpx;
+ font-weight: 600;
+ }
+ }
+
+ .grid-live-badge {
+ position: absolute;
+ top: 12rpx;
+ left: 12rpx;
+ background: rgba(5, 199, 199, 0.9);
+ border-radius: 12rpx;
+ padding: 6rpx 12rpx;
+ display: flex;
+ align-items: center;
+ gap: 6rpx;
+
+ .grid-live-dot {
+ width: 8rpx;
+ height: 8rpx;
+ background: #ffffff;
+ border-radius: 50%;
+ animation: pulse 1.5s infinite;
+ }
+
+ .grid-live-text {
+ font-size: 18rpx;
+ color: #ffffff;
+ font-weight: 600;
+ }
+ }
+
+ .grid-play-overlay {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background: rgba(0, 0, 0, 0.2);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+
+ .grid-play-button {
+ width: 60rpx;
+ height: 60rpx;
+ background: rgba(255, 255, 255, 0.9);
+ border-radius: 50%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
+
+ &:active {
+ transform: scale(1.1);
+ }
+
+ .grid-play-icon {
+ font-size: 24rpx;
+ color: #374151;
+ margin-left: 3rpx;
+ }
+ }
+ }
+
+ .grid-video-avatar {
+ position: absolute;
+ top: 12rpx;
+ right: 12rpx;
+ width: 36rpx;
+ height: 36rpx;
+ background: rgba(255, 255, 255, 0.8);
+ border-radius: 50%;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ // Bottom Notice
+ .notice-section {
+ padding: 48rpx 24rpx 40rpx 24rpx;
+
+ .notice-card {
+ background: rgba(254, 243, 199, 0.8);
+ border: 2rpx solid rgba(251, 191, 36, 0.3);
+ border-radius: 24rpx;
+ padding: 24rpx;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: 16rpx;
+
+ .notice-icon {
+ width: 32rpx;
+ height: 32rpx;
+ border: 3rpx solid #f59e0b;
+ border-radius: 50%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+
+ .notice-text {
+ font-size: 18rpx;
+ color: #f59e0b;
+ font-weight: bold;
+ }
+ }
+
+ .notice-message {
+ font-size: 22rpx;
+ color: #d97706;
+ font-weight: 500;
+ }
+ }
+ }
+
+ // Bottom Actions
+ .bottom-actions {
+ position: fixed;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ background: rgba(255, 255, 255, 1);
+ border-top: 2rpx solid rgba(229, 231, 235, 0.3);
+ padding: 24rpx;
+ display: flex;
+ gap: 24rpx;
+ z-index: 100;
+
+ // 模拟 backdrop-blur
+ &::before {
+ content: '';
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background: rgba(255, 255, 255, 0.8);
+ z-index: -1;
+ }
+
+ .action-btn {
+ border-radius: 24rpx;
+ padding: 24rpx;
+ text-align: center;
+ font-weight: bold;
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
+
+ &:active {
+ transform: scale(1.02);
+ }
+
+ .action-btn-text {
+ font-size: 32rpx;
+ }
+
+ &.primary {
+ flex: 1;
+ background: linear-gradient(135deg, #fb923c 0%, #ef4444 100%);
+ box-shadow: 0 12rpx 36rpx rgba(251, 146, 60, 0.3);
+
+ .action-btn-text {
+ color: #ffffff;
+ }
+ }
+
+ &.secondary {
+ background: transparent;
+
+ .action-btn-text {
+ color: #6b7280;
+ font-size: 26rpx;
+ }
+ }
+ }
+ }
+
+ // Bottom Spacing
+ .bottom-spacing {
+ height: 144rpx;
+ }
+}
+
+// Animations
+@keyframes pulse {
+ 0%, 100% {
+ opacity: 1;
+ }
+ 50% {
+ opacity: 0.5;
+ }
+}
+
+// Responsive Design
+@media (max-width: 480px) {
+ .zone-venue {
+ .hero-header {
+ height: 480rpx;
+
+ .venue-info-card .venue-card-inner {
+ padding: 32rpx;
+ gap: 24rpx;
+
+ .venue-avatar-container .venue-avatar {
+ width: 120rpx;
+ height: 120rpx;
+ border-radius: 32rpx;
+ }
+
+ .venue-details .venue-title {
+ font-size: 40rpx;
+ }
+ }
+ }
+
+ .main-video-section {
+ padding: 48rpx 24rpx 0;
+ border-radius: 40rpx 40rpx 0 0;
+ position: relative;
+ z-index: 10;
+ margin-top: -40rpx;
+
+ .video-player-card .video-container {
+ height: 400rpx;
+
+ .video-play-overlay .play-button-large {
+ width: 120rpx;
+ height: 120rpx;
+
+ .play-icon {
+ font-size: 48rpx;
+ }
+ }
+ }
+ }
+
+ .nav-tabs-section {
+ padding: 48rpx 24rpx 0;
+ }
+
+ .selection-section {
+ padding: 32rpx 24rpx 0;
+ flex-direction: column;
+ }
+
+ .video-grid-section {
+ padding: 32rpx 24rpx 0;
+
+ .video-grid {
+ gap: 24rpx;
+
+ .video-grid-item .grid-video-card .grid-video-container {
+ height: 200rpx;
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/client/src/pages/zone/index.vue b/client/src/pages/zone/index.vue
new file mode 100644
index 0000000..1bac619
--- /dev/null
+++ b/client/src/pages/zone/index.vue
@@ -0,0 +1,260 @@
+
+
+
+
+
+
+
+
+
+
+ Basketball Game
+
+
+
+ ▶
+
+
+
+ ⛶
+
+
+
+
+
+
+
+
+ 直播中
+
+
+
+
+
+
+
+
+
+
+ 精彩瞬间
+
+
+ 直播回放
+
+
+
+
+
+
+
+
+
+ 1号篮球场
+ ▼
+
+
+ 🕐
+ 今日 08:20 ~ 22:00
+ ▼
+
+
+
+
+
+
+
+
+
+
+
+ Live Game
+
+
+
+ 正在播放
+
+
+
+
+
+
+
+
+
+
+
+ Game {{index + 1}}
+
+
+
+
+
+
+
+
+
+
+
+
+ !
+
+ 视频云端保存仅7天,请及时下载
+
+
+
+
+
+
+ 合成剪辑
+
+
+ 创作记录 →
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/client/src/services/request.ts b/client/src/services/request.ts
new file mode 100644
index 0000000..c00b3bb
--- /dev/null
+++ b/client/src/services/request.ts
@@ -0,0 +1,210 @@
+/**
+ * 网络请求服务
+ */
+import Taro from '@tarojs/taro'
+import { API_CONFIG, STATUS_CODE } from '@/constants'
+
+export interface RequestOptions {
+ url: string
+ method?: 'GET' | 'POST' | 'PUT' | 'DELETE'
+ data?: any
+ header?: Record
+ timeout?: number
+ loading?: boolean
+ loadingText?: string
+}
+
+export interface ResponseData {
+ code: number
+ data: T
+ message: string
+}
+
+class RequestService {
+ private baseURL: string
+ private timeout: number
+ private defaultHeaders: Record
+
+ constructor() {
+ this.baseURL = API_CONFIG.BASE_URL
+ this.timeout = API_CONFIG.TIMEOUT
+ this.defaultHeaders = {
+ 'Content-Type': 'application/json'
+ }
+ }
+
+ /**
+ * 显示加载提示
+ */
+ private showLoading(text: string = '加载中...') {
+ Taro.showLoading({
+ title: text,
+ mask: true
+ })
+ }
+
+ /**
+ * 隐藏加载提示
+ */
+ private hideLoading() {
+ Taro.hideLoading()
+ }
+
+ /**
+ * 获取请求头
+ */
+ private getHeaders(customHeaders?: Record) {
+ const token = Taro.getStorageSync('token')
+ const headers = {
+ ...this.defaultHeaders,
+ ...customHeaders
+ }
+
+ if (token) {
+ headers['Authorization'] = `Bearer ${token}`
+ }
+
+ return headers
+ }
+
+ /**
+ * 处理响应
+ */
+ private handleResponse(response: any): Promise> {
+ const { statusCode, data } = response
+
+ if (statusCode === STATUS_CODE.SUCCESS) {
+ if (data.code === STATUS_CODE.SUCCESS) {
+ return Promise.resolve(data)
+ } else {
+ // 业务错误
+ Taro.showToast({
+ title: data.message || '请求失败',
+ icon: 'none'
+ })
+ return Promise.reject(data)
+ }
+ } else {
+ // HTTP错误
+ let errorMessage = '网络错误'
+ switch (statusCode) {
+ case STATUS_CODE.UNAUTHORIZED:
+ errorMessage = '登录已过期,请重新登录'
+ // 清除token并跳转到登录页
+ Taro.removeStorageSync('token')
+ Taro.redirectTo({
+ url: '/pages/login/index'
+ })
+ break
+ case STATUS_CODE.NOT_FOUND:
+ errorMessage = '请求的资源不存在'
+ break
+ case STATUS_CODE.SERVER_ERROR:
+ errorMessage = '服务器错误'
+ break
+ default:
+ errorMessage = `请求失败(${statusCode})`
+ }
+
+ Taro.showToast({
+ title: errorMessage,
+ icon: 'none'
+ })
+
+ return Promise.reject({ code: statusCode, message: errorMessage })
+ }
+ }
+
+ /**
+ * 发送请求
+ */
+ async request(options: RequestOptions): Promise> {
+ const {
+ url,
+ method = 'GET',
+ data,
+ header,
+ timeout = this.timeout,
+ loading = true,
+ loadingText = '加载中...'
+ } = options
+
+ // 显示加载提示
+ if (loading) {
+ this.showLoading(loadingText)
+ }
+
+ try {
+ const response = await Taro.request({
+ url: url.startsWith('http') ? url : `${this.baseURL}${url}`,
+ method,
+ data,
+ header: this.getHeaders(header),
+ timeout
+ })
+
+ return this.handleResponse(response)
+ } catch (error) {
+ console.error('Request error:', error)
+ Taro.showToast({
+ title: '网络请求失败',
+ icon: 'none'
+ })
+ return Promise.reject(error)
+ } finally {
+ // 隐藏加载提示
+ if (loading) {
+ this.hideLoading()
+ }
+ }
+ }
+
+ /**
+ * GET请求
+ */
+ get(url: string, params?: any, options?: Partial) {
+ return this.request({
+ url: params ? `${url}?${new URLSearchParams(params).toString()}` : url,
+ method: 'GET',
+ ...options
+ })
+ }
+
+ /**
+ * POST请求
+ */
+ post(url: string, data?: any, options?: Partial) {
+ return this.request({
+ url,
+ method: 'POST',
+ data,
+ ...options
+ })
+ }
+
+ /**
+ * PUT请求
+ */
+ put(url: string, data?: any, options?: Partial) {
+ return this.request({
+ url,
+ method: 'PUT',
+ data,
+ ...options
+ })
+ }
+
+ /**
+ * DELETE请求
+ */
+ delete(url: string, data?: any, options?: Partial) {
+ return this.request({
+ url,
+ method: 'DELETE',
+ data,
+ ...options
+ })
+ }
+}
+
+export default new RequestService()
\ No newline at end of file
diff --git a/client/src/utils/index.ts b/client/src/utils/index.ts
new file mode 100644
index 0000000..edcd28c
--- /dev/null
+++ b/client/src/utils/index.ts
@@ -0,0 +1,126 @@
+/**
+ * 工具函数库
+ */
+
+/**
+ * 格式化时间
+ * @param date 时间对象或时间戳
+ * @param format 格式化字符串
+ * @returns 格式化后的时间字符串
+ */
+export function formatDate(date: Date | number, format: string = 'YYYY-MM-DD HH:mm:ss'): string {
+ const d = new Date(date)
+ const year = d.getFullYear()
+ const month = String(d.getMonth() + 1).padStart(2, '0')
+ const day = String(d.getDate()).padStart(2, '0')
+ const hours = String(d.getHours()).padStart(2, '0')
+ const minutes = String(d.getMinutes()).padStart(2, '0')
+ const seconds = String(d.getSeconds()).padStart(2, '0')
+
+ return format
+ .replace('YYYY', String(year))
+ .replace('MM', month)
+ .replace('DD', day)
+ .replace('HH', hours)
+ .replace('mm', minutes)
+ .replace('ss', seconds)
+}
+
+/**
+ * 防抖函数
+ * @param fn 要防抖的函数
+ * @param delay 延迟时间
+ * @returns 防抖后的函数
+ */
+export function debounce any>(
+ fn: T,
+ delay: number
+): (...args: Parameters) => void {
+ let timeoutId: NodeJS.Timeout | null = null
+ return (...args: Parameters) => {
+ if (timeoutId) {
+ clearTimeout(timeoutId)
+ }
+ timeoutId = setTimeout(() => fn(...args), delay)
+ }
+}
+
+/**
+ * 节流函数
+ * @param fn 要节流的函数
+ * @param delay 延迟时间
+ * @returns 节流后的函数
+ */
+export function throttle any>(
+ fn: T,
+ delay: number
+): (...args: Parameters) => void {
+ let lastTime = 0
+ return (...args: Parameters) => {
+ const now = Date.now()
+ if (now - lastTime >= delay) {
+ lastTime = now
+ fn(...args)
+ }
+ }
+}
+
+/**
+ * 深拷贝
+ * @param obj 要拷贝的对象
+ * @returns 拷贝后的对象
+ */
+export function deepClone(obj: T): T {
+ if (obj === null || typeof obj !== 'object') {
+ return obj
+ }
+
+ if (obj instanceof Date) {
+ return new Date(obj.getTime()) as T
+ }
+
+ if (obj instanceof Array) {
+ return obj.map(item => deepClone(item)) as T
+ }
+
+ if (typeof obj === 'object') {
+ const cloned = {} as T
+ for (const key in obj) {
+ if (obj.hasOwnProperty(key)) {
+ cloned[key] = deepClone(obj[key])
+ }
+ }
+ return cloned
+ }
+
+ return obj
+}
+
+/**
+ * 获取URL参数
+ * @param key 参数名
+ * @param url URL字符串
+ * @returns 参数值
+ */
+export function getUrlParam(key: string, url?: string): string | null {
+ const searchParams = new URLSearchParams(url ? url.split('?')[1] : window.location.search)
+ return searchParams.get(key)
+}
+
+/**
+ * 价格格式化
+ * @param price 价格
+ * @returns 格式化后的价格字符串
+ */
+export function formatPrice(price: number): string {
+ return `¥${price.toFixed(2)}`
+}
+
+/**
+ * 手机号脱敏
+ * @param phone 手机号
+ * @returns 脱敏后的手机号
+ */
+export function maskPhone(phone: string): string {
+ return phone.replace(/(\d{3})\d{4}(\d{4})/, '$1****$2')
+}
\ No newline at end of file
diff --git a/client/tsconfig.json b/client/tsconfig.json
new file mode 100644
index 0000000..aef1dbe
--- /dev/null
+++ b/client/tsconfig.json
@@ -0,0 +1,30 @@
+{
+ "compilerOptions": {
+ "target": "es2017",
+ "module": "commonjs",
+ "removeComments": false,
+ "preserveConstEnums": true,
+ "moduleResolution": "node",
+ "experimentalDecorators": true,
+ "noImplicitAny": false,
+ "allowSyntheticDefaultImports": true,
+ "outDir": "lib",
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+ "strictNullChecks": true,
+ "sourceMap": true,
+ "rootDir": ".",
+ "jsx": "preserve",
+ "allowJs": true,
+ "resolveJsonModule": true,
+ "typeRoots": [
+ "node_modules/@types"
+ ],
+ "paths": {
+ // TS5090 leading './'
+ "@/*": ["./src/*"]
+ }
+ },
+ "include": ["./src", "./types", "./config"],
+ "compileOnSave": false
+}
diff --git a/client/types/global.d.ts b/client/types/global.d.ts
new file mode 100644
index 0000000..de558cc
--- /dev/null
+++ b/client/types/global.d.ts
@@ -0,0 +1,31 @@
+///
+
+declare module '*.png';
+declare module '*.gif';
+declare module '*.jpg';
+declare module '*.jpeg';
+declare module '*.svg';
+declare module '*.css';
+declare module '*.less';
+declare module '*.scss';
+declare module '*.sass';
+declare module '*.styl';
+
+declare namespace NodeJS {
+ interface ProcessEnv {
+ /** NODE 内置环境变量, 会影响到最终构建生成产物 */
+ NODE_ENV: 'development' | 'production',
+ /** 当前构建的平台 */
+ TARO_ENV: 'weapp' | 'swan' | 'alipay' | 'h5' | 'rn' | 'tt' | 'quickapp' | 'qq' | 'jd'
+ /**
+ * 当前构建的小程序 appid
+ * @description 若不同环境有不同的小程序,可通过在 env 文件中配置环境变量`TARO_APP_ID`来方便快速切换 appid, 而不必手动去修改 dist/project.config.json 文件
+ * @see https://taro-docs.jd.com/docs/next/env-mode-config#特殊环境变量-taro_app_id
+ */
+ TARO_APP_ID: string
+ }
+}
+
+declare module '@tarojs/components' {
+ export * from '@tarojs/components/types/index.vue3'
+}
diff --git a/client/types/vue.d.ts b/client/types/vue.d.ts
new file mode 100644
index 0000000..a97867d
--- /dev/null
+++ b/client/types/vue.d.ts
@@ -0,0 +1,10 @@
+export {}
+
+declare module 'vue' {
+ export interface GlobalComponents extends JSX.IntrinsicElements {
+ /** Note: Vue 在 runtime 中将 JSX.IntrinsicElements 通过 index signature 重复声明标签
+ * 这会导致插件无法正常跳转类型,可以手动覆盖声明标签活得更好的体验,参考如下:
+ * 'scroll-view': JSX.IntrinsicElements['scroll-view']
+ */
+ }
+}