This commit is contained in:
oneao committed 2026-03-02 22:09:47 +08:00
1 parent cf23acdabc
commit 2efeb7cee2
13 files changed
+271 -49

No files matched your search

@@ -183,6 +183,38 @@ func DissolveSpace(c fiber.Ctx) error {
return responsex.OK(c, "空间已成功解散")
}
// ExitSpace 退出空间
func ExitSpace(c fiber.Ctx) error {
dto := new(types.ExitSpaceDTO)
if err := requestx.BindAndValidate(c, dto); err != nil {
return err
}
userId, err := middleware.GetUserID(c)
if err != nil {
return err
}
spaceId := dto.SpaceId
_, err = db.Queries.GetSpaceById(c.Context(), spaceId)
if err != nil {
if errorx.IsNotFound(err) {
return responsex.Error(c, "空间不存在或已解散")
}
return err
}
if err := db.Queries.UpdateSpaceMemberExit(c.Context(), repo.UpdateSpaceMemberExitParams{
SpaceID: spaceId,
UserID: userId,
}); err != nil {
return err
}
return responsex.OK(c, "空间已成功退出")
}
// UpdateMemberName 更新群成员名称
func UpdateMemberName(c fiber.Ctx) error {
dto := new(types.UpdateMemberNameDTO)
@@ -257,6 +289,7 @@ func UpdateSpaceDefault(c fiber.Ctx) error {
return responsex.OK(c, "更新成功")
}
// UpdateSpaceName 更新空间名称
func UpdateSpaceName(c fiber.Ctx) error {
dto := new(types.UpdateSpaceNameDTO)
if err := requestx.BindAndValidate(c, dto); err != nil {
@@ -295,6 +328,7 @@ func UpdateSpaceName(c fiber.Ctx) error {
return responsex.OK(c, "更新成功")
}
// GetSpaceSettings 获取空间配置信息
func GetSpaceSettings(c fiber.Ctx) error {
dto := new(types.GetSpaceSettingDTO)
if err := requestx.BindAndValidate(c, dto); err != nil {
@@ -329,6 +363,7 @@ func GetSpaceSettings(c fiber.Ctx) error {
vo.MemberCount = row.MemberCount
vo.OwnerId = row.OwnerID
vo.SortNumber = row.SortNumber
vo.InviteCode = row.InviteCode
return responsex.OK(c, vo)
}
@@ -30,6 +30,7 @@ func SetupRouters(app *fiber.App) {
spaceRouter.Post("/member/default", space.UpdateSpaceDefault)
spaceRouter.Post("/member/name", space.UpdateMemberName)
spaceRouter.Post("/member/sortNumber", space.UpdateSpaceSortNumber)
spaceRouter.Post("/member/exit", space.ExitSpace)
// authGroup.Get("/user/profile", user.GetProfile)
// spaceGroup.Get("/space/info", space.GetInfo)
+5
View File
@@ -21,6 +21,7 @@ type UserSpace struct {
IsDefault int16 `json:"is_default"`
MemberName string `json:"member_name"`
SortNumber int32 `json:"sort_number"`
InviteCode string `json:"invite_code"`
}
type ListSpacesVO struct {
@@ -31,6 +32,10 @@ type DissolveSpaceDTO struct {
SpaceId int64 `json:"space_id" validate:"required"`
}
type ExitSpaceDTO struct {
SpaceId int64 `json:"space_id" validate:"required"`
}
type UpdateMemberNameDTO struct {
SpaceId int64 `json:"space_id" validate:"required"`
MemberName string `json:"member_name" validate:"required"`
+4 -3
View File
@@ -39,7 +39,7 @@ FROM space s
GROUP BY space_id
) mc
ON s.id = mc.space_id
WHERE sm.user_id = $1
WHERE sm.user_id = $1 AND s.status != 2 AND sm.status != 2
ORDER BY sm.is_default DESC, sm.sort_number ASC;
-- name: GetSpaceByUserIdAndSpaceId :one
@@ -51,7 +51,8 @@ SELECT
COALESCE(mc.member_count, 0) AS member_count,
sm.is_default,
sm.member_name,
sm.sort_number
sm.sort_number,
s.invite_code
FROM space s
JOIN space_member sm
ON s.id = sm.space_id
@@ -61,7 +62,7 @@ FROM space s
GROUP BY space_id
) mc
ON s.id = mc.space_id
WHERE sm.user_id = $1 AND s.id = $2
WHERE sm.user_id = $1 AND s.id = $2 AND s.status != 2 AND sm.status != 2
ORDER BY sm.is_default DESC, sm.sort_number ASC;
-- name: UpdateSpaceName :exec
@@ -38,4 +38,11 @@ WHERE user_id = $2
UPDATE space_member
SET sort_number = $1
WHERE user_id = $2
AND space_id = $3;
AND space_id = $3;
-- 退出空间
-- name: UpdateSpaceMemberExit :exec
UPDATE space_member
SET status = 2 AND exit_time = NOW()
WHERE space_id = $1
AND user_id = $2;
+6 -3
View File
@@ -75,7 +75,8 @@ SELECT
COALESCE(mc.member_count, 0) AS member_count,
sm.is_default,
sm.member_name,
sm.sort_number
sm.sort_number,
s.invite_code
FROM space s
JOIN space_member sm
ON s.id = sm.space_id
@@ -85,7 +86,7 @@ FROM space s
GROUP BY space_id
) mc
ON s.id = mc.space_id
WHERE sm.user_id = $1 AND s.id = $2
WHERE sm.user_id = $1 AND s.id = $2 AND s.status != 2 AND sm.status != 2
ORDER BY sm.is_default DESC, sm.sort_number ASC
`
@@ -103,6 +104,7 @@ type GetSpaceByUserIdAndSpaceIdRow struct {
IsDefault int16
MemberName string
SortNumber int32
InviteCode string
}
func (q *Queries) GetSpaceByUserIdAndSpaceId(ctx context.Context, arg GetSpaceByUserIdAndSpaceIdParams) (GetSpaceByUserIdAndSpaceIdRow, error) {
@@ -117,6 +119,7 @@ func (q *Queries) GetSpaceByUserIdAndSpaceId(ctx context.Context, arg GetSpaceBy
&i.IsDefault,
&i.MemberName,
&i.SortNumber,
&i.InviteCode,
)
return i, err
}
@@ -164,7 +167,7 @@ FROM space s
GROUP BY space_id
) mc
ON s.id = mc.space_id
WHERE sm.user_id = $1
WHERE sm.user_id = $1 AND s.status != 2 AND sm.status != 2
ORDER BY sm.is_default DESC, sm.sort_number ASC
`
@@ -108,6 +108,24 @@ func (q *Queries) UpdateSpaceIsDefault(ctx context.Context, arg UpdateSpaceIsDef
return err
}
const updateSpaceMemberExit = `-- name: UpdateSpaceMemberExit :exec
UPDATE space_member
SET status = 2 AND exit_time = NOW()
WHERE space_id = $1
AND user_id = $2
`
type UpdateSpaceMemberExitParams struct {
SpaceID int64
UserID int64
}
// 退出空间
func (q *Queries) UpdateSpaceMemberExit(ctx context.Context, arg UpdateSpaceMemberExitParams) error {
_, err := q.db.Exec(ctx, updateSpaceMemberExit, arg.SpaceID, arg.UserID)
return err
}
const updateSpaceMemberName = `-- name: UpdateSpaceMemberName :exec
UPDATE space_member SET member_name = $1
WHERE space_id = $2 AND user_id = $3
@@ -0,0 +1,96 @@
import * as Clipboard from 'expo-clipboard'
import { useLocalSearchParams } from 'expo-router'
import { useEffect, useState } from 'react'
import {
Modal,
Pressable,
Text,
TouchableOpacity,
View,
} from 'react-native'
import QRCode from 'react-native-qrcode-svg'
import { toast } from 'sonner-native'
import { AButton, ADivider, LucideIcon } from '@/components/common/ao'
import CustomHeader from '@/components/custom/Header'
export default function Invite() {
const params = useLocalSearchParams<{ invite_code: string }>()
const { invite_code } = params
const [qrcode, setQrcode] = useState('space:')
const [previewVisible, setPreviewVisible] = useState(false)
async function copyInviteCode() {
await Clipboard.setStringAsync(invite_code)
toast.success('邀请码已复制')
}
useEffect(() => {
setQrcode(`space:${invite_code}`)
console.log(`space:${invite_code}`)
}, [invite_code])
return (
<View className="flex-1 bg-[#f5f6f8]">
<CustomHeader title="分享空间" />
<View className="px-5 pt-6">
{/* 邀请码卡片 */}
<View className="bg-white rounded-2xl p-6 items-center">
<Text className="text-gray-500 text-sm mb-3">
复制邀请码邀请家庭成员加入
</Text>
<Text className="text-4xl font-bold tracking-[8px] text-gray-900 mb-4">
{invite_code}
</Text>
<AButton
onPress={copyInviteCode}
className="rounded-full"
leftIcon={
<LucideIcon icon="Copy" size={16} color="#fff" clickable={false} />
}
>
复制邀请码
</AButton>
</View>
<View className="my-8">
<ADivider direction="horizontal" text="或扫描二维码" />
</View>
{/* 二维码卡片 */}
<TouchableOpacity
activeOpacity={0.9}
onPress={() => setPreviewVisible(true)}
className="bg-white rounded-2xl py-8 items-center"
>
<QRCode
size={160}
value={qrcode || ''}
/>
</TouchableOpacity>
</View>
{/* 全屏预览(无动画) */}
<Modal
visible={previewVisible}
transparent={false}
>
<Pressable
onPress={() => setPreviewVisible(false)}
className="flex-1 bg-white items-center justify-center"
>
<QRCode
size={280}
value={qrcode || ''}
/>
</Pressable>
</Modal>
</View>
)
}
@@ -0,0 +1,10 @@
import { View } from 'react-native'
import CustomHeader from '@/components/custom/Header'
export default function () {
return (
<View className="flex-1">
<CustomHeader title="成员管理" />
</View>
)
}
@@ -14,7 +14,7 @@ import {
ActionsheetDragIndicator,
ActionsheetDragIndicatorWrapper,
} from '@/components/ui/actionsheet'
import { dissolveSpaceApi, getSpaceSettingsApi, updateSpaceDefaultApi, updateSpaceMemberNameApi, updateSpaceNameApi } from '@/request/api/space'
import { dissolveSpaceApi, exitSpaceApi, getSpaceSettingsApi, updateSpaceDefaultApi, updateSpaceMemberNameApi, updateSpaceNameApi } from '@/request/api/space'
import { useUserStore } from '@/store/user'
interface ItemProps {
@@ -65,6 +65,8 @@ export default function Settings() {
const { spaceId } = params
const [space, setSpace] = useState<UserSpace | null>(null)
// 是否自己的空间
const [isOwner, setIsOwner] = useState(false)
// 空间名称
const [showName, setShowName] = useState(false)
@@ -114,6 +116,15 @@ export default function Settings() {
}
}, [spaceId])
useEffect(() => {
if (space) {
setIsOwner(space.owner_id === userStore.userInfo.id)
}
else {
setIsOwner(false)
}
}, [space])
return (
<View className="flex-1 bg-[#f5f6f8]">
<CustomHeader title="空间设置" />
@@ -124,13 +135,14 @@ export default function Settings() {
)
: (
<View className="flex-1 px-4 pt-4">
<Text className="text-sm font-semibold text-gray-400 mb-2 tracking-wider">
常用设置
</Text>
<View className="bg-white rounded-xl overflow-hidden mb-6">
{
space?.owner_id === userStore.userInfo.id && (
isOwner && (
<Item
icon="Earth"
title="空间名称"
@@ -205,19 +217,42 @@ export default function Settings() {
/>
</View>
<Text className="text-sm font-semibold text-gray-400 mb-2 tracking-wider">
分享
</Text>
<View className="bg-white rounded-xl overflow-hidden mb-6">
<Item
icon="Share2"
title="邀请"
onPress={() => {
router.navigate({
pathname: '/space/manage/sub-page/invite',
params: {
invite_code: space.invite_code,
},
})
}}
/>
</View>
<Text className="text-sm font-semibold text-gray-400 mb-2 tracking-wider">
其它操作
</Text>
<View className="bg-white rounded-xl overflow-hidden">
<Item
icon="Users"
title="成员管理"
/>
{
isOwner && (
<Item
icon="Users"
title="成员管理"
/>
)
}
<Item
icon="LogOut"
title={userStore.userInfo.id === space.owner_id ? '解散空间' : '退出空间'}
title={isOwner ? '解散空间' : '退出空间'}
isLast
onPress={() => {
setShowDissolve(true)
@@ -397,7 +432,7 @@ export default function Settings() {
{/* 解散 */}
<AModal
title="提示"
body="确认解散该空间么?"
body={isOwner ? '确认解散该空间么?' : '确定解散空间么?'}
isOpen={showDissolve}
onClose={() => {
setShowDissolve(false)
@@ -411,18 +446,34 @@ export default function Settings() {
return
}
await dissolveSpaceApi({
space_id: space.space_id,
}).then((res) => {
if (res.isSuccess) {
setShowDissolve(false)
toast.success('解散成功')
if (isOwner) {
await dissolveSpaceApi({
space_id: space.space_id,
}).then((res) => {
if (res.isSuccess) {
setShowDissolve(false)
toast.success('已解散该空间')
setTimeout(() => {
router.back()
}, 300)
}
})
setTimeout(() => {
router.back()
}, 300)
}
})
}
else {
await exitSpaceApi({
space_id: space.space_id,
}).then((res) => {
if (res.isSuccess) {
setShowDissolve(false)
toast.success('已退出该空间')
setTimeout(() => {
router.back()
}, 300)
}
})
}
}}
/>
</View>
@@ -2,9 +2,7 @@ import type {
CreateSpaceParams,
CreateSpaceResult,
DissolveSpaceParams,
DissolveSpaceResult,
ExitSpaceParams,
ExitSpaceResult,
GetSpaceSettingsParams,
JoinSpaceParams,
JoinSpaceResult,
@@ -60,21 +58,21 @@ export function getSpaceSettingsApi(params: GetSpaceSettingsParams) {
return alovaInstance.Post<ResponseData<UserSpace>>(`${API_PREFIX}/settings`, params)
}
// 解散空间
export function dissolveSpaceApi(params: DissolveSpaceParams) {
return alovaInstance.Post<ResponseData<void>>(`${API_PREFIX}/dissolve`, params)
}
// 退出空间
export function exitSpaceApi(params: ExitSpaceParams) {
return alovaInstance.Post<ResponseData<void>>(`${API_PREFIX}/member/exit`, params)
}
// Join Space API
export function joinSpaceApi(params: JoinSpaceParams) {
return alovaInstance.Post<ResponseData<JoinSpaceResult>>(`${API_PREFIX}/join`, params)
}
// Dissolve Space API
export function dissolveSpaceApi(params: DissolveSpaceParams) {
return alovaInstance.Post<ResponseData<DissolveSpaceResult>>(`${API_PREFIX}/dissolve`, params)
}
// Exit Space API
export function exitSpaceApi(params: ExitSpaceParams) {
return alovaInstance.Post<ResponseData<ExitSpaceResult>>(`${API_PREFIX}/exit`, params)
}
// Remove Space User API
export function removeSpaceMemberApi(params: RemoveSpaceMemberParams) {
return alovaInstance.Post<ResponseData<RemoveSpaceMemberResult>>(`${API_PREFIX}/remove`, params)
+2 -2
View File
@@ -28,8 +28,8 @@ function isWhiteListed(url: string) {
}
const alovaInstance = createAlova({
// baseURL: 'http://192.168.5.6:8086/app',
baseURL: 'http://192.168.1.137:8086/app',
baseURL: 'http://192.168.5.6:8086/app',
// baseURL: 'http://192.168.1.137:8086/app',
requestAdapter: axiosRequestAdapter(),
timeout: 5000,
cacheFor: null,
@@ -8,6 +8,8 @@ export interface UserSpace {
member_name: string
sort_number: number
invite_code: string
}
// 创建空间
@@ -31,9 +33,11 @@ export interface ListUserSpacesResult {
export interface DissolveSpaceParams {
space_id: number
}
export interface DissolveSpaceResult {
}
// 退出空间
export interface ExitSpaceParams {
space_id: number
}
// 更新空间名称
export interface UpdateSpaceNameParams {
space_id: number
@@ -74,13 +78,6 @@ export interface JoinSpaceResult {
space_name: string
}
// 退出空间
export interface ExitSpaceParams {
space_id: number
}
export interface ExitSpaceResult {
}
// 踢出用户
export interface RemoveSpaceMemberParams {
space_id: number