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,118 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { useBulletinStore } from '@/stores/bulletin'
|
||||
import { BulletinPriorityMap } from '@/types'
|
||||
import type { BulletinPost } from '@/types'
|
||||
|
||||
const emit = defineEmits<{
|
||||
viewAll: []
|
||||
}>()
|
||||
|
||||
const bulletinStore = useBulletinStore()
|
||||
|
||||
const hasPinned = computed(() => bulletinStore.pinnedPosts.length > 0)
|
||||
|
||||
function handleClick(post: BulletinPost) {
|
||||
bulletinStore.markAsRead(post.id)
|
||||
emit('viewAll')
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="hasPinned" class="bulletin-pinned">
|
||||
<div
|
||||
v-for="post in bulletinStore.pinnedPosts"
|
||||
:key="post.id"
|
||||
class="pinned-item"
|
||||
:class="{ 'pinned-item--urgent': post.priority === 'urgent' }"
|
||||
@click="handleClick(post)"
|
||||
>
|
||||
<span class="pinned-item__dot" />
|
||||
<span class="pinned-item__priority">{{ BulletinPriorityMap[post.priority] }}</span>
|
||||
<span class="pinned-item__title">{{ post.title }}</span>
|
||||
<span v-if="!bulletinStore.isRead(post.id)" class="pinned-item__unread" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.bulletin-pinned {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.pinned-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 10px 16px;
|
||||
background: var(--gg-bg-card);
|
||||
border: 1px solid var(--gg-border);
|
||||
border-left: 3px solid var(--gg-primary);
|
||||
border-radius: var(--gg-radius-md);
|
||||
cursor: pointer;
|
||||
transition: border-color 0.2s, box-shadow 0.2s;
|
||||
}
|
||||
|
||||
.pinned-item:hover {
|
||||
border-color: var(--gg-primary-light);
|
||||
box-shadow: 0 0 16px rgba(5, 150, 105, 0.08);
|
||||
}
|
||||
|
||||
.pinned-item--urgent {
|
||||
border-left-color: var(--gg-danger);
|
||||
background: rgba(239, 68, 68, 0.04);
|
||||
}
|
||||
|
||||
.pinned-item--urgent:hover {
|
||||
box-shadow: 0 0 16px rgba(239, 68, 68, 0.08);
|
||||
}
|
||||
|
||||
.pinned-item__dot {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
border-radius: 50%;
|
||||
background: var(--gg-primary);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.pinned-item--urgent .pinned-item__dot {
|
||||
background: var(--gg-danger);
|
||||
}
|
||||
|
||||
.pinned-item__priority {
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
padding: 1px 6px;
|
||||
border-radius: 4px;
|
||||
background: var(--gg-bg-elevated);
|
||||
color: var(--gg-text-muted);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.pinned-item--urgent .pinned-item__priority {
|
||||
background: rgba(239, 68, 68, 0.12);
|
||||
color: var(--gg-danger);
|
||||
}
|
||||
|
||||
.pinned-item__title {
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
color: var(--gg-text);
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.pinned-item__unread {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
background: var(--gg-danger);
|
||||
box-shadow: 0 0 6px var(--gg-danger);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user