feat: 成员Tab中支持设置Owner视角

This commit is contained in:
digua
2025-12-24 23:45:06 +08:00
parent c82334b1ad
commit 8175d175b5
9 changed files with 174 additions and 25 deletions
+1
View File
@@ -28,6 +28,7 @@ declare module 'vue' {
UModal: typeof import('./../node_modules/.pnpm/@nuxt+ui@4.2.1_@babel+parser@7.28.5_axios@1.13.2_embla-carousel@8.6.0_typescript@5.9.3__1572391ae10a8169a5c9784ec5cec455/node_modules/@nuxt/ui/dist/runtime/components/Modal.vue')['default']
UPopover: typeof import('./../node_modules/.pnpm/@nuxt+ui@4.2.1_@babel+parser@7.28.5_axios@1.13.2_embla-carousel@8.6.0_typescript@5.9.3__1572391ae10a8169a5c9784ec5cec455/node_modules/@nuxt/ui/dist/runtime/components/Popover.vue')['default']
UProgress: typeof import('./../node_modules/.pnpm/@nuxt+ui@4.2.1_@babel+parser@7.28.5_axios@1.13.2_embla-carousel@8.6.0_typescript@5.9.3__1572391ae10a8169a5c9784ec5cec455/node_modules/@nuxt/ui/dist/runtime/components/Progress.vue')['default']
USelect: typeof import('./../node_modules/.pnpm/@nuxt+ui@4.2.1_@babel+parser@7.28.5_axios@1.13.2_embla-carousel@8.6.0_typescript@5.9.3__1572391ae10a8169a5c9784ec5cec455/node_modules/@nuxt/ui/dist/runtime/components/Select.vue')['default']
USwitch: typeof import('./../node_modules/.pnpm/@nuxt+ui@4.2.1_@babel+parser@7.28.5_axios@1.13.2_embla-carousel@8.6.0_typescript@5.9.3__1572391ae10a8169a5c9784ec5cec455/node_modules/@nuxt/ui/dist/runtime/components/Switch.vue')['default']
UTabs: typeof import('./../node_modules/.pnpm/@nuxt+ui@4.2.1_@babel+parser@7.28.5_axios@1.13.2_embla-carousel@8.6.0_typescript@5.9.3__1572391ae10a8169a5c9784ec5cec455/node_modules/@nuxt/ui/dist/runtime/components/Tabs.vue')['default']
UTextarea: typeof import('./../node_modules/.pnpm/@nuxt+ui@4.2.1_@babel+parser@7.28.5_axios@1.13.2_embla-carousel@8.6.0_typescript@5.9.3__1572391ae10a8169a5c9784ec5cec455/node_modules/@nuxt/ui/dist/runtime/components/Textarea.vue')['default']
@@ -0,0 +1,105 @@
<script setup lang="ts">
import { ref, computed } from 'vue'
import type { MemberWithStats } from '@/types/analysis'
import { useSessionStore } from '@/stores/session'
const sessionStore = useSessionStore()
// Props
const props = defineProps<{
sessionId: string
members: MemberWithStats[]
isLoading?: boolean
chatType?: 'group' | 'private'
}>()
// Owner 配置相关
const isSavingOwner = ref(false)
// 获取成员显示名称
function getDisplayName(member: MemberWithStats): string {
return member.groupNickname || member.accountName || member.platformId
}
// 当前 owner 的成员信息
const currentOwner = computed(() => {
const ownerId = sessionStore.currentSession?.ownerId
if (!ownerId) return null
return props.members.find((m) => m.platformId === ownerId) || null
})
// 特殊值,用于表示"未设置"
const UNSET_VALUE = '__UNSET__'
// 成员选项(用于下拉选择)
const memberOptions = computed(() => {
return [
{ label: '未设置', value: UNSET_VALUE },
...props.members.map((m) => ({
label: `${getDisplayName(m)} (${m.platformId})`,
value: m.platformId,
})),
]
})
// 当前选中的 owner 值(用于 USelect
const selectedOwnerValue = computed(() => {
return sessionStore.currentSession?.ownerId || UNSET_VALUE
})
// 提示文字
const hintText = computed(() => {
if (currentOwner.value) {
return `当前:${getDisplayName(currentOwner.value)}`
}
return props.chatType === 'group' ? '选择你在群聊中的身份,便于数据分析' : '选择你在对话中的身份,便于数据分析'
})
// 更新 owner
async function updateOwner(value: string) {
const platformId = value === UNSET_VALUE ? null : value
isSavingOwner.value = true
try {
await sessionStore.updateSessionOwnerId(props.sessionId, platformId)
} catch (error) {
console.error('更新所有者失败:', error)
} finally {
isSavingOwner.value = false
}
}
</script>
<template>
<div class="w-150 rounded-lg border border-gray-200 bg-white p-3 shadow-sm dark:border-gray-700 dark:bg-gray-900">
<div class="flex items-center justify-between gap-3">
<div class="flex items-center gap-2">
<div
class="flex h-8 w-8 items-center justify-center rounded-full bg-linear-to-br"
:class="chatType === 'group' ? 'from-pink-400 to-pink-600' : 'from-purple-400 to-purple-600'"
>
<UIcon name="i-heroicons-user" class="h-4 w-4 text-white" />
</div>
<div>
<h3 class="text-sm font-medium text-gray-900 dark:text-white">设置Owner</h3>
<p class="text-xs text-gray-500 dark:text-gray-400">{{ hintText }}</p>
</div>
</div>
<div class="flex items-center gap-2">
<USelect
:model-value="selectedOwnerValue"
:items="memberOptions"
placeholder="选择成员"
class="w-48"
:disabled="isSavingOwner || isLoading"
@update:model-value="updateOwner"
/>
<UIcon
v-if="isSavingOwner"
name="i-heroicons-arrow-path"
class="h-4 w-4 animate-spin"
:class="chatType === 'group' ? 'text-pink-500' : 'text-purple-500'"
/>
</div>
</div>
</div>
</template>
@@ -1,6 +1,7 @@
<script setup lang="ts">
import { ref, computed, watch, onMounted } from 'vue'
import type { MemberWithStats } from '@/types/analysis'
import OwnerSelector from '@/components/analysis/member/OwnerSelector.vue'
// Props
const props = defineProps<{
@@ -17,6 +18,10 @@ const members = ref<MemberWithStats[]>([])
const isLoading = ref(false)
const searchQuery = ref('')
// 删除确认状态
const deletingMember = ref<MemberWithStats | null>(null)
const isDeleting = ref(false)
// 分页配置
const pageSize = 20
const currentPage = ref(1)
@@ -24,10 +29,6 @@ const currentPage = ref(1)
// 排序配置
const sortOrder = ref<'desc' | 'asc'>('desc') // desc = 发言多在前
// 删除确认状态
const deletingMember = ref<MemberWithStats | null>(null)
const isDeleting = ref(false)
// 正在保存别名的成员ID(用于显示加载状态)
const savingAliasesId = ref<number | null>(null)
@@ -188,6 +189,9 @@ onMounted(() => {
</div>
</div>
<!-- Owner配置 -->
<OwnerSelector class="mb-6" :session-id="sessionId" :members="members" :is-loading="isLoading" chat-type="group" />
<!-- 搜索框 -->
<div class="mb-4">
<UInput
@@ -1,6 +1,7 @@
<script setup lang="ts">
import { ref, computed, watch, onMounted } from 'vue'
import type { MemberWithStats } from '@/types/analysis'
import OwnerSelector from '@/components/analysis/member/OwnerSelector.vue'
// Props
const props = defineProps<{
@@ -104,6 +105,15 @@ onMounted(() => {
</div>
</div>
<!-- Owner配置 -->
<OwnerSelector
class="mb-6"
:session-id="sessionId"
:members="members"
:is-loading="isLoading"
chat-type="private"
/>
<!-- 加载状态 -->
<div v-if="isLoading" class="flex h-60 items-center justify-center">
<UIcon name="i-heroicons-arrow-path" class="h-8 w-8 animate-spin text-pink-500" />
+3 -1
View File
@@ -118,6 +118,7 @@ export interface DbMeta {
imported_at: number // 导入时间戳(秒)
group_id: string | null // 群ID(群聊类型有值,私聊为空)
group_avatar: string | null // 群头像(base64 Data URL
owner_id: string | null // 所有者/导出者的 platformId
}
/**
@@ -179,6 +180,7 @@ export interface ParseResult {
type: ChatType
groupId?: string // 群ID(群聊类型有值)
groupAvatar?: string // 群头像(base64 Data URL
ownerId?: string // 所有者/导出者的 platformId
}
members: ParsedMember[]
messages: ParsedMessage[]
@@ -200,6 +202,7 @@ export interface AnalysisSession {
dbPath: string // 数据库文件完整路径
groupId: string | null // 群ID(群聊类型有值,私聊为空)
groupAvatar: string | null // 群头像(base64 Data URL
ownerId: string | null // 所有者/导出者的 platformId
}
/**
@@ -223,4 +226,3 @@ export interface ImportResult {
sessionId?: string // 成功时返回会话ID
error?: string // 失败时返回错误信息
}
+1 -1
View File
@@ -36,6 +36,7 @@ export interface ChatLabMeta {
sources?: MergeSource[] // 合并来源(可选)
groupId?: string // 群ID(可选,仅群聊)
groupAvatar?: string // 群头像(base64 Data URL,可选)
ownerId?: string // 所有者/导出者的 platformId(可选)
}
/**
@@ -181,4 +182,3 @@ export interface ChatRecordMessage {
timestamp: number
type: number
}