fix: bug fixes and UX improvements (v0.3.5)
- Fix clipboard copy error in HTTP environment with execCommand fallback - Fix team invite page not loading user groups, always showing "join group first" - Fix JoinGroupPage isMember check using group object instead of user ID - Fix cancelRSVP deleting all users' RSVP records instead of current user's - Fix event detail not loading event data itself - Fix event comment avatar URL missing PocketBase baseUrl prefix - Fix event creation missing endTime > startTime validation - Fix event manage/delete permission split (creator+owner vs creator+owner) - Fix event create button only visible to admins, now all members can create - Fix event expand not subscribing to comments/RSVP realtime updates - Fix event relative time not using status field - Remove duplicate create/join group buttons from header and welcome bar - Refactor team invite link to use API function Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -190,6 +190,8 @@ export function subscribeGroup(groupId: string, callback: (group: Group) => void
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 转让群主
|
// 转让群主
|
||||||
|
// 安全提示:PB groups updateRule 允许认证用户更新,前端校验是唯一防线。
|
||||||
|
// 如需服务端强制保护,需添加 PocketBase JS hooks 或改用更严格的 updateRule。
|
||||||
export async function transferGroupOwnership(groupId: string, newOwnerId: string) {
|
export async function transferGroupOwnership(groupId: string, newOwnerId: string) {
|
||||||
const user = pb.authStore.model
|
const user = pb.authStore.model
|
||||||
if (!user) throw new Error('未登录')
|
if (!user) throw new Error('未登录')
|
||||||
|
|||||||
@@ -38,6 +38,14 @@ export async function getGroupTeamSessions(groupId: string): Promise<TeamSession
|
|||||||
return result.items as unknown as TeamSession[]
|
return result.items as unknown as TeamSession[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 获取单个临时小组详情
|
||||||
|
export async function getTeamSession(sessionId: string): Promise<TeamSession> {
|
||||||
|
return pb.collection('team_sessions').getOne(sessionId, {
|
||||||
|
expand: 'sourceGroup',
|
||||||
|
$autoCancel: false
|
||||||
|
}) as unknown as TeamSession
|
||||||
|
}
|
||||||
|
|
||||||
// 更新临时小组状态
|
// 更新临时小组状态
|
||||||
export async function updateTeamStatus(sessionId: string, status: TeamStatus): Promise<TeamSession> {
|
export async function updateTeamStatus(sessionId: string, status: TeamStatus): Promise<TeamSession> {
|
||||||
const updateData: Partial<TeamSession> = { status }
|
const updateData: Partial<TeamSession> = { status }
|
||||||
|
|||||||
@@ -69,6 +69,10 @@ async function handleSubmit() {
|
|||||||
ElMessage.warning('请选择开始时间')
|
ElMessage.warning('请选择开始时间')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if (endTime.value && new Date(endTime.value) <= new Date(startTime.value)) {
|
||||||
|
ElMessage.warning('结束时间必须晚于开始时间')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
submitting.value = true
|
submitting.value = true
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -27,7 +27,13 @@ const commentInput = ref('')
|
|||||||
|
|
||||||
const canManage = computed(() => {
|
const canManage = computed(() => {
|
||||||
const userId = pb.authStore.model?.id
|
const userId = pb.authStore.model?.id
|
||||||
return groupStore.isGroupOwner || groupStore.isGroupAdmin ||
|
return groupStore.isGroupOwner ||
|
||||||
|
(props.event.creator === userId)
|
||||||
|
})
|
||||||
|
|
||||||
|
const canDelete = computed(() => {
|
||||||
|
const userId = pb.authStore.model?.id
|
||||||
|
return groupStore.isGroupOwner ||
|
||||||
(props.event.creator === userId)
|
(props.event.creator === userId)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -37,12 +43,13 @@ function formatDateTime(dateStr: string): string {
|
|||||||
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}`
|
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}`
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatRelative(dateStr: string): string {
|
function formatRelative(event: Event): string {
|
||||||
const diff = new Date(dateStr).getTime() - Date.now()
|
if (event.status === 'completed') return '已结束'
|
||||||
const days = Math.floor(diff / (1000 * 60 * 60 * 24))
|
if (event.status === 'cancelled') return '已取消'
|
||||||
if (diff < 0 && days === 0) return '进行中'
|
if (event.status === 'ongoing') return '进行中'
|
||||||
if (diff < 0) return '已结束'
|
const diff = new Date(event.startTime).getTime() - Date.now()
|
||||||
if (days === 0) return '今天'
|
const days = Math.ceil(diff / (1000 * 60 * 60 * 24))
|
||||||
|
if (days <= 0) return '今天'
|
||||||
if (days === 1) return '明天'
|
if (days === 1) return '明天'
|
||||||
return `${days} 天后`
|
return `${days} 天后`
|
||||||
}
|
}
|
||||||
@@ -79,7 +86,7 @@ const comments = computed(() =>
|
|||||||
{{ EventStatusMap[event.status] }}
|
{{ EventStatusMap[event.status] }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<span class="event-relative">{{ formatRelative(event.startTime) }}</span>
|
<span class="event-relative">{{ formatRelative(event) }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="event-info">
|
<div class="event-info">
|
||||||
@@ -123,7 +130,7 @@ const comments = computed(() =>
|
|||||||
<p v-if="comments.length === 0" class="no-comments">暂无评论,来说两句吧</p>
|
<p v-if="comments.length === 0" class="no-comments">暂无评论,来说两句吧</p>
|
||||||
<div v-else class="comments-list">
|
<div v-else class="comments-list">
|
||||||
<div v-for="c in comments" :key="c.id" class="comment-item">
|
<div v-for="c in comments" :key="c.id" class="comment-item">
|
||||||
<img :src="c.expand?.user?.avatar || '/default-avatar.svg'" class="comment-avatar" />
|
<img :src="c.expand?.user?.avatar ? `${pb.baseUrl}/api/files/users/${c.user}/${c.expand.user.avatar}` : '/default-avatar.svg'" class="comment-avatar" />
|
||||||
<div class="comment-body">
|
<div class="comment-body">
|
||||||
<div class="comment-meta">
|
<div class="comment-meta">
|
||||||
<span class="comment-author">{{ displayName(c.expand?.user) }}</span>
|
<span class="comment-author">{{ displayName(c.expand?.user) }}</span>
|
||||||
@@ -149,9 +156,9 @@ const comments = computed(() =>
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="canManage" class="event-actions">
|
<div v-if="canManage || canDelete" class="event-actions">
|
||||||
<button class="action-link" @click.stop="emit('edit', event)">编辑</button>
|
<button v-if="canManage" class="action-link" @click.stop="emit('edit', event)">编辑</button>
|
||||||
<button class="action-link action-link--danger" @click.stop="emit('delete', event)">删除</button>
|
<button v-if="canDelete" class="action-link action-link--danger" @click.stop="emit('delete', event)">删除</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import type { Event } from '@/types'
|
|||||||
import { Plus } from '@element-plus/icons-vue'
|
import { Plus } from '@element-plus/icons-vue'
|
||||||
import CreateEventDialog from './CreateEventDialog.vue'
|
import CreateEventDialog from './CreateEventDialog.vue'
|
||||||
import EventCard from './EventCard.vue'
|
import EventCard from './EventCard.vue'
|
||||||
import { subscribeEvents } from '@/api/events'
|
import { subscribeEvents, subscribeEventComments, subscribeEventRSVPs } from '@/api/events'
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const eventStore = useEventStore()
|
const eventStore = useEventStore()
|
||||||
@@ -20,9 +20,11 @@ const showCreateDialog = ref(false)
|
|||||||
const editingEvent = ref<Event | null>(null)
|
const editingEvent = ref<Event | null>(null)
|
||||||
const expandedEventId = ref<string | null>(null)
|
const expandedEventId = ref<string | null>(null)
|
||||||
|
|
||||||
const canManage = computed(() => groupStore.canManageGroup)
|
const isMember = computed(() => !!groupStore.currentGroup)
|
||||||
|
|
||||||
let unsubscribeFn: (() => void) | null = null
|
let unsubscribeFn: (() => void) | null = null
|
||||||
|
let unsubscribeCommentsFn: (() => void) | null = null
|
||||||
|
let unsubscribeRSVPsFn: (() => void) | null = null
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
await eventStore.loadEvents(groupId)
|
await eventStore.loadEvents(groupId)
|
||||||
@@ -35,19 +37,31 @@ onMounted(async () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
if (unsubscribeFn) {
|
if (unsubscribeFn) unsubscribeFn()
|
||||||
unsubscribeFn()
|
if (unsubscribeCommentsFn) unsubscribeCommentsFn()
|
||||||
unsubscribeFn = null
|
if (unsubscribeRSVPsFn) unsubscribeRSVPsFn()
|
||||||
}
|
unsubscribeFn = null
|
||||||
|
unsubscribeCommentsFn = null
|
||||||
|
unsubscribeRSVPsFn = null
|
||||||
eventStore.clear()
|
eventStore.clear()
|
||||||
})
|
})
|
||||||
|
|
||||||
async function toggleExpand(event: Event) {
|
async function toggleExpand(event: Event) {
|
||||||
if (expandedEventId.value === event.id) {
|
if (expandedEventId.value === event.id) {
|
||||||
expandedEventId.value = null
|
expandedEventId.value = null
|
||||||
|
if (unsubscribeCommentsFn) { unsubscribeCommentsFn(); unsubscribeCommentsFn = null }
|
||||||
|
if (unsubscribeRSVPsFn) { unsubscribeRSVPsFn(); unsubscribeRSVPsFn = null }
|
||||||
} else {
|
} else {
|
||||||
expandedEventId.value = event.id
|
expandedEventId.value = event.id
|
||||||
await eventStore.loadCommentsAndRSVPs(event.id)
|
await eventStore.loadCommentsAndRSVPs(event.id)
|
||||||
|
if (unsubscribeCommentsFn) unsubscribeCommentsFn()
|
||||||
|
if (unsubscribeRSVPsFn) unsubscribeRSVPsFn()
|
||||||
|
unsubscribeCommentsFn = await subscribeEventComments(event.id, () => {
|
||||||
|
eventStore.loadCommentsAndRSVPs(event.id)
|
||||||
|
})
|
||||||
|
unsubscribeRSVPsFn = await subscribeEventRSVPs(event.id, () => {
|
||||||
|
eventStore.loadCommentsAndRSVPs(event.id)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -99,7 +113,7 @@ function onDialogClosed() {
|
|||||||
<div class="event-list">
|
<div class="event-list">
|
||||||
<div class="list-header">
|
<div class="list-header">
|
||||||
<h2 class="list-title">群组活动</h2>
|
<h2 class="list-title">群组活动</h2>
|
||||||
<button v-if="canManage" class="create-btn" @click="showCreateDialog = true">
|
<button v-if="isMember" class="create-btn" @click="showCreateDialog = true">
|
||||||
<el-icon><Plus /></el-icon> 发起活动
|
<el-icon><Plus /></el-icon> 发起活动
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -111,7 +125,7 @@ function onDialogClosed() {
|
|||||||
|
|
||||||
<div v-else-if="eventStore.events.length === 0" class="empty-state">
|
<div v-else-if="eventStore.events.length === 0" class="empty-state">
|
||||||
<p>暂无活动</p>
|
<p>暂无活动</p>
|
||||||
<p v-if="canManage" class="empty-hint">点击右上角发起一个活动吧</p>
|
<p v-if="isMember" class="empty-hint">点击右上角发起一个活动吧</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-else class="event-sections">
|
<div v-else class="event-sections">
|
||||||
|
|||||||
@@ -54,8 +54,23 @@ async function loadJoinRequests() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function copyInviteLink() {
|
function copyInviteLink() {
|
||||||
if (group.value?.id) {
|
if (!group.value?.id) return
|
||||||
navigator.clipboard.writeText(inviteLink.value)
|
const text = inviteLink.value
|
||||||
|
if (navigator.clipboard?.writeText) {
|
||||||
|
navigator.clipboard.writeText(text).then(() => {
|
||||||
|
ElMessage.success('邀请链接已复制,分享给好友即可加入')
|
||||||
|
}).catch(() => {
|
||||||
|
ElMessage.error('复制失败,请手动复制')
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
const ta = document.createElement('textarea')
|
||||||
|
ta.value = text
|
||||||
|
ta.style.position = 'fixed'
|
||||||
|
ta.style.opacity = '0'
|
||||||
|
document.body.appendChild(ta)
|
||||||
|
ta.select()
|
||||||
|
document.execCommand('copy')
|
||||||
|
document.body.removeChild(ta)
|
||||||
ElMessage.success('邀请链接已复制,分享给好友即可加入')
|
ElMessage.success('邀请链接已复制,分享给好友即可加入')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,8 +56,23 @@ const inviteLink = computed(() => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
function copyInviteLink() {
|
function copyInviteLink() {
|
||||||
if (inviteLink.value) {
|
if (!inviteLink.value) return
|
||||||
navigator.clipboard.writeText(inviteLink.value)
|
const text = inviteLink.value
|
||||||
|
if (navigator.clipboard?.writeText) {
|
||||||
|
navigator.clipboard.writeText(text).then(() => {
|
||||||
|
ElMessage.success('邀请链接已复制')
|
||||||
|
}).catch(() => {
|
||||||
|
ElMessage.error('复制失败,请手动复制')
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
const ta = document.createElement('textarea')
|
||||||
|
ta.value = text
|
||||||
|
ta.style.position = 'fixed'
|
||||||
|
ta.style.opacity = '0'
|
||||||
|
document.body.appendChild(ta)
|
||||||
|
ta.select()
|
||||||
|
document.execCommand('copy')
|
||||||
|
document.body.removeChild(ta)
|
||||||
ElMessage.success('邀请链接已复制')
|
ElMessage.success('邀请链接已复制')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
import { defineStore } from 'pinia'
|
import { defineStore } from 'pinia'
|
||||||
import { ref, computed } from 'vue'
|
import { ref, computed } from 'vue'
|
||||||
|
import pb from '@/api/pocketbase'
|
||||||
import type { Event, EventComment, EventRSVP, RSVPType } from '@/types'
|
import type { Event, EventComment, EventRSVP, RSVPType } from '@/types'
|
||||||
import {
|
import {
|
||||||
listEvents,
|
listEvents,
|
||||||
|
getEvent,
|
||||||
listEventComments,
|
listEventComments,
|
||||||
listEventRSVPs,
|
listEventRSVPs,
|
||||||
createEvent,
|
createEvent,
|
||||||
@@ -53,11 +55,12 @@ export const useEventStore = defineStore('event', () => {
|
|||||||
async function loadEventDetail(eventId: string) {
|
async function loadEventDetail(eventId: string) {
|
||||||
try {
|
try {
|
||||||
loading.value = true
|
loading.value = true
|
||||||
const [, commentData, rsvpData] = await Promise.all([
|
const [eventData, commentData, rsvpData] = await Promise.all([
|
||||||
listEventComments(eventId).then(() => null).catch(() => null),
|
getEvent(eventId),
|
||||||
listEventComments(eventId),
|
listEventComments(eventId),
|
||||||
listEventRSVPs(eventId)
|
listEventRSVPs(eventId)
|
||||||
])
|
])
|
||||||
|
currentEvent.value = eventData
|
||||||
comments.value = commentData
|
comments.value = commentData
|
||||||
rsvps.value = rsvpData
|
rsvps.value = rsvpData
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -161,7 +164,8 @@ export const useEventStore = defineStore('event', () => {
|
|||||||
async function cancelRSVP(eventId: string) {
|
async function cancelRSVP(eventId: string) {
|
||||||
try {
|
try {
|
||||||
await cancelEventRSVP(eventId)
|
await cancelEventRSVP(eventId)
|
||||||
rsvps.value = rsvps.value.filter(r => r.event !== eventId)
|
const userId = pb.authStore.model?.id
|
||||||
|
rsvps.value = rsvps.value.filter(r => !(r.event === eventId && r.user === userId))
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error('取消 RSVP 失败:', error)
|
console.error('取消 RSVP 失败:', error)
|
||||||
throw error
|
throw error
|
||||||
|
|||||||
@@ -10,6 +10,26 @@ interface LogEntry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const logs = ref<LogEntry[]>([
|
const logs = ref<LogEntry[]>([
|
||||||
|
{
|
||||||
|
version: 'v0.3.5',
|
||||||
|
date: '2026-04-21',
|
||||||
|
title: 'Bug 修复与体验优化',
|
||||||
|
items: [
|
||||||
|
{ type: 'fix', text: '修复邀请链接复制在 HTTP 环境下报错的问题,添加 execCommand 降级方案' },
|
||||||
|
{ type: 'fix', text: '修复小队邀请链接页面未加载用户群组数据,导致始终提示"需要先加入群组"' },
|
||||||
|
{ type: 'fix', text: '修复加入群组页面 isMember 判断错误,使用了群组对象而非用户 ID' },
|
||||||
|
{ type: 'fix', text: '修复取消 RSVP 时误删所有用户 RSVP 记录的问题' },
|
||||||
|
{ type: 'fix', text: '修复活动详情加载时未获取活动本身数据的问题' },
|
||||||
|
{ type: 'fix', text: '修复活动评论头像 URL 未拼接 PocketBase baseUrl 导致图片加载失败' },
|
||||||
|
{ type: 'fix', text: '修复活动创建未校验结束时间必须晚于开始时间' },
|
||||||
|
{ type: 'fix', text: '修复活动管理权限判断,拆分编辑权限(创建者+群主)和删除权限(创建者+群主)' },
|
||||||
|
{ type: 'fix', text: '修复活动发起按钮仅管理员可见,改为所有群组成员可发起' },
|
||||||
|
{ type: 'fix', text: '修复活动展开详情后未订阅评论和 RSVP 实时更新' },
|
||||||
|
{ type: 'fix', text: '修复活动相对时间未使用 status 字段判断状态的问题' },
|
||||||
|
{ type: 'refactor', text: '移除顶部 Header 和首页欢迎条中重复的"创建群组""加入群组"按钮' },
|
||||||
|
{ type: 'refactor', text: '小队邀请链接改用 API 函数替代原始 PocketBase 调用' },
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
version: 'v0.3.4',
|
version: 'v0.3.4',
|
||||||
date: '2026-04-21',
|
date: '2026-04-21',
|
||||||
|
|||||||
@@ -78,14 +78,6 @@ function openGameDetail(game: Game) {
|
|||||||
<span v-if="userStore.user?.statusNote" class="status-note">{{ userStore.user.statusNote }}</span>
|
<span v-if="userStore.user?.statusNote" class="status-note">{{ userStore.user.statusNote }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="!hasNoGroup" class="welcome-actions">
|
|
||||||
<button class="cta-btn cta-btn--primary" @click="showCreateGroup = true">
|
|
||||||
<el-icon><Plus /></el-icon> 创建群组
|
|
||||||
</button>
|
|
||||||
<button class="cta-btn cta-btn--secondary" @click="showJoinGroup = true">
|
|
||||||
<el-icon><Search /></el-icon> 加入群组
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<!-- 无群组引导 -->
|
<!-- 无群组引导 -->
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { useRoute, useRouter } from 'vue-router'
|
|||||||
import { ElMessage } from 'element-plus'
|
import { ElMessage } from 'element-plus'
|
||||||
import { getGroup, joinGroup, createJoinRequest } from '@/api/groups'
|
import { getGroup, joinGroup, createJoinRequest } from '@/api/groups'
|
||||||
import { useGroupStore } from '@/stores/group'
|
import { useGroupStore } from '@/stores/group'
|
||||||
import { isAuthenticated } from '@/api/pocketbase'
|
import { pb, isAuthenticated } from '@/api/pocketbase'
|
||||||
import type { Group } from '@/types'
|
import type { Group } from '@/types'
|
||||||
import { User, Link } from '@element-plus/icons-vue'
|
import { User, Link } from '@element-plus/icons-vue'
|
||||||
|
|
||||||
@@ -63,8 +63,9 @@ async function handleJoin() {
|
|||||||
|
|
||||||
const isMember = computed(() => {
|
const isMember = computed(() => {
|
||||||
if (!group.value) return false
|
if (!group.value) return false
|
||||||
const userId = groupStore.groups.find(g => g.id === groupId)
|
const userId = pb.authStore.model?.id
|
||||||
return !!userId
|
if (!userId) return false
|
||||||
|
return group.value.members?.includes(userId) || false
|
||||||
})
|
})
|
||||||
|
|
||||||
const isLoggedIn = computed(() => isAuthenticated())
|
const isLoggedIn = computed(() => isAuthenticated())
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { useRoute, useRouter } from 'vue-router'
|
|||||||
import { ElMessage } from 'element-plus'
|
import { ElMessage } from 'element-plus'
|
||||||
import { pb, isAuthenticated } from '@/api/pocketbase'
|
import { pb, isAuthenticated } from '@/api/pocketbase'
|
||||||
import { useGroupStore } from '@/stores/group'
|
import { useGroupStore } from '@/stores/group'
|
||||||
import { joinTeamSession } from '@/api/sessions'
|
import { joinTeamSession, getTeamSession } from '@/api/sessions'
|
||||||
import type { TeamSession } from '@/types'
|
import type { TeamSession } from '@/types'
|
||||||
import { Promotion, User } from '@element-plus/icons-vue'
|
import { Promotion, User } from '@element-plus/icons-vue'
|
||||||
|
|
||||||
@@ -21,11 +21,10 @@ const sessionId = route.params.sessionId as string
|
|||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
try {
|
try {
|
||||||
const record = await pb.collection('team_sessions').getOne(sessionId, {
|
if (isAuthenticated()) {
|
||||||
expand: 'sourceGroup',
|
await groupStore.loadGroups()
|
||||||
$autoCancel: false
|
}
|
||||||
}) as any
|
session.value = await getTeamSession(sessionId)
|
||||||
session.value = record as unknown as TeamSession
|
|
||||||
} catch {
|
} catch {
|
||||||
error.value = '小队不存在或已解散'
|
error.value = '小队不存在或已解散'
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
@@ -173,14 +173,6 @@ const pageTitle = computed(() => {
|
|||||||
<h2 class="page-title">{{ pageTitle }}</h2>
|
<h2 class="page-title">{{ pageTitle }}</h2>
|
||||||
|
|
||||||
<div class="header-actions">
|
<div class="header-actions">
|
||||||
<button class="header-action-btn" @click="showCreateGroup = true" title="创建群组">
|
|
||||||
<el-icon><Plus /></el-icon>
|
|
||||||
<span class="header-action-label">创建群组</span>
|
|
||||||
</button>
|
|
||||||
<button class="header-action-btn" @click="showJoinGroup = true" title="加入群组">
|
|
||||||
<el-icon><Search /></el-icon>
|
|
||||||
<span class="header-action-label">加入群组</span>
|
|
||||||
</button>
|
|
||||||
<button class="icon-btn" @click="notificationStore.togglePanel()">
|
<button class="icon-btn" @click="notificationStore.togglePanel()">
|
||||||
<span v-if="notificationStore.unreadCount > 0" class="badge">
|
<span v-if="notificationStore.unreadCount > 0" class="badge">
|
||||||
{{ notificationStore.unreadCount }}
|
{{ notificationStore.unreadCount }}
|
||||||
|
|||||||
Reference in New Issue
Block a user