更新回复列表
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<aside :style="{ width: sidebarWidth + 'px', minWidth: '96px', maxWidth: '400px' }" class="h-full bg-gradient-to-b from-white to-gray-50 border-r border-gray-200 flex flex-col shadow-sm flex-shrink-0">
|
<aside :style="{ width: sidebarWidth + 'px', minWidth: '96px', maxWidth: '400px' }"
|
||||||
|
class="h-full bg-gradient-to-b from-white to-gray-50 border-r border-gray-200 flex flex-col shadow-sm flex-shrink-0">
|
||||||
<!-- 返回和停止按钮 -->
|
<!-- 返回和停止按钮 -->
|
||||||
<div class="m-3 mb-0 flex gap-2">
|
<div class="m-3 mb-0 flex gap-2">
|
||||||
<button @click="onGoBack"
|
<button @click="onGoBack"
|
||||||
@@ -71,7 +72,8 @@
|
|||||||
|
|
||||||
<!-- 详细统计 -->
|
<!-- 详细统计 -->
|
||||||
<div class="flex-1 min-h-0 border-t border-gray-200 flex flex-col">
|
<div class="flex-1 min-h-0 border-t border-gray-200 flex flex-col">
|
||||||
<div class="px-3 py-2 text-xs font-semibold text-gray-500 uppercase tracking-wider bg-gray-50 flex justify-between items-center">
|
<div
|
||||||
|
class="px-3 py-2 text-xs font-semibold text-gray-500 uppercase tracking-wider bg-gray-50 flex justify-between items-center">
|
||||||
<span>详细统计</span>
|
<span>详细统计</span>
|
||||||
<span class="text-[10px] font-normal text-gray-400">招呼/邀请/回复</span>
|
<span class="text-[10px] font-normal text-gray-400">招呼/邀请/回复</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -81,14 +83,17 @@
|
|||||||
暂无统计数据
|
暂无统计数据
|
||||||
</div>
|
</div>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<div v-for="(groupStats, groupName) in statsByGroup" :key="groupName" class="border-b border-gray-100 last:border-0">
|
<div v-for="(groupStats, groupName) in statsByGroup" :key="groupName"
|
||||||
|
class="border-b border-gray-100 last:border-0">
|
||||||
<div class="px-3 py-1.5 bg-gray-100/50 text-xs font-medium text-gray-600">
|
<div class="px-3 py-1.5 bg-gray-100/50 text-xs font-medium text-gray-600">
|
||||||
{{ groupName }}
|
{{ groupName }}
|
||||||
</div>
|
</div>
|
||||||
<div v-for="stat in groupStats" :key="stat.viewId" class="px-3 py-1.5 flex items-center justify-between hover:bg-white transition-colors text-xs">
|
<div v-for="stat in groupStats" :key="stat.viewId"
|
||||||
|
class="px-3 py-1.5 flex items-center justify-between hover:bg-white transition-colors text-xs">
|
||||||
<div class="flex items-center gap-1">
|
<div class="flex items-center gap-1">
|
||||||
<span class="text-gray-500">视图 {{ stat.viewId }}</span>
|
<span class="text-gray-500">视图 {{ stat.viewId }}</span>
|
||||||
<span v-if="stat.unread > 0" class="w-1.5 h-1.5 rounded-full bg-red-500" :title="`${stat.unread} 条未读消息`"></span>
|
<span v-if="stat.unread > 0" class="w-1.5 h-1.5 rounded-full bg-red-500"
|
||||||
|
:title="`${stat.unread} 条未读消息`"></span>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center gap-3 font-mono text-gray-700">
|
<div class="flex items-center gap-3 font-mono text-gray-700">
|
||||||
<span class="text-blue-600 w-6 text-right">{{ stat.greeting }}</span>
|
<span class="text-blue-600 w-6 text-right">{{ stat.greeting }}</span>
|
||||||
@@ -135,19 +140,23 @@
|
|||||||
<div class="flex items-center justify-between text-xs">
|
<div class="flex items-center justify-between text-xs">
|
||||||
<span class="text-gray-500">已回复</span>
|
<span class="text-gray-500">已回复</span>
|
||||||
<div class="flex items-center gap-1">
|
<div class="flex items-center gap-1">
|
||||||
<button @click="showReplyList" class="text-blue-500 hover:text-blue-600 hover:underline cursor-pointer" title="查看回复列表">
|
<button @click="showReplyList"
|
||||||
|
class="text-blue-500 hover:text-blue-600 hover:underline cursor-pointer" title="查看回复列表">
|
||||||
历史回复
|
历史回复
|
||||||
</button>
|
</button>
|
||||||
<span class="text-emerald-600 font-medium">{{ greetingStats.replyCount || 0 }} 条</span>
|
<span class="text-emerald-600 font-medium">{{ greetingStats.replyCount || 0 }} 条</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- 回复列表弹出框 -->
|
<!-- 回复列表弹出框 -->
|
||||||
<div v-if="replyListVisible" class="absolute left-0 right-0 bottom-full mb-1 bg-white border border-gray-200 rounded-lg shadow-lg z-50 max-h-48 overflow-hidden" :style="{ maxWidth: sidebarWidth + 'px' }">
|
<div v-if="replyListVisible"
|
||||||
|
class="absolute left-0 right-0 bottom-full mb-1 bg-white border border-gray-200 rounded-lg shadow-lg z-50 max-h-48 overflow-hidden"
|
||||||
|
:style="{ maxWidth: sidebarWidth + 'px' }">
|
||||||
<div class="flex items-center justify-between px-3 py-2 border-b border-gray-100 bg-gray-50">
|
<div class="flex items-center justify-between px-3 py-2 border-b border-gray-100 bg-gray-50">
|
||||||
<span class="text-xs font-semibold text-gray-600">历史回复列表</span>
|
<span class="text-xs font-semibold text-gray-600">历史回复列表</span>
|
||||||
<button @click="replyListVisible = false" class="text-gray-400 hover:text-gray-600">
|
<button @click="replyListVisible = false" class="text-gray-400 hover:text-gray-600">
|
||||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||||
|
d="M6 18L18 6M6 6l12 12" />
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -156,8 +165,24 @@
|
|||||||
暂无回复记录
|
暂无回复记录
|
||||||
</div>
|
</div>
|
||||||
<div v-else>
|
<div v-else>
|
||||||
<div v-for="(name, index) in repliedSessions" :key="index" class="px-3 py-1.5 text-xs text-gray-700 hover:bg-gray-50 border-b border-gray-50 last:border-0 truncate" :title="name">
|
<div v-for="(session, index) in repliedSessions" :key="index"
|
||||||
{{ name }}
|
class="px-3 py-1.5 text-xs text-gray-700 hover:bg-gray-50 border-b border-gray-50 last:border-0 flex items-center justify-between">
|
||||||
|
<div class="truncate" :title="session.name">
|
||||||
|
{{ session.name }}
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<!-- <span class="text-gray-400 text-[10px]">
|
||||||
|
ID: {{ session.id }}
|
||||||
|
</span> -->
|
||||||
|
<button @click="copyAnchorId(session.id)"
|
||||||
|
class="text-blue-500 hover:text-blue-600 p-1 rounded hover:bg-blue-50 transition-colors"
|
||||||
|
title="复制 ID">
|
||||||
|
<svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||||
|
d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -169,7 +194,7 @@
|
|||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, computed, watch, onUnmounted } from 'vue'
|
import { ref, computed, watch, onUnmounted } from 'vue'
|
||||||
|
import { ElMessage } from 'element-plus'
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
tabs: { type: Array, required: true },
|
tabs: { type: Array, required: true },
|
||||||
currentTab: { type: String, required: true },
|
currentTab: { type: String, required: true },
|
||||||
@@ -188,7 +213,7 @@ const emit = defineEmits(['tabSwitch', 'goBack', 'stopAll'])
|
|||||||
|
|
||||||
// 回复列表相关
|
// 回复列表相关
|
||||||
const replyListVisible = ref(false)
|
const replyListVisible = ref(false)
|
||||||
/** @type {import('vue').Ref<string[]>} */
|
/** @type {import('vue').Ref<Array<{name: string; id: string}>>} */
|
||||||
const repliedSessions = ref([])
|
const repliedSessions = ref([])
|
||||||
|
|
||||||
// 显示回复列表
|
// 显示回复列表
|
||||||
@@ -206,6 +231,54 @@ const showReplyList = async () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 复制主播 ID
|
||||||
|
const copyAnchorId = (id) => {
|
||||||
|
if (navigator.clipboard && window.isSecureContext) {
|
||||||
|
// 现代浏览器使用 Clipboard API
|
||||||
|
navigator.clipboard.writeText(id).then(() => {
|
||||||
|
showCopySuccess()
|
||||||
|
}).catch(err => {
|
||||||
|
console.error('复制失败:', err)
|
||||||
|
// 回退到传统方法
|
||||||
|
fallbackCopyTextToClipboard(id)
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
// 传统方法
|
||||||
|
fallbackCopyTextToClipboard(id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 回退复制方法(兼容旧浏览器)
|
||||||
|
const fallbackCopyTextToClipboard = (text) => {
|
||||||
|
const textArea = document.createElement('textarea')
|
||||||
|
textArea.value = text
|
||||||
|
textArea.style.position = 'fixed'
|
||||||
|
textArea.style.left = '-999999px'
|
||||||
|
textArea.style.top = '-999999px'
|
||||||
|
document.body.appendChild(textArea)
|
||||||
|
textArea.focus()
|
||||||
|
textArea.select()
|
||||||
|
|
||||||
|
try {
|
||||||
|
const successful = document.execCommand('copy')
|
||||||
|
if (successful) {
|
||||||
|
showCopySuccess()
|
||||||
|
} else {
|
||||||
|
console.error('复制命令执行失败')
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error('回退复制失败:', err)
|
||||||
|
} finally {
|
||||||
|
document.body.removeChild(textArea)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 复制成功提示
|
||||||
|
const showCopySuccess = () => {
|
||||||
|
// 回退到 alert
|
||||||
|
ElMessage.success('复制成功!')
|
||||||
|
}
|
||||||
|
|
||||||
// Event handlers
|
// Event handlers
|
||||||
const onTabSwitch = (id) => emit('tabSwitch', id)
|
const onTabSwitch = (id) => emit('tabSwitch', id)
|
||||||
const onGoBack = () => emit('goBack')
|
const onGoBack = () => emit('goBack')
|
||||||
|
|||||||
2
src/types/electron.d.ts
vendored
2
src/types/electron.d.ts
vendored
@@ -126,7 +126,7 @@ export interface ElectronAPI {
|
|||||||
|
|
||||||
// 打招呼统计
|
// 打招呼统计
|
||||||
getGreetingStats: () => Promise<GreetingStats>
|
getGreetingStats: () => Promise<GreetingStats>
|
||||||
getRepliedSessions: () => Promise<string[]>
|
getRepliedSessions: () => Promise<Array<{ name: string; id: string }>>
|
||||||
|
|
||||||
// 获取打招呼内容
|
// 获取打招呼内容
|
||||||
fetchPrologue: () => Promise<{ success: boolean; data?: string[]; error?: string }>
|
fetchPrologue: () => Promise<{ success: boolean; data?: string[]; error?: string }>
|
||||||
|
|||||||
Reference in New Issue
Block a user