feat: onboarding optimization, invite links, admin roles, and event board

- Home: hide duplicate create/join buttons when user has no groups
- Invite links: /join/group/:id and /join/team/:id pages for one-click joining
- Admin: group admins field, ownership transfer, member management toggle
- Events: new events collection with RSVP (going/interested/maybe) and comments

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
wjl
2026-04-21 17:27:24 +08:00
parent 0cde794c85
commit 2fec2108ca
19 changed files with 2644 additions and 21 deletions
+163
View File
@@ -0,0 +1,163 @@
import pb from './pocketbase'
import type { Event, EventComment, EventRSVP, RSVPType } from '@/types'
// 获取群组的活动列表
export async function listEvents(groupId: string): Promise<Event[]> {
const result = await pb.collection('events').getFullList({
filter: `group="${groupId}"`,
sort: '-startTime',
expand: 'creator',
$autoCancel: false
})
return result as unknown as Event[]
}
// 获取活动详情
export async function getEvent(eventId: string): Promise<Event> {
return pb.collection('events').getOne(eventId, {
expand: 'creator,group',
$autoCancel: false
}) as unknown as Event
}
// 创建活动
export async function createEvent(data: {
group: string
title: string
description?: string
location?: string
startTime: string
endTime?: string
maxParticipants?: number
}): Promise<Event> {
const user = pb.authStore.model
if (!user) throw new Error('未登录')
return pb.collection('events').create({
...data,
creator: user.id,
status: 'upcoming'
}) as unknown as Event
}
// 更新活动
export async function updateEvent(eventId: string, data: Partial<Event>): Promise<Event> {
return pb.collection('events').update(eventId, data) as unknown as Event
}
// 删除活动
export async function deleteEvent(eventId: string) {
return pb.collection('events').delete(eventId)
}
// 获取活动的评论
export async function listEventComments(eventId: string): Promise<EventComment[]> {
const result = await pb.collection('event_comments').getFullList({
filter: `event="${eventId}"`,
sort: '-created',
expand: 'user',
$autoCancel: false
})
return result as unknown as EventComment[]
}
// 创建评论
export async function createEventComment(eventId: string, content: string): Promise<EventComment> {
const user = pb.authStore.model
if (!user) throw new Error('未登录')
return pb.collection('event_comments').create({
event: eventId,
user: user.id,
content
}) as unknown as EventComment
}
// 删除评论
export async function deleteEventComment(commentId: string) {
return pb.collection('event_comments').delete(commentId)
}
// 获取活动的 RSVP 列表
export async function listEventRSVPs(eventId: string): Promise<EventRSVP[]> {
const result = await pb.collection('event_rsvps').getFullList({
filter: `event="${eventId}"`,
sort: '-created',
expand: 'user',
$autoCancel: false
})
return result as unknown as EventRSVP[]
}
// 创建/更新 RSVP
export async function setEventRSVP(
eventId: string,
type: RSVPType,
comment?: string
): Promise<EventRSVP> {
const user = pb.authStore.model
if (!user) throw new Error('未登录')
// 检查是否已有 RSVP
const existing = await pb.collection('event_rsvps').getList(1, 1, {
filter: `event="${eventId}" && user="${user.id}"`
})
if (existing.items.length > 0) {
return pb.collection('event_rsvps').update(existing.items[0].id, {
type,
comment
}) as unknown as EventRSVP
}
return pb.collection('event_rsvps').create({
event: eventId,
user: user.id,
type,
comment
}) as unknown as EventRSVP
}
// 取消 RSVP
export async function cancelEventRSVP(eventId: string) {
const user = pb.authStore.model
if (!user) throw new Error('未登录')
const existing = await pb.collection('event_rsvps').getList(1, 1, {
filter: `event="${eventId}" && user="${user.id}"`
})
if (existing.items.length > 0) {
return pb.collection('event_rsvps').delete(existing.items[0].id)
}
}
// 订阅活动变更
export function subscribeEvents(groupId: string, callback: () => void) {
return pb.collection('events').subscribe('*', (payload) => {
const record = payload.record as any
if (record.group === groupId) {
callback()
}
})
}
// 订阅活动评论变更
export function subscribeEventComments(eventId: string, callback: () => void) {
return pb.collection('event_comments').subscribe('*', (payload) => {
const record = payload.record as any
if (record.event === eventId) {
callback()
}
})
}
// 订阅 RSVP 变更
export function subscribeEventRSVPs(eventId: string, callback: () => void) {
return pb.collection('event_rsvps').subscribe('*', (payload) => {
const record = payload.record as any
if (record.event === eventId) {
callback()
}
})
}
+70
View File
@@ -189,6 +189,76 @@ export function subscribeGroup(groupId: string, callback: (group: Group) => void
})
}
// 转让群主
export async function transferGroupOwnership(groupId: string, newOwnerId: string) {
const user = pb.authStore.model
if (!user) throw new Error('未登录')
const group = await pb.collection('groups').getOne(groupId)
if (group.owner !== user.id) {
throw new Error('只有群主可以转让群组')
}
const admins = (group.admins as string[]) || []
// 将原群主加入 admins,新群主从 admins 中移除
const newAdmins = [...new Set([...admins, user.id])].filter(id => id !== newOwnerId)
return pb.collection('groups').update(groupId, {
owner: newOwnerId,
admins: newAdmins
})
}
// 添加管理员
export async function addGroupAdmin(groupId: string, userId: string) {
const user = pb.authStore.model
if (!user) throw new Error('未登录')
const group = await pb.collection('groups').getOne(groupId)
if (group.owner !== user.id) {
throw new Error('只有群主可以设置管理员')
}
const admins = (group.admins as string[]) || []
if (admins.includes(userId)) {
throw new Error('该用户已是管理员')
}
return pb.collection('groups').update(groupId, {
admins: [...admins, userId]
})
}
// 移除管理员
export async function removeGroupAdmin(groupId: string, userId: string) {
const user = pb.authStore.model
if (!user) throw new Error('未登录')
const group = await pb.collection('groups').getOne(groupId)
if (group.owner !== user.id) {
throw new Error('只有群主可以移除管理员')
}
const admins = (group.admins as string[]) || []
return pb.collection('groups').update(groupId, {
admins: admins.filter(id => id !== userId)
})
}
// 更新全员管理设置
export async function updateGroupMemberManage(groupId: string, allow: boolean) {
const user = pb.authStore.model
if (!user) throw new Error('未登录')
const group = await pb.collection('groups').getOne(groupId)
if (group.owner !== user.id) {
throw new Error('只有群主可以修改此设置')
}
return pb.collection('groups').update(groupId, { allowMemberManage: allow })
}
// 订阅加入申请变更
export function subscribeJoinRequests(groupId: string, callback: (request: JoinRequest) => void) {
return pb.collection('join_requests').subscribe('*', (payload) => {