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>
@@ -0,0 +1,95 @@
// Code generated by hertz generator.
package space
import (
"cn/oneao/base-go/biz/router/middleware"
"cn/oneao/base-go/db"
"cn/oneao/base-go/db/repo"
"cn/oneao/base-go/utils"
"cn/oneao/base-go/utils/errorx"
"cn/oneao/base-go/utils/idgen"
"cn/oneao/base-go/utils/response"
"context"
space "cn/oneao/base-go/biz/model/space"
"github.com/cloudwego/hertz/pkg/app"
"github.com/cloudwego/hertz/pkg/protocol/consts"
)
// InsertSpace .
// @router /space/insert [POST]
func InsertSpace(ctx context.Context, c *app.RequestContext) {
var err error
var req space.InsertSpaceParams
err = c.BindAndValidate(&req)
if err != nil {
c.String(consts.StatusBadRequest, err.Error())
return
}
userID := middleware.GetUserID(ctx)
// 检查该用户是否已经加入空间了
_, err = db.Queries.GetSpaceByUserId(ctx, userID)
if err != nil {
if errorx.IsNotFound(err) {
// 忽略
} else {
errorx.AddError(c, err)
return
}
} else {
response.Fail(c).Message("请先退出已加入的空间").Send()
return
}
// 生成邀请码
var inviteCode string
for {
// 生成邀请码
code, err := utils.GenerateCode(6)
if err != nil {
errorx.AddError(c, err)
return
}
// 检查数据库是否存在
_, err = db.Queries.GetSpaceByInviteCode(ctx, code)
if err != nil {
if errorx.IsNotFound(err) {
// 不存在,唯一,直接使用
inviteCode = code
break
} else {
// 系统错误
errorx.AddError(c, err)
return
}
}
// 如果存在,继续循环重新生成
}
// 创建空间
spaceId := idgen.NextId()
params := repo.InsertSpaceParams{
ID: spaceId,
InviteCode: inviteCode,
}
err = db.Queries.InsertSpace(ctx, params)
if err != nil {
errorx.AddError(c, err)
return
}
var resp space.InsertSpaceResult
resp.Id = spaceId
resp.InviteCode = inviteCode
response.Success(c).Data(resp).Send()
}
@@ -0,0 +1,78 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.28.0
// protoc v4.25.3
// source: index.proto
package space
import (
_ "cn/oneao/base-go/biz/model/api"
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
var File_index_proto protoreflect.FileDescriptor
var file_index_proto_rawDesc = []byte{
0x0a, 0x0b, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x05, 0x73,
0x70, 0x61, 0x63, 0x65, 0x1a, 0x09, 0x61, 0x70, 0x69, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a,
0x17, 0x73, 0x70, 0x61, 0x63, 0x65, 0x2f, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x74, 0x79, 0x70,
0x65, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32, 0x64, 0x0a, 0x0c, 0x53, 0x70, 0x61, 0x63,
0x65, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x54, 0x0a, 0x0b, 0x49, 0x6e, 0x73, 0x65,
0x72, 0x74, 0x53, 0x70, 0x61, 0x63, 0x65, 0x12, 0x18, 0x2e, 0x73, 0x70, 0x61, 0x63, 0x65, 0x2e,
0x49, 0x6e, 0x73, 0x65, 0x72, 0x74, 0x53, 0x70, 0x61, 0x63, 0x65, 0x50, 0x61, 0x72, 0x61, 0x6d,
0x73, 0x1a, 0x18, 0x2e, 0x73, 0x70, 0x61, 0x63, 0x65, 0x2e, 0x49, 0x6e, 0x73, 0x65, 0x72, 0x74,
0x53, 0x70, 0x61, 0x63, 0x65, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x22, 0x11, 0xd2, 0xc1, 0x18,
0x0d, 0x2f, 0x73, 0x70, 0x61, 0x63, 0x65, 0x2f, 0x69, 0x6e, 0x73, 0x65, 0x72, 0x74, 0x42, 0x22,
0x5a, 0x20, 0x63, 0x6e, 0x2f, 0x6f, 0x6e, 0x65, 0x61, 0x6f, 0x2f, 0x62, 0x61, 0x73, 0x65, 0x2d,
0x67, 0x6f, 0x2f, 0x62, 0x69, 0x7a, 0x2f, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x2f, 0x73, 0x70, 0x61,
0x63, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var file_index_proto_goTypes = []interface{}{
(*InsertSpaceParams)(nil), // 0: space.InsertSpaceParams
(*InsertSpaceResult)(nil), // 1: space.InsertSpaceResult
}
var file_index_proto_depIdxs = []int32{
0, // 0: space.SpaceService.InsertSpace:input_type -> space.InsertSpaceParams
1, // 1: space.SpaceService.InsertSpace:output_type -> space.InsertSpaceResult
1, // [1:2] is the sub-list for method output_type
0, // [0:1] is the sub-list for method input_type
0, // [0:0] is the sub-list for extension type_name
0, // [0:0] is the sub-list for extension extendee
0, // [0:0] is the sub-list for field type_name
}
func init() { file_index_proto_init() }
func file_index_proto_init() {
if File_index_proto != nil {
return
}
file_space_space_types_proto_init()
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_index_proto_rawDesc,
NumEnums: 0,
NumMessages: 0,
NumExtensions: 0,
NumServices: 1,
},
GoTypes: file_index_proto_goTypes,
DependencyIndexes: file_index_proto_depIdxs,
}.Build()
File_index_proto = out.File
file_index_proto_rawDesc = nil
file_index_proto_goTypes = nil
file_index_proto_depIdxs = nil
}
@@ -0,0 +1,486 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.28.0
// protoc v4.25.3
// source: space/space_types.proto
package space
import (
_ "cn/oneao/base-go/biz/model/api"
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
// 创建空间
type InsertSpaceParams struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
}
func (x *InsertSpaceParams) Reset() {
*x = InsertSpaceParams{}
if protoimpl.UnsafeEnabled {
mi := &file_space_space_types_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *InsertSpaceParams) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*InsertSpaceParams) ProtoMessage() {}
func (x *InsertSpaceParams) ProtoReflect() protoreflect.Message {
mi := &file_space_space_types_proto_msgTypes[0]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use InsertSpaceParams.ProtoReflect.Descriptor instead.
func (*InsertSpaceParams) Descriptor() ([]byte, []int) {
return file_space_space_types_proto_rawDescGZIP(), []int{0}
}
type InsertSpaceResult struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Id int64 `protobuf:"varint,1,opt,name=Id,proto3" form:"id" json:"id"`
InviteCode string `protobuf:"bytes,2,opt,name=InviteCode,proto3" form:"invite_code" json:"invite_code"`
}
func (x *InsertSpaceResult) Reset() {
*x = InsertSpaceResult{}
if protoimpl.UnsafeEnabled {
mi := &file_space_space_types_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *InsertSpaceResult) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*InsertSpaceResult) ProtoMessage() {}
func (x *InsertSpaceResult) ProtoReflect() protoreflect.Message {
mi := &file_space_space_types_proto_msgTypes[1]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use InsertSpaceResult.ProtoReflect.Descriptor instead.
func (*InsertSpaceResult) Descriptor() ([]byte, []int) {
return file_space_space_types_proto_rawDescGZIP(), []int{1}
}
func (x *InsertSpaceResult) GetId() int64 {
if x != nil {
return x.Id
}
return 0
}
func (x *InsertSpaceResult) GetInviteCode() string {
if x != nil {
return x.InviteCode
}
return ""
}
// 解散空间
type DeleteSpaceParams struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
}
func (x *DeleteSpaceParams) Reset() {
*x = DeleteSpaceParams{}
if protoimpl.UnsafeEnabled {
mi := &file_space_space_types_proto_msgTypes[2]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *DeleteSpaceParams) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*DeleteSpaceParams) ProtoMessage() {}
func (x *DeleteSpaceParams) ProtoReflect() protoreflect.Message {
mi := &file_space_space_types_proto_msgTypes[2]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use DeleteSpaceParams.ProtoReflect.Descriptor instead.
func (*DeleteSpaceParams) Descriptor() ([]byte, []int) {
return file_space_space_types_proto_rawDescGZIP(), []int{2}
}
type DeleteSpaceResult struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
}
func (x *DeleteSpaceResult) Reset() {
*x = DeleteSpaceResult{}
if protoimpl.UnsafeEnabled {
mi := &file_space_space_types_proto_msgTypes[3]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *DeleteSpaceResult) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*DeleteSpaceResult) ProtoMessage() {}
func (x *DeleteSpaceResult) ProtoReflect() protoreflect.Message {
mi := &file_space_space_types_proto_msgTypes[3]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use DeleteSpaceResult.ProtoReflect.Descriptor instead.
func (*DeleteSpaceResult) Descriptor() ([]byte, []int) {
return file_space_space_types_proto_rawDescGZIP(), []int{3}
}
// 获取空间下的所有用户
type QuerySpaceUsersParams struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
SpaceId int64 `protobuf:"varint,1,opt,name=SpaceId,proto3" form:"space_id" json:"space_id" vd:"$ != 0"`
}
func (x *QuerySpaceUsersParams) Reset() {
*x = QuerySpaceUsersParams{}
if protoimpl.UnsafeEnabled {
mi := &file_space_space_types_proto_msgTypes[4]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *QuerySpaceUsersParams) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*QuerySpaceUsersParams) ProtoMessage() {}
func (x *QuerySpaceUsersParams) ProtoReflect() protoreflect.Message {
mi := &file_space_space_types_proto_msgTypes[4]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use QuerySpaceUsersParams.ProtoReflect.Descriptor instead.
func (*QuerySpaceUsersParams) Descriptor() ([]byte, []int) {
return file_space_space_types_proto_rawDescGZIP(), []int{4}
}
func (x *QuerySpaceUsersParams) GetSpaceId() int64 {
if x != nil {
return x.SpaceId
}
return 0
}
type SpaceUser struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
}
func (x *SpaceUser) Reset() {
*x = SpaceUser{}
if protoimpl.UnsafeEnabled {
mi := &file_space_space_types_proto_msgTypes[5]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *SpaceUser) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*SpaceUser) ProtoMessage() {}
func (x *SpaceUser) ProtoReflect() protoreflect.Message {
mi := &file_space_space_types_proto_msgTypes[5]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use SpaceUser.ProtoReflect.Descriptor instead.
func (*SpaceUser) Descriptor() ([]byte, []int) {
return file_space_space_types_proto_rawDescGZIP(), []int{5}
}
type QuerySpaceUsersResult struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
}
func (x *QuerySpaceUsersResult) Reset() {
*x = QuerySpaceUsersResult{}
if protoimpl.UnsafeEnabled {
mi := &file_space_space_types_proto_msgTypes[6]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *QuerySpaceUsersResult) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*QuerySpaceUsersResult) ProtoMessage() {}
func (x *QuerySpaceUsersResult) ProtoReflect() protoreflect.Message {
mi := &file_space_space_types_proto_msgTypes[6]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use QuerySpaceUsersResult.ProtoReflect.Descriptor instead.
func (*QuerySpaceUsersResult) Descriptor() ([]byte, []int) {
return file_space_space_types_proto_rawDescGZIP(), []int{6}
}
var File_space_space_types_proto protoreflect.FileDescriptor
var file_space_space_types_proto_rawDesc = []byte{
0x0a, 0x17, 0x73, 0x70, 0x61, 0x63, 0x65, 0x2f, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x74, 0x79,
0x70, 0x65, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x05, 0x73, 0x70, 0x61, 0x63, 0x65,
0x1a, 0x09, 0x61, 0x70, 0x69, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x13, 0x0a, 0x11, 0x49,
0x6e, 0x73, 0x65, 0x72, 0x74, 0x53, 0x70, 0x61, 0x63, 0x65, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x73,
0x22, 0x5c, 0x0a, 0x11, 0x49, 0x6e, 0x73, 0x65, 0x72, 0x74, 0x53, 0x70, 0x61, 0x63, 0x65, 0x52,
0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x16, 0x0a, 0x02, 0x49, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28,
0x03, 0x42, 0x06, 0xca, 0xbb, 0x18, 0x02, 0x69, 0x64, 0x52, 0x02, 0x49, 0x64, 0x12, 0x2f, 0x0a,
0x0a, 0x49, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x43, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28,
0x09, 0x42, 0x0f, 0xca, 0xbb, 0x18, 0x0b, 0x69, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x5f, 0x63, 0x6f,
0x64, 0x65, 0x52, 0x0a, 0x49, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x43, 0x6f, 0x64, 0x65, 0x22, 0x13,
0x0a, 0x11, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x53, 0x70, 0x61, 0x63, 0x65, 0x50, 0x61, 0x72,
0x61, 0x6d, 0x73, 0x22, 0x13, 0x0a, 0x11, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x53, 0x70, 0x61,
0x63, 0x65, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x22, 0x49, 0x0a, 0x15, 0x51, 0x75, 0x65, 0x72,
0x79, 0x53, 0x70, 0x61, 0x63, 0x65, 0x55, 0x73, 0x65, 0x72, 0x73, 0x50, 0x61, 0x72, 0x61, 0x6d,
0x73, 0x12, 0x30, 0x0a, 0x07, 0x53, 0x70, 0x61, 0x63, 0x65, 0x49, 0x64, 0x18, 0x01, 0x20, 0x01,
0x28, 0x03, 0x42, 0x16, 0xca, 0xbb, 0x18, 0x08, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x69, 0x64,
0xda, 0xbb, 0x18, 0x06, 0x24, 0x20, 0x21, 0x3d, 0x20, 0x30, 0x52, 0x07, 0x53, 0x70, 0x61, 0x63,
0x65, 0x49, 0x64, 0x22, 0x0b, 0x0a, 0x09, 0x53, 0x70, 0x61, 0x63, 0x65, 0x55, 0x73, 0x65, 0x72,
0x22, 0x17, 0x0a, 0x15, 0x51, 0x75, 0x65, 0x72, 0x79, 0x53, 0x70, 0x61, 0x63, 0x65, 0x55, 0x73,
0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x42, 0x22, 0x5a, 0x20, 0x63, 0x6e, 0x2f,
0x6f, 0x6e, 0x65, 0x61, 0x6f, 0x2f, 0x62, 0x61, 0x73, 0x65, 0x2d, 0x67, 0x6f, 0x2f, 0x62, 0x69,
0x7a, 0x2f, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x2f, 0x73, 0x70, 0x61, 0x63, 0x65, 0x62, 0x06, 0x70,
0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
file_space_space_types_proto_rawDescOnce sync.Once
file_space_space_types_proto_rawDescData = file_space_space_types_proto_rawDesc
)
func file_space_space_types_proto_rawDescGZIP() []byte {
file_space_space_types_proto_rawDescOnce.Do(func() {
file_space_space_types_proto_rawDescData = protoimpl.X.CompressGZIP(file_space_space_types_proto_rawDescData)
})
return file_space_space_types_proto_rawDescData
}
var file_space_space_types_proto_msgTypes = make([]protoimpl.MessageInfo, 7)
var file_space_space_types_proto_goTypes = []interface{}{
(*InsertSpaceParams)(nil), // 0: space.InsertSpaceParams
(*InsertSpaceResult)(nil), // 1: space.InsertSpaceResult
(*DeleteSpaceParams)(nil), // 2: space.DeleteSpaceParams
(*DeleteSpaceResult)(nil), // 3: space.DeleteSpaceResult
(*QuerySpaceUsersParams)(nil), // 4: space.QuerySpaceUsersParams
(*SpaceUser)(nil), // 5: space.SpaceUser
(*QuerySpaceUsersResult)(nil), // 6: space.QuerySpaceUsersResult
}
var file_space_space_types_proto_depIdxs = []int32{
0, // [0:0] is the sub-list for method output_type
0, // [0:0] is the sub-list for method input_type
0, // [0:0] is the sub-list for extension type_name
0, // [0:0] is the sub-list for extension extendee
0, // [0:0] is the sub-list for field type_name
}
func init() { file_space_space_types_proto_init() }
func file_space_space_types_proto_init() {
if File_space_space_types_proto != nil {
return
}
if !protoimpl.UnsafeEnabled {
file_space_space_types_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*InsertSpaceParams); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_space_space_types_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*InsertSpaceResult); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_space_space_types_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*DeleteSpaceParams); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_space_space_types_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*DeleteSpaceResult); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_space_space_types_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*QuerySpaceUsersParams); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_space_space_types_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*SpaceUser); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_space_space_types_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*QuerySpaceUsersResult); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_space_space_types_proto_rawDesc,
NumEnums: 0,
NumMessages: 7,
NumExtensions: 0,
NumServices: 0,
},
GoTypes: file_space_space_types_proto_goTypes,
DependencyIndexes: file_space_space_types_proto_depIdxs,
MessageInfos: file_space_space_types_proto_msgTypes,
}.Build()
File_space_space_types_proto = out.File
file_space_space_types_proto_rawDesc = nil
file_space_space_types_proto_goTypes = nil
file_space_space_types_proto_depIdxs = nil
}
@@ -0,0 +1,224 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.28.0
// protoc v4.25.3
// source: space/space_user_types.proto
package space
import (
_ "cn/oneao/base-go/biz/model/api"
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
// 加入空间
type InsertSpaceUserParams struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
InviteCode int64 `protobuf:"varint,1,opt,name=InviteCode,proto3" form:"invite_code" json:"invite_code"` // 邀请码
}
func (x *InsertSpaceUserParams) Reset() {
*x = InsertSpaceUserParams{}
if protoimpl.UnsafeEnabled {
mi := &file_space_space_user_types_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *InsertSpaceUserParams) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*InsertSpaceUserParams) ProtoMessage() {}
func (x *InsertSpaceUserParams) ProtoReflect() protoreflect.Message {
mi := &file_space_space_user_types_proto_msgTypes[0]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use InsertSpaceUserParams.ProtoReflect.Descriptor instead.
func (*InsertSpaceUserParams) Descriptor() ([]byte, []int) {
return file_space_space_user_types_proto_rawDescGZIP(), []int{0}
}
func (x *InsertSpaceUserParams) GetInviteCode() int64 {
if x != nil {
return x.InviteCode
}
return 0
}
type InsertSpaceUserResult struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
SpaceId int64 `protobuf:"varint,1,opt,name=SpaceId,proto3" form:"space_id" json:"space_id"`
InviteCode string `protobuf:"bytes,2,opt,name=InviteCode,proto3" form:"invite_code" json:"invite_code"`
}
func (x *InsertSpaceUserResult) Reset() {
*x = InsertSpaceUserResult{}
if protoimpl.UnsafeEnabled {
mi := &file_space_space_user_types_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *InsertSpaceUserResult) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*InsertSpaceUserResult) ProtoMessage() {}
func (x *InsertSpaceUserResult) ProtoReflect() protoreflect.Message {
mi := &file_space_space_user_types_proto_msgTypes[1]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use InsertSpaceUserResult.ProtoReflect.Descriptor instead.
func (*InsertSpaceUserResult) Descriptor() ([]byte, []int) {
return file_space_space_user_types_proto_rawDescGZIP(), []int{1}
}
func (x *InsertSpaceUserResult) GetSpaceId() int64 {
if x != nil {
return x.SpaceId
}
return 0
}
func (x *InsertSpaceUserResult) GetInviteCode() string {
if x != nil {
return x.InviteCode
}
return ""
}
var File_space_space_user_types_proto protoreflect.FileDescriptor
var file_space_space_user_types_proto_rawDesc = []byte{
0x0a, 0x1c, 0x73, 0x70, 0x61, 0x63, 0x65, 0x2f, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x75, 0x73,
0x65, 0x72, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x05,
0x73, 0x70, 0x61, 0x63, 0x65, 0x1a, 0x09, 0x61, 0x70, 0x69, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
0x22, 0x48, 0x0a, 0x15, 0x49, 0x6e, 0x73, 0x65, 0x72, 0x74, 0x53, 0x70, 0x61, 0x63, 0x65, 0x55,
0x73, 0x65, 0x72, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x12, 0x2f, 0x0a, 0x0a, 0x49, 0x6e, 0x76,
0x69, 0x74, 0x65, 0x43, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x42, 0x0f, 0xca,
0xbb, 0x18, 0x0b, 0x69, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x52, 0x0a,
0x49, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x43, 0x6f, 0x64, 0x65, 0x22, 0x70, 0x0a, 0x15, 0x49, 0x6e,
0x73, 0x65, 0x72, 0x74, 0x53, 0x70, 0x61, 0x63, 0x65, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x73,
0x75, 0x6c, 0x74, 0x12, 0x26, 0x0a, 0x07, 0x53, 0x70, 0x61, 0x63, 0x65, 0x49, 0x64, 0x18, 0x01,
0x20, 0x01, 0x28, 0x03, 0x42, 0x0c, 0xca, 0xbb, 0x18, 0x08, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f,
0x69, 0x64, 0x52, 0x07, 0x53, 0x70, 0x61, 0x63, 0x65, 0x49, 0x64, 0x12, 0x2f, 0x0a, 0x0a, 0x49,
0x6e, 0x76, 0x69, 0x74, 0x65, 0x43, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42,
0x0f, 0xca, 0xbb, 0x18, 0x0b, 0x69, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x5f, 0x63, 0x6f, 0x64, 0x65,
0x52, 0x0a, 0x49, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x43, 0x6f, 0x64, 0x65, 0x42, 0x22, 0x5a, 0x20,
0x63, 0x6e, 0x2f, 0x6f, 0x6e, 0x65, 0x61, 0x6f, 0x2f, 0x62, 0x61, 0x73, 0x65, 0x2d, 0x67, 0x6f,
0x2f, 0x62, 0x69, 0x7a, 0x2f, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x2f, 0x73, 0x70, 0x61, 0x63, 0x65,
0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
file_space_space_user_types_proto_rawDescOnce sync.Once
file_space_space_user_types_proto_rawDescData = file_space_space_user_types_proto_rawDesc
)
func file_space_space_user_types_proto_rawDescGZIP() []byte {
file_space_space_user_types_proto_rawDescOnce.Do(func() {
file_space_space_user_types_proto_rawDescData = protoimpl.X.CompressGZIP(file_space_space_user_types_proto_rawDescData)
})
return file_space_space_user_types_proto_rawDescData
}
var file_space_space_user_types_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
var file_space_space_user_types_proto_goTypes = []interface{}{
(*InsertSpaceUserParams)(nil), // 0: space.InsertSpaceUserParams
(*InsertSpaceUserResult)(nil), // 1: space.InsertSpaceUserResult
}
var file_space_space_user_types_proto_depIdxs = []int32{
0, // [0:0] is the sub-list for method output_type
0, // [0:0] is the sub-list for method input_type
0, // [0:0] is the sub-list for extension type_name
0, // [0:0] is the sub-list for extension extendee
0, // [0:0] is the sub-list for field type_name
}
func init() { file_space_space_user_types_proto_init() }
func file_space_space_user_types_proto_init() {
if File_space_space_user_types_proto != nil {
return
}
if !protoimpl.UnsafeEnabled {
file_space_space_user_types_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*InsertSpaceUserParams); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_space_space_user_types_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*InsertSpaceUserResult); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_space_space_user_types_proto_rawDesc,
NumEnums: 0,
NumMessages: 2,
NumExtensions: 0,
NumServices: 0,
},
GoTypes: file_space_space_user_types_proto_goTypes,
DependencyIndexes: file_space_space_user_types_proto_depIdxs,
MessageInfos: file_space_space_user_types_proto_msgTypes,
}.Build()
File_space_space_user_types_proto = out.File
file_space_space_user_types_proto_rawDesc = nil
file_space_space_user_types_proto_goTypes = nil
file_space_space_user_types_proto_depIdxs = nil
}
@@ -5,12 +5,15 @@ package router
import (
auth "cn/oneao/base-go/biz/router/auth"
money "cn/oneao/base-go/biz/router/money"
space "cn/oneao/base-go/biz/router/space"
"github.com/cloudwego/hertz/pkg/app/server"
)
// GeneratedRegister registers routers generated by IDL.
func GeneratedRegister(r *server.Hertz) {
//INSERT_POINT: DO NOT DELETE THIS LINE!
space.Register(r)
auth.Register(r)
money.Register(r)
}
@@ -0,0 +1,24 @@
// Code generated by hertz generator. DO NOT EDIT.
package space
import (
space "cn/oneao/base-go/biz/handler/space"
"github.com/cloudwego/hertz/pkg/app/server"
)
/*
This file will register all the routes of the services in the master idl.
And it will update automatically when you use the "update" command for the idl.
So don't modify the contents of the file, or your code will be deleted when it is updated.
*/
// Register register routes based on the IDL 'api.${HTTP Method}' annotation.
func Register(r *server.Hertz) {
root := r.Group("/", rootMw()...)
{
_space := root.Group("/space", _spaceMw()...)
_space.POST("/insert", append(_insertspaceMw(), space.InsertSpace)...)
}
}
@@ -0,0 +1,22 @@
// Code generated by hertz generator.
package space
import (
"github.com/cloudwego/hertz/pkg/app"
)
func rootMw() []app.HandlerFunc {
// your code...
return nil
}
func _spaceMw() []app.HandlerFunc {
// your code...
return nil
}
func _insertspaceMw() []app.HandlerFunc {
// your code...
return nil
}
@@ -0,0 +1,36 @@
-- 新增分享空间
-- name: InsertSpace :exec
INSERT INTO space (id,invite_code) VALUES ($1,$2);
-- 删除分享空间
-- name: DeleteSpace :exec
DELETE FROM space WHERE id = $1;
-- 根据id获取分享空间信息
-- name: GetSpaceById :one
SELECT * FROM space WHERE id = $1;
-- 查询用户所在的空间信息
-- name: GetSpaceByUserId :one
SELECT s.*
FROM space s
JOIN space_user m
ON s.id = m.space_id
WHERE m.user_id = $1;
-- 获取空间下的所有用户信息
-- name: GetSpaceUsers :many
SELECT
u.id as user_id,
u.username,
su.role,
su.join_time
FROM space_user su
JOIN space s ON su.space_id = s.id
JOIN user_info u ON su.user_id = u.id
WHERE s.id = $1
ORDER BY su.join_time ASC;
-- 根据验证码获取空间
-- name: GetSpaceByInviteCode :one
SELECT * FROM space WHERE invite_code = $1;
@@ -0,0 +1,19 @@
-- 加入空间
-- name: InsertSpaceUser :exec
INSERT INTO space_user(id,space_id,user_id,role)
VALUES ($1,$2,$3,$4);
-- 删除空间下的单个用户
-- name: DeleteSpaceUser :exec
DELETE FROM space_user WHERE user_id = $1;
-- 删除空间下的所有用户
-- name: DeleteSpaceUserAll :exec
DELETE FROM space_user WHERE space_id = $1;
-- 修改角色
-- name: UpdateSpaceUserRole :exec
UPDATE space_user
SET role = $1
WHERE user_id = $2
AND space_id = $3;
@@ -38,6 +38,34 @@ type MoneyUserCategory struct {
CreateTime pgtype.Timestamp
}
// 共享空间(一个空间包含多个成员)
type Space struct {
// 空间ID
ID int64
// 空间名称
Name pgtype.Text
// 空间邀请码
InviteCode string
// 空间创建时间
CreateTime pgtype.Timestamp
// 空间更新时间
UpdateTime pgtype.Timestamp
}
// 共享空间成员表
type SpaceUser struct {
// 主键ID
ID int64
// 所属空间ID
SpaceID int64
// 用户ID(唯一,每用户只加入一个空间)
UserID int64
// 成员角色:0创建者 1管理员 2普通
Role int32
// 加入空间时间
JoinTime pgtype.Timestamp
}
type UserInfo struct {
ID int64
SeqNo pgtype.Int4
@@ -0,0 +1,141 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.30.0
// source: space.sql
package repo
import (
"context"
"github.com/jackc/pgx/v5/pgtype"
)
const deleteSpace = `-- name: DeleteSpace :exec
DELETE FROM space WHERE id = $1
`
// 删除分享空间
func (q *Queries) DeleteSpace(ctx context.Context, id int64) error {
_, err := q.db.Exec(ctx, deleteSpace, id)
return err
}
const getSpaceById = `-- name: GetSpaceById :one
SELECT id, name, invite_code, create_time, update_time FROM space WHERE id = $1
`
// 根据id获取分享空间信息
func (q *Queries) GetSpaceById(ctx context.Context, id int64) (Space, error) {
row := q.db.QueryRow(ctx, getSpaceById, id)
var i Space
err := row.Scan(
&i.ID,
&i.Name,
&i.InviteCode,
&i.CreateTime,
&i.UpdateTime,
)
return i, err
}
const getSpaceByInviteCode = `-- name: GetSpaceByInviteCode :one
SELECT id, name, invite_code, create_time, update_time FROM space WHERE invite_code = $1
`
// 根据验证码获取空间
func (q *Queries) GetSpaceByInviteCode(ctx context.Context, inviteCode string) (Space, error) {
row := q.db.QueryRow(ctx, getSpaceByInviteCode, inviteCode)
var i Space
err := row.Scan(
&i.ID,
&i.Name,
&i.InviteCode,
&i.CreateTime,
&i.UpdateTime,
)
return i, err
}
const getSpaceByUserId = `-- name: GetSpaceByUserId :one
SELECT s.id, s.name, s.invite_code, s.create_time, s.update_time
FROM space s
JOIN space_user m
ON s.id = m.space_id
WHERE m.user_id = $1
`
// 查询用户所在的空间信息
func (q *Queries) GetSpaceByUserId(ctx context.Context, userID int64) (Space, error) {
row := q.db.QueryRow(ctx, getSpaceByUserId, userID)
var i Space
err := row.Scan(
&i.ID,
&i.Name,
&i.InviteCode,
&i.CreateTime,
&i.UpdateTime,
)
return i, err
}
const getSpaceUsers = `-- name: GetSpaceUsers :many
SELECT
u.id as user_id,
u.username,
su.role,
su.join_time
FROM space_user su
JOIN space s ON su.space_id = s.id
JOIN user_info u ON su.user_id = u.id
WHERE s.id = $1
ORDER BY su.join_time ASC
`
type GetSpaceUsersRow struct {
UserID int64
Username pgtype.Text
Role int32
JoinTime pgtype.Timestamp
}
// 获取空间下的所有用户信息
func (q *Queries) GetSpaceUsers(ctx context.Context, id int64) ([]GetSpaceUsersRow, error) {
rows, err := q.db.Query(ctx, getSpaceUsers, id)
if err != nil {
return nil, err
}
defer rows.Close()
var items []GetSpaceUsersRow
for rows.Next() {
var i GetSpaceUsersRow
if err := rows.Scan(
&i.UserID,
&i.Username,
&i.Role,
&i.JoinTime,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const insertSpace = `-- name: InsertSpace :exec
INSERT INTO space (id,invite_code) VALUES ($1,$2)
`
type InsertSpaceParams struct {
ID int64
InviteCode string
}
// 新增分享空间
func (q *Queries) InsertSpace(ctx context.Context, arg InsertSpaceParams) error {
_, err := q.db.Exec(ctx, insertSpace, arg.ID, arg.InviteCode)
return err
}
@@ -0,0 +1,72 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.30.0
// source: space_user.sql
package repo
import (
"context"
)
const deleteSpaceUser = `-- name: DeleteSpaceUser :exec
DELETE FROM space_user WHERE user_id = $1
`
// 删除空间下的单个用户
func (q *Queries) DeleteSpaceUser(ctx context.Context, userID int64) error {
_, err := q.db.Exec(ctx, deleteSpaceUser, userID)
return err
}
const deleteSpaceUserAll = `-- name: DeleteSpaceUserAll :exec
DELETE FROM space_user WHERE space_id = $1
`
// 删除空间下的所有用户
func (q *Queries) DeleteSpaceUserAll(ctx context.Context, spaceID int64) error {
_, err := q.db.Exec(ctx, deleteSpaceUserAll, spaceID)
return err
}
const insertSpaceUser = `-- name: InsertSpaceUser :exec
INSERT INTO space_user(id,space_id,user_id,role)
VALUES ($1,$2,$3,$4)
`
type InsertSpaceUserParams struct {
ID int64
SpaceID int64
UserID int64
Role int32
}
// 加入空间
func (q *Queries) InsertSpaceUser(ctx context.Context, arg InsertSpaceUserParams) error {
_, err := q.db.Exec(ctx, insertSpaceUser,
arg.ID,
arg.SpaceID,
arg.UserID,
arg.Role,
)
return err
}
const updateSpaceUserRole = `-- name: UpdateSpaceUserRole :exec
UPDATE space_user
SET role = $1
WHERE user_id = $2
AND space_id = $3
`
type UpdateSpaceUserRoleParams struct {
Role int32
UserID int64
SpaceID int64
}
// 修改角色
func (q *Queries) UpdateSpaceUserRole(ctx context.Context, arg UpdateSpaceUserRoleParams) error {
_, err := q.db.Exec(ctx, updateSpaceUserRole, arg.Role, arg.UserID, arg.SpaceID)
return err
}
+33 -4
View File
@@ -41,8 +41,37 @@ CREATE TABLE money_user_category(
create_time timestamp DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE share_space(
id BIGINT PRIMARY KEY,
name VARCHAR(100) NOT NULL
)
-- 共享空间表
CREATE TABLE space (
id BIGSERIAL PRIMARY KEY,
name VARCHAR(100) DEFAULT '',
invite_code VARCHAR(30) NOT NULL,
create_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
update_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);
-- 注释
COMMENT ON TABLE space IS '共享空间(一个空间包含多个成员)';
COMMENT ON COLUMN space.id IS '空间ID';
COMMENT ON COLUMN space.name IS '空间名称';
COMMENT ON COLUMN space.invite_code IS '空间邀请码';
COMMENT ON COLUMN space.create_time IS '空间创建时间';
COMMENT ON COLUMN space.update_time IS '空间更新时间';
-- 空间成员表(每个用户只能加入一个空间)
CREATE TABLE space_user (
id BIGSERIAL PRIMARY KEY,
space_id BIGINT NOT NULL,
user_id BIGINT NOT NULL,
role INT NOT NULL DEFAULT 2,
join_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);
-- 注释
COMMENT ON TABLE space_user IS '共享空间成员表';
COMMENT ON COLUMN space_user.id IS '主键ID';
COMMENT ON COLUMN space_user.space_id IS '所属空间ID';
COMMENT ON COLUMN space_user.user_id IS '用户ID(唯一,每用户只加入一个空间)';
COMMENT ON COLUMN space_user.role IS '成员角色:0创建者 1管理员 2普通';
COMMENT ON COLUMN space_user.join_time IS '加入空间时间';
@@ -0,0 +1,16 @@
syntax = "proto3";
package space;
option go_package = "space";
import "api.proto";
import "space/space_types.proto";
//import "space/space_user_types.proto";
service SpaceService {
rpc InsertSpace(InsertSpaceParams) returns (InsertSpaceResult){
option (api.post) = '/space/insert';
};
}
@@ -0,0 +1,39 @@
syntax = "proto3";
package space;
option go_package = "space";
import "api.proto";
// 创建空间
message InsertSpaceParams {
}
message InsertSpaceResult {
int64 Id = 1 [(api.body)="id"];
string InviteCode = 2 [(api.body)="invite_code"];
}
// 解散空间
message DeleteSpaceParams {
}
message DeleteSpaceResult {
}
// 获取空间下的所有用户
message QuerySpaceUsersParams{
int64 SpaceId = 1 [(api.body)="space_id", (api.vd)="$ != 0"];
}
message SpaceUser {
}
message QuerySpaceUsersResult{
}
@@ -0,0 +1,17 @@
syntax = "proto3";
package space;
option go_package = "space";
import "api.proto";
// 加入空间
message InsertSpaceUserParams {
int64 InviteCode = 1 [(api.body)="invite_code"]; // 邀请码
}
message InsertSpaceUserResult {
int64 SpaceId = 1 [(api.body)="space_id"];
string InviteCode = 2 [(api.body)="invite_code"];
}
@@ -0,0 +1,22 @@
package utils
import (
"crypto/rand"
"math/big"
)
// GenerateCode 生成指定长度的随机邀请码(大写字母+数字)
func GenerateCode(length int) (string, error) {
const charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
code := make([]byte, length)
for i := 0; i < length; i++ {
num, err := rand.Int(rand.Reader, big.NewInt(int64(len(charset))))
if err != nil {
return "", err
}
code[i] = charset[num.Int64()]
}
return string(code), nil
}