feat(bulletin): add group bulletin board (信息公示板)
- Add bulletin_posts and bulletin_reads PocketBase collections with migrations - Add BulletinPost/BulletinRead types and priority levels (low/normal/high/urgent) - Add bulletin API (CRUD, read tracking, realtime subscription) - Add bulletin Pinia store with pinned/normal sections and expiration filtering - Add BulletinPinned header component for pinned announcements - Add BulletinBoard tab component with sectioned list layout - Add BulletinPostCard with priority tags, unread dots, and hover actions - Add CreateBulletinDialog with title, content, priority, pinned, expiresAt - Integrate bulletin tab into GroupView with Bell icon and real-time updates Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,113 @@
|
||||
import { pb } from './pocketbase'
|
||||
import type { BulletinPost, BulletinRead } from '@/types'
|
||||
|
||||
export async function createBulletin(data: {
|
||||
group: string
|
||||
title: string
|
||||
content: string
|
||||
priority?: 'low' | 'normal' | 'high' | 'urgent'
|
||||
pinned?: boolean
|
||||
expiresAt?: string
|
||||
}): Promise<BulletinPost> {
|
||||
const user = pb.authStore.model
|
||||
if (!user) throw new Error('未登录')
|
||||
|
||||
const post = await pb.collection('bulletin_posts').create({
|
||||
group: data.group,
|
||||
creator: user.id,
|
||||
title: data.title,
|
||||
content: data.content,
|
||||
priority: data.priority || 'normal',
|
||||
pinned: data.pinned || false,
|
||||
expiresAt: data.expiresAt || '',
|
||||
})
|
||||
return post as unknown as BulletinPost
|
||||
}
|
||||
|
||||
export async function listBulletins(groupId: string): Promise<BulletinPost[]> {
|
||||
const result = await pb.collection('bulletin_posts').getFullList({
|
||||
filter: `group="${groupId}"`,
|
||||
sort: '-pinned,-created',
|
||||
expand: 'creator',
|
||||
$autoCancel: false,
|
||||
})
|
||||
return result as unknown as BulletinPost[]
|
||||
}
|
||||
|
||||
export async function getBulletin(postId: string): Promise<BulletinPost> {
|
||||
const result = await pb.collection('bulletin_posts').getOne(postId, {
|
||||
expand: 'creator',
|
||||
})
|
||||
return result as unknown as BulletinPost
|
||||
}
|
||||
|
||||
export async function updateBulletin(
|
||||
postId: string,
|
||||
data: {
|
||||
title?: string
|
||||
content?: string
|
||||
priority?: 'low' | 'normal' | 'high' | 'urgent'
|
||||
pinned?: boolean
|
||||
expiresAt?: string
|
||||
}
|
||||
): Promise<BulletinPost> {
|
||||
const payload: Record<string, any> = {}
|
||||
if (data.title !== undefined) payload.title = data.title
|
||||
if (data.content !== undefined) payload.content = data.content
|
||||
if (data.priority !== undefined) payload.priority = data.priority
|
||||
if (data.pinned !== undefined) payload.pinned = data.pinned
|
||||
if (data.expiresAt !== undefined) payload.expiresAt = data.expiresAt
|
||||
|
||||
const result = await pb.collection('bulletin_posts').update(postId, payload)
|
||||
return result as unknown as BulletinPost
|
||||
}
|
||||
|
||||
export async function deleteBulletin(postId: string): Promise<void> {
|
||||
await pb.collection('bulletin_posts').delete(postId)
|
||||
}
|
||||
|
||||
// 已读追踪
|
||||
export async function markBulletinAsRead(postId: string): Promise<BulletinRead | null> {
|
||||
const user = pb.authStore.model
|
||||
if (!user) return null
|
||||
|
||||
// 检查是否已存在
|
||||
const existing = await pb.collection('bulletin_reads').getList(1, 1, {
|
||||
filter: `post="${postId}" && user="${user.id}"`,
|
||||
$autoCancel: false,
|
||||
})
|
||||
if (existing.items.length > 0) return existing.items[0] as unknown as BulletinRead
|
||||
|
||||
const result = await pb.collection('bulletin_reads').create({
|
||||
post: postId,
|
||||
user: user.id,
|
||||
})
|
||||
return result as unknown as BulletinRead
|
||||
}
|
||||
|
||||
export async function listMyReads(): Promise<BulletinRead[]> {
|
||||
const user = pb.authStore.model
|
||||
if (!user) return []
|
||||
|
||||
const result = await pb.collection('bulletin_reads').getFullList({
|
||||
filter: `user="${user.id}"`,
|
||||
$autoCancel: false,
|
||||
})
|
||||
return result as unknown as BulletinRead[]
|
||||
}
|
||||
|
||||
// 实时订阅
|
||||
export async function subscribeBulletins(
|
||||
groupId: string,
|
||||
callback: (data: any) => void
|
||||
): Promise<() => void> {
|
||||
await pb.collection('bulletin_posts').subscribe('*', (data) => {
|
||||
if (data.record?.group === groupId) {
|
||||
callback(data)
|
||||
}
|
||||
})
|
||||
|
||||
return () => {
|
||||
pb.collection('bulletin_posts').unsubscribe('*')
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user