This commit is contained in:
oneao committed 2025-12-02 17:30:50 +08:00
1 parent 0ab2796dd4
commit f7b0c6ad2f
26 files changed
+1893 -354

No files matched your search

+1
View File
@@ -26,6 +26,7 @@
"dayjs": "^1.11.19",
"expo": "~54.0.23",
"expo-blur": "~15.0.7",
"expo-clipboard": "~8.0.7",
"expo-constants": "~18.0.10",
"expo-font": "~14.0.9",
"expo-linear-gradient": "~15.0.7",
+24
View File
@@ -56,6 +56,9 @@ importers:
expo-blur:
specifier: ~15.0.7
version: 15.0.7(expo@54.0.23)(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)
expo-clipboard:
specifier: ~8.0.7
version: 8.0.7(expo@54.0.23)(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)
expo-constants:
specifier: ~18.0.10
version: 18.0.10(expo@54.0.23)(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))
@@ -3248,6 +3251,13 @@ packages:
react: '*'
react-native: '*'
expo-clipboard@8.0.7:
resolution: {integrity: sha512-zvlfFV+wB2QQrQnHWlo0EKHAkdi2tycLtE+EXFUWTPZYkgu1XcH+aiKfd4ul7Z0SDF+1IuwoiW9AA9eO35aj3Q==}
peerDependencies:
expo: '*'
react: '*'
react-native: '*'
expo-constants@18.0.10:
resolution: {integrity: sha512-Rhtv+X974k0Cahmvx6p7ER5+pNhBC0XbP1lRviL2J1Xl4sT2FBaIuIxF/0I0CbhOsySf0ksqc5caFweAy9Ewiw==}
peerDependencies:
@@ -3930,48 +3940,56 @@ packages:
engines: {node: '>= 12.0.0'}
cpu: [arm64]
os: [linux]
libc: [glibc]
lightningcss-linux-arm64-gnu@1.30.2:
resolution: {integrity: sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A==}
engines: {node: '>= 12.0.0'}
cpu: [arm64]
os: [linux]
libc: [glibc]
lightningcss-linux-arm64-musl@1.27.0:
resolution: {integrity: sha512-rCGBm2ax7kQ9pBSeITfCW9XSVF69VX+fm5DIpvDZQl4NnQoMQyRwhZQm9pd59m8leZ1IesRqWk2v/DntMo26lg==}
engines: {node: '>= 12.0.0'}
cpu: [arm64]
os: [linux]
libc: [musl]
lightningcss-linux-arm64-musl@1.30.2:
resolution: {integrity: sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==}
engines: {node: '>= 12.0.0'}
cpu: [arm64]
os: [linux]
libc: [musl]
lightningcss-linux-x64-gnu@1.27.0:
resolution: {integrity: sha512-Dk/jovSI7qqhJDiUibvaikNKI2x6kWPN79AQiD/E/KeQWMjdGe9kw51RAgoWFDi0coP4jinaH14Nrt/J8z3U4A==}
engines: {node: '>= 12.0.0'}
cpu: [x64]
os: [linux]
libc: [glibc]
lightningcss-linux-x64-gnu@1.30.2:
resolution: {integrity: sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==}
engines: {node: '>= 12.0.0'}
cpu: [x64]
os: [linux]
libc: [glibc]
lightningcss-linux-x64-musl@1.27.0:
resolution: {integrity: sha512-QKjTxXm8A9s6v9Tg3Fk0gscCQA1t/HMoF7Woy1u68wCk5kS4fR+q3vXa1p3++REW784cRAtkYKrPy6JKibrEZA==}
engines: {node: '>= 12.0.0'}
cpu: [x64]
os: [linux]
libc: [musl]
lightningcss-linux-x64-musl@1.30.2:
resolution: {integrity: sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==}
engines: {node: '>= 12.0.0'}
cpu: [x64]
os: [linux]
libc: [musl]
lightningcss-win32-arm64-msvc@1.27.0:
resolution: {integrity: sha512-/wXegPS1hnhkeG4OXQKEMQeJd48RDC3qdh+OA8pCuOPCyvnm/yEayrJdJVqzBsqpy1aJklRCVxscpFur80o6iQ==}
@@ -9832,6 +9850,12 @@ snapshots:
react: 19.1.0
react-native: 0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0)
expo-clipboard@8.0.7(expo@54.0.23)(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0):
dependencies:
expo: 54.0.23(@babel/core@7.28.5)(@expo/metro-runtime@6.1.2)(expo-router@6.0.14)(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)
react: 19.1.0
react-native: 0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0)
expo-constants@18.0.10(expo@54.0.23)(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0)):
dependencies:
'@expo/config': 12.0.10
@@ -0,0 +1,58 @@
import type { AFormRef } from '@/components/common/ao'
import { useRef, useState } from 'react'
import { Text, View } from 'react-native'
import { AButton, ADivider, AForm, AFormItem, AInput } from '@/components/common/ao'
import { Link, LinkText } from '@/components/ui/link'
interface JoinSpaceProps {
onBack?: () => void
}
export default function JoinSpace({
onBack,
}: JoinSpaceProps) {
const formRef = useRef<AFormRef>(null)
const [form, setForm] = useState({
code: '',
})
return (
<View className="px-4 items-center">
<AForm ref={formRef} form={form}>
<AFormItem
name="username"
label="邀请码"
>
<AInput placeholder="请输入邀请码"></AInput>
</AFormItem>
</AForm>
<AButton className="w-full">加入空间</AButton>
<View
className="flex-row items-center w-full"
style={{
marginTop: 16,
marginBottom: 16,
}}
>
<View className="flex-1">
<ADivider />
</View>
<Text className="mx-3 text-gray-500 text-sm">或</Text>
<View className="flex-1">
<ADivider />
</View>
</View>
<AButton variant="outline" className="w-full">扫码加入</AButton>
<Link
className="mt-4"
onPress={() => {
onBack?.()
}}
>
<LinkText>返回</LinkText>
</Link>
</View>
)
}
@@ -0,0 +1,37 @@
import { Text, View } from 'react-native'
import { AButton, ADivider, LucideIcon } from '@/components/common/ao'
interface SpaceHomeProps {
onCreate?: () => void
onJoin?: () => void
}
export default function SpaceHome({
onCreate,
onJoin,
}: SpaceHomeProps) {
return (
<View className="p-4">
<View className="flex-col gap-4 justify-center items-center">
<LucideIcon icon="Earth" size={48} strokeWidth={1.5} />
<Text className="font-semibold text-xl">创建共享空间</Text>
<Text className="text-base text-center">创建一个专属空间,邀请朋友加入,共享文件、聊天和协作</Text>
<AButton
className="w-full"
onPress={() => onCreate?.()}
>
创建我的空间
</AButton>
</View>
<ADivider text="或" />
<AButton
variant="outline"
onPress={() => onJoin?.()}
>
加入已有空间
</AButton>
</View>
)
}
@@ -0,0 +1,176 @@
import * as Clipboard from 'expo-clipboard'
import { useState } from 'react'
import { FlatList, Pressable, TouchableOpacity, View } from 'react-native'
import QRCode from 'react-native-qrcode-svg'
import { AButton, ADivider, LucideIcon } from '@/components/common/ao'
import {
Accordion,
AccordionContent,
AccordionHeader,
AccordionIcon,
AccordionItem,
AccordionTitleText,
AccordionTrigger,
} from '@/components/ui/accordion'
import {
Avatar,
AvatarBadge,
AvatarFallbackText,
} from '@/components/ui/avatar'
import { Badge, BadgeText } from '@/components/ui/badge'
import { Heading } from '@/components/ui/heading'
import { HStack } from '@/components/ui/hstack'
import { ChevronDownIcon, ChevronUpIcon } from '@/components/ui/icon'
import { Menu, MenuItem, MenuItemLabel } from '@/components/ui/menu'
import { Text } from '@/components/ui/text'
import { VStack } from '@/components/ui/vstack'
import { toast } from '@/utils/toast'
export default function SpaceInviteAndMembers() {
const [accordion, setAccordion] = useState(['a'])
const members = [
{ id: 1, name: '张三' },
{ id: 2, name: '李四' },
{ id: 3, name: '王五' },
]
async function copyInviteCode() {
await Clipboard.setStringAsync('hello world')
toast.show({
title: '复制成功',
})
}
return (
<View className="px-4 flex-1">
<Accordion
size="md"
variant="unfilled"
type="multiple"
isCollapsible={true}
isDisabled={false}
value={accordion}
onValueChange={(value) => {
setAccordion(value)
}}
>
<AccordionItem value="a">
<AccordionHeader>
<AccordionTrigger
style={{
paddingLeft: 0,
paddingRight: 0,
}}
>
{({ isExpanded }: { isExpanded: boolean }) => {
return (
<>
<AccordionTitleText className="font-semibold text-lg">
我的邀请码
</AccordionTitleText>
{isExpanded
? (
<AccordionIcon as={ChevronUpIcon} className="ml-3" />
)
: (
<AccordionIcon as={ChevronDownIcon} className="ml-3" />
)}
</>
)
}}
</AccordionTrigger>
</AccordionHeader>
<AccordionContent style={{
marginBottom: 0,
paddingBottom: 0,
}}
>
<TouchableOpacity
className="flex-row items-center justify-center"
onPress={() => {
copyInviteCode()
}}
>
<Text className="font-bold text-3xl tracking-[4px] mr-3">123456</Text>
<LucideIcon icon="Copy" size={16} color="blue" clickable={false} />
</TouchableOpacity>
<View className="items-center justify-center">
<ADivider direction="horizontal" text="扫描二维码" />
<QRCode
size={120}
value="http://awesome.link.qr"
/>
</View>
</AccordionContent>
</AccordionItem>
</Accordion>
<View className="mt-4 flex-1">
<View className="flex flex-row items-center justify-between">
<Text className="font-semibold text-lg">空间成员</Text>
<Badge size="lg" variant="solid" action="muted">
<BadgeText>3 人</BadgeText>
</Badge>
</View>
<View>
<FlatList
data={members}
keyExtractor={item => item.id.toString()}
renderItem={({ item }) => (
<TouchableOpacity>
<HStack space="md" className="my-2 items-center">
<Avatar className="bg-indigo-600">
<AvatarFallbackText className="text-white text-lg">
{item.name}
</AvatarFallbackText>
<AvatarBadge />
</Avatar>
<VStack>
<HStack space="sm">
<Heading size="sm">{item.name}</Heading>
<Badge size="sm" variant="solid" action="muted">
<BadgeText>管理员</BadgeText>
</Badge>
</HStack>
<Text size="sm">Marketing Coordinator</Text>
</VStack>
<View className="ml-auto">
<Menu
placement="top"
offset={5}
trigger={triggerProps => (
<Pressable
{...triggerProps}
style={{
padding: 8,
justifyContent: 'center',
alignItems: 'center',
}}
>
<LucideIcon icon="EllipsisVertical" size={20} clickable={false} />
</Pressable>
)}
>
<MenuItem key="Add account" textValue="Add account">
<MenuItemLabel size="sm">添加为管理员</MenuItemLabel>
</MenuItem>
<MenuItem key="Community" textValue="Community">
<MenuItemLabel size="sm">移出空间</MenuItemLabel>
</MenuItem>
</Menu>
</View>
</HStack>
</TouchableOpacity>
)}
/>
</View>
<AButton size="sm" className="mb-8 mt-4">退出该空间</AButton>
</View>
</View>
)
}
@@ -0,0 +1,21 @@
import Animated, { FadeIn, FadeOut } from 'react-native-reanimated'
interface StepFadeProps {
visible: boolean
children: React.ReactNode
}
export default function StepFade({ visible, children }: StepFadeProps) {
if (!visible)
return null
return (
<Animated.View
entering={FadeIn.duration(200)}
exiting={FadeOut.duration(200)}
style={{ flex: 1 }}
>
{children}
</Animated.View>
)
}
@@ -1,241 +1,41 @@
import type { AFormRef } from '@/components/common/ao'
import * as React from 'react'
import { useRef, useState } from 'react'
import { FlatList, Pressable, TouchableOpacity, View } from 'react-native'
import QRCode from 'react-native-qrcode-svg'
import { AButton, ADivider, AForm, AFormItem, AInput, LucideIcon } from '@/components/common/ao'
import { useState } from 'react'
import { View } from 'react-native'
import CustomHeader from '@/components/custom/Header'
import {
Accordion,
AccordionContent,
AccordionHeader,
AccordionIcon,
AccordionItem,
AccordionTitleText,
AccordionTrigger,
} from '@/components/ui/accordion'
import {
Avatar,
AvatarBadge,
AvatarFallbackText,
AvatarImage,
} from '@/components/ui/avatar'
import { Badge, BadgeText } from '@/components/ui/badge'
import { Heading } from '@/components/ui/heading'
import { HStack } from '@/components/ui/hstack'
import { ChevronDownIcon, ChevronUpIcon } from '@/components/ui/icon'
import { Link, LinkText } from '@/components/ui/link'
import { Menu, MenuItem, MenuItemLabel } from '@/components/ui/menu'
import { Text } from '@/components/ui/text'
import { VStack } from '@/components/ui/vstack'
import JoinSpace from './components/JoinSpace'
import SpaceHome from './components/SpaceHome'
import SpaceInviteAndMembers from './components/SpaceInviteAndMembers'
import StepFade from './components/StepFade'
export default function PagerWithTab() {
const formRef = useRef<AFormRef>(null)
const [form, setForm] = useState({
code: '',
})
const [accordion, setAccordion] = useState(['a'])
function EnterSapce() {
return (
<View className="px-4 items-center">
<AForm ref={formRef} form={form}>
<AFormItem
name="username"
label="邀请码"
>
<AInput placeholder="请输入邀请码"></AInput>
</AFormItem>
</AForm>
<AButton className="w-full">加入空间</AButton>
<View
className="flex-row items-center w-full"
style={{
marginTop: 16,
marginBottom: 16,
}}
>
<View className="flex-1">
<ADivider />
</View>
<Text className="mx-3 text-gray-500 text-sm">或</Text>
<View className="flex-1">
<ADivider />
</View>
</View>
<AButton variant="outline" className="w-full">扫码加入</AButton>
<Link className="mt-4">
<LinkText>返回</LinkText>
</Link>
</View>
)
}
const members = [
{ id: 1, name: '张三' },
{ id: 2, name: '李四' },
{ id: 3, name: '王五' },
]
const [step, setStep] = useState<'join' | 'invite' | 'home'>('home')
return (
<View className="flex-1">
<CustomHeader title="共享空间" />
<View className="flex-1">
{/* <View className="p-4">
<View className="flex-col gap-4 justify-center items-center">
<LucideIcon icon="Earth" size={48} strokeWidth={1.5} />
<Text className="font-semibold text-xl">创建共享空间</Text>
<Text className="text-base text-center">创建一个专属空间,邀请朋友加入,共享文件、聊天和协作</Text>
<AButton className="w-full">创建我的空间</AButton>
</View>
<View
className="flex-row items-center w-full"
style={{
marginTop: 16,
marginBottom: 16,
<StepFade visible={step === 'home'}>
<SpaceHome
onCreate={() => {
setStep('invite')
}}
>
<View className="flex-1">
<Divider />
</View>
<Text className="mx-3 text-gray-500 text-sm">或</Text>
<View className="flex-1">
<Divider />
</View>
</View>
<AButton variant="outline">加入已有空间</AButton>
</View> */}
{/* <View className="px-4 flex-1">
<Accordion
size="md"
variant="unfilled"
type="multiple"
isCollapsible={true}
isDisabled={false}
value={accordion}
onValueChange={(value) => {
setAccordion(value)
onJoin={() => {
setStep('join')
}}
>
<AccordionItem value="a">
<AccordionHeader>
<AccordionTrigger
style={{
paddingLeft: 0,
paddingRight: 0,
}}
>
{({ isExpanded }: { isExpanded: boolean }) => {
return (
<>
<AccordionTitleText className="font-semibold text-lg">
我的邀请码
</AccordionTitleText>
{isExpanded
? (
<AccordionIcon as={ChevronUpIcon} className="ml-3" />
)
: (
<AccordionIcon as={ChevronDownIcon} className="ml-3" />
)}
</>
)
}}
</AccordionTrigger>
</AccordionHeader>
<AccordionContent style={{
marginBottom: 0,
paddingBottom: 0,
}}
>
<TouchableOpacity className="flex-row items-center justify-center ">
<Text className="font-bold text-3xl tracking-[4px] mr-3">123456</Text>
<LucideIcon icon="Copy" size={16} color="blue" clickable={false} />
</TouchableOpacity>
/>
</StepFade>
<View className="items-center justify-center">
<ADivider direction="horizontal" text="扫描二维码" />
<QRCode
size={120}
value="http://awesome.link.qr"
/>
</View>
</AccordionContent>
</AccordionItem>
</Accordion>
<View className="mt-4 flex-1">
<View className="flex flex-row items-center justify-between">
<Text className="font-semibold text-lg">空间成员</Text>
<Badge size="lg" variant="solid" action="muted">
<BadgeText>3 人</BadgeText>
</Badge>
</View>
<StepFade visible={step === 'join'}>
<JoinSpace
onBack={() => {
setStep('home')
}}
/>
</StepFade>
<View>
<FlatList
data={members}
keyExtractor={item => item.id.toString()}
renderItem={({ item }) => (
<TouchableOpacity>
<HStack space="md" className="my-2 items-center">
<Avatar className="bg-indigo-600">
<AvatarFallbackText className="text-white text-lg">
{item.name}
</AvatarFallbackText>
<AvatarBadge />
</Avatar>
<VStack>
<HStack space="sm">
<Heading size="sm">{item.name}</Heading>
<Badge size="sm" variant="solid" action="muted">
<BadgeText>管理员</BadgeText>
</Badge>
</HStack>
<Text size="sm">Marketing Coordinator</Text>
</VStack>
<View className="ml-auto">
<Menu
placement="top"
offset={5}
trigger={triggerProps => (
<Pressable
{...triggerProps}
style={{
padding: 8,
justifyContent: 'center',
alignItems: 'center',
}}
>
<LucideIcon icon="EllipsisVertical" size={20} clickable={false} />
</Pressable>
)}
>
<MenuItem key="Add account" textValue="Add account">
<MenuItemLabel size="sm">添加为管理员</MenuItemLabel>
</MenuItem>
<MenuItem key="Community" textValue="Community">
<MenuItemLabel size="sm">移出空间</MenuItemLabel>
</MenuItem>
</Menu>
</View>
</HStack>
</TouchableOpacity>
)}
/>
</View>
<AButton size="sm" className="mb-8 mt-4">退出该空间</AButton>
</View>
</View> */}
<EnterSapce />
<StepFade visible={step === 'invite'}>
<SpaceInviteAndMembers />
</StepFade>
</View>
</View>
)
@@ -32,8 +32,8 @@ function isWhiteListed(url: string) {
}
const alovaInstance = createAlova({
baseURL: 'http://192.168.5.6:8086/app',
// baseURL: 'http://192.168.1.136:8086/app',
// baseURL: 'http://192.168.5.6:8086/app',
baseURL: 'http://192.168.1.136:8086/app',
requestAdapter: axiosRequestAdapter(),
timeout: 5000,
cacheFor: null,
+194 -123
View File
@@ -1,148 +1,219 @@
<!DOCTYPE html>
<html lang="zh">
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<title>高级邀请页面</title>
<style>
body {
margin: 0;
font-family: "Inter", sans-serif;
background: #f3f4f7;
}
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>初次登录引导页</title>
<style>
/* 重置样式 */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: Arial, sans-serif;
}
/* 顶部渐变 Header */
.header {
width: 100%;
padding: 40px 24px;
background: linear-gradient(135deg, #4d8dff, #6fa5ff);
color: white;
font-size: 26px;
font-weight: 600;
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
}
body {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
background: #f7f7f7;
}
/* 内容卡片 */
.container {
margin-top: -32px;
padding: 0 24px 40px;
}
.onboarding-container {
width: 90%;
max-width: 400px;
background: #fff;
border-radius: 16px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
overflow: hidden;
display: flex;
flex-direction: column;
text-align: center;
padding: 24px;
animation: fadeIn 0.5s ease-in-out;
}
.card {
background: white;
padding: 24px;
border-radius: 20px;
box-shadow: 0 6px 20px rgba(0,0,0,0.06);
margin-bottom: 24px;
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(20px);
}
.title-row {
display: flex;
align-items: center;
justify-content: space-between;
}
to {
opacity: 1;
transform: translateY(0);
}
}
.title {
font-size: 20px;
font-weight: 600;
}
.onboarding-step {
display: none;
flex-direction: column;
align-items: center;
}
.invite-code {
font-size: 40px;
font-weight: 700;
letter-spacing: 4px;
color: #333;
text-align: center;
margin-top: 12px;
}
.onboarding-step.active {
display: flex;
}
.copy-btn {
display: flex;
justify-content: center;
margin-top: 8px;
font-size: 14px;
color: #4d8dff;
cursor: pointer;
}
.avatar {
width: 100px;
height: 100px;
border-radius: 50%;
background: #ccc;
margin-bottom: 16px;
overflow: hidden;
}
/* 二维码卡片设计 */
.qr-card {
background: white;
padding: 20px;
border-radius: 20px;
text-align: center;
box-shadow: 0 4px 14px rgba(0,0,0,0.05);
margin-bottom: 24px;
}
.avatar img {
width: 100%;
height: 100%;
object-fit: cover;
}
.qr-img {
width: 190px;
height: 190px;
border-radius: 12px;
border: 1px solid #eee;
}
.username {
font-size: 20px;
font-weight: bold;
margin-bottom: 8px;
}
.badge {
background: #eef1ff;
padding: 6px 14px;
border-radius: 14px;
font-size: 14px;
color: #4d5bff;
}
.info-text {
font-size: 14px;
color: #666;
margin-bottom: 24px;
}
.member-item {
padding: 14px 0;
border-bottom: 1px solid #f0f0f0;
font-size: 16px;
}
.buttons {
display: flex;
justify-content: space-between;
width: 100%;
}
.member-item:last-child {
border-bottom: none;
}
</style>
.btn {
padding: 10px 20px;
border-radius: 8px;
border: none;
cursor: pointer;
font-size: 16px;
flex: 1;
margin: 0 4px;
}
.btn-primary {
background: #4CAF50;
color: #fff;
}
.btn-secondary {
background: #e0e0e0;
color: #333;
}
.progress {
display: flex;
justify-content: center;
margin-top: 16px;
}
.dot {
width: 10px;
height: 10px;
background: #ddd;
border-radius: 50%;
margin: 0 4px;
transition: background 0.3s;
}
.dot.active {
background: #4CAF50;
}
</style>
</head>
<body>
<!-- Header -->
<div class="header">邀请好友加入空间</div>
<!-- 内容 -->
<div class="container">
<!-- 邀请码卡片 -->
<div class="card">
<div class="title-row">
<div class="title">我的邀请码</div>
<div class="onboarding-container">
<!-- 步骤 1 -->
<div class="onboarding-step active" id="step-1">
<div class="avatar">
<img src="https://placekitten.com/200/200" alt="头像">
</div>
<div class="username">张三</div>
<div class="info-text">欢迎加入我们的平台!让我们来完成一些设置吧。</div>
<div class="buttons">
<button class="btn btn-secondary" disabled>上一步</button>
<button class="btn btn-primary" onclick="nextStep()">下一步</button>
</div>
</div>
<div class="invite-code">123456</div>
<div class="copy-btn">点击复制邀请码</div>
</div>
<!-- 二维码卡片 -->
<div class="qr-card">
<img
src="https://api.qrserver.com/v1/create-qr-code/?size=200x200&data=demo"
class="qr-img"
alt="二维码"
/>
<p style="margin-top: 12px; color: #666; font-size: 14px;">
扫码加入我的共享空间
</p>
</div>
<!-- 成员卡片 -->
<div class="card">
<div class="title-row">
<div class="title">空间成员</div>
<div class="badge">3 人</div>
<!-- 步骤 2 -->
<div class="onboarding-step" id="step-2">
<div class="username">选择你的兴趣</div>
<div class="info-text">帮助我们推荐更适合你的内容</div>
<div class="buttons">
<button class="btn btn-secondary" onclick="prevStep()">上一步</button>
<button class="btn btn-primary" onclick="nextStep()">下一步</button>
</div>
</div>
<div class="member-item">成员 A</div>
<div class="member-item">成员 B</div>
<div class="member-item">成员 C</div>
<!-- 步骤 3 -->
<div class="onboarding-step" id="step-3">
<div class="username">完成!</div>
<div class="info-text">你已经准备好开始使用了</div>
<div class="buttons">
<button class="btn btn-secondary" onclick="prevStep()">上一步</button>
<button class="btn btn-primary" onclick="finishOnboarding()">开始使用</button>
</div>
</div>
<!-- 进度指示器 -->
<div class="progress">
<div class="dot active" id="dot-1"></div>
<div class="dot" id="dot-2"></div>
<div class="dot" id="dot-3"></div>
</div>
</div>
</div>
<script>
let currentStep = 1;
const totalSteps = 3;
function showStep(step) {
for (let i = 1; i <= totalSteps; i++) {
const s = document.getElementById(`step-${i}`);
const d = document.getElementById(`dot-${i}`);
if (i === step) {
s.classList.add('active');
d.classList.add('active');
} else {
s.classList.remove('active');
d.classList.remove('active');
}
}
}
function nextStep() {
if (currentStep < totalSteps) {
currentStep++;
showStep(currentStep);
}
}
function prevStep() {
if (currentStep > 1) {
currentStep--;
showStep(currentStep);
}
}
function finishOnboarding() {
alert('初始化完成,进入应用!');
// 这里可以跳转到首页或关闭引导页
}
</script>
</body>
</html>
</html>