mirror of
https://github.com/hicccc77/WeFlow.git
synced 2026-03-24 23:06:51 +00:00
导出群成员第一版
This commit is contained in:
@@ -656,6 +656,32 @@
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
|
||||
.export-btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 6px 10px;
|
||||
border: none;
|
||||
background: var(--bg-tertiary);
|
||||
border-radius: 8px;
|
||||
color: var(--text-secondary);
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
-webkit-app-region: no-drag;
|
||||
font-size: 12px;
|
||||
flex-shrink: 0;
|
||||
|
||||
&:hover {
|
||||
background: var(--bg-hover);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
opacity: 0.4;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.content-body {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useState, useEffect, useRef, useCallback } from 'react'
|
||||
import { Users, BarChart3, Clock, Image, Loader2, RefreshCw, User, Medal, Search, X, ChevronLeft, Copy, Check } from 'lucide-react'
|
||||
import { Users, BarChart3, Clock, Image, Loader2, RefreshCw, User, Medal, Search, X, ChevronLeft, Copy, Check, Download } from 'lucide-react'
|
||||
import { Avatar } from '../components/Avatar'
|
||||
import ReactECharts from 'echarts-for-react'
|
||||
import DateRangePicker from '../components/DateRangePicker'
|
||||
@@ -39,6 +39,7 @@ function GroupAnalyticsPage() {
|
||||
const [activeHours, setActiveHours] = useState<Record<number, number>>({})
|
||||
const [mediaStats, setMediaStats] = useState<{ typeCounts: Array<{ type: number; name: string; count: number }>; total: number } | null>(null)
|
||||
const [functionLoading, setFunctionLoading] = useState(false)
|
||||
const [isExportingMembers, setIsExportingMembers] = useState(false)
|
||||
|
||||
// 成员详情弹框
|
||||
const [selectedMember, setSelectedMember] = useState<GroupMember | null>(null)
|
||||
@@ -181,6 +182,10 @@ function GroupAnalyticsPage() {
|
||||
return num.toLocaleString()
|
||||
}
|
||||
|
||||
const sanitizeFileName = (name: string) => {
|
||||
return name.replace(/[<>:"/\\|?*]+/g, '_').trim()
|
||||
}
|
||||
|
||||
const getHourlyOption = () => {
|
||||
const hours = Array.from({ length: 24 }, (_, i) => i)
|
||||
const data = hours.map(h => activeHours[h] || 0)
|
||||
@@ -252,6 +257,35 @@ function GroupAnalyticsPage() {
|
||||
setCopiedField(null)
|
||||
}
|
||||
|
||||
const handleExportMembers = async () => {
|
||||
if (!selectedGroup || isExportingMembers) return
|
||||
setIsExportingMembers(true)
|
||||
try {
|
||||
const downloadsPath = await window.electronAPI.app.getDownloadsPath()
|
||||
const baseName = sanitizeFileName(`${selectedGroup.displayName || selectedGroup.username}_群成员列表`)
|
||||
const separator = downloadsPath && downloadsPath.includes('\\') ? '\\' : '/'
|
||||
const defaultPath = downloadsPath ? `${downloadsPath}${separator}${baseName}.csv` : `${baseName}.csv`
|
||||
const saveResult = await window.electronAPI.dialog.saveFile({
|
||||
title: '导出群成员列表',
|
||||
defaultPath,
|
||||
filters: [{ name: 'CSV', extensions: ['csv'] }]
|
||||
})
|
||||
if (!saveResult || saveResult.canceled || !saveResult.filePath) return
|
||||
|
||||
const result = await window.electronAPI.groupAnalytics.exportGroupMembers(selectedGroup.username, saveResult.filePath)
|
||||
if (result.success) {
|
||||
alert(`导出成功,共 ${result.count ?? members.length} 人`)
|
||||
} else {
|
||||
alert(`导出失败:${result.error || '未知错误'}`)
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('导出群成员失败:', e)
|
||||
alert(`导出失败:${String(e)}`)
|
||||
} finally {
|
||||
setIsExportingMembers(false)
|
||||
}
|
||||
}
|
||||
|
||||
const handleCopy = async (text: string, field: string) => {
|
||||
try {
|
||||
await navigator.clipboard.writeText(text)
|
||||
@@ -423,6 +457,12 @@ function GroupAnalyticsPage() {
|
||||
onRangeComplete={handleDateRangeComplete}
|
||||
/>
|
||||
)}
|
||||
{selectedFunction === 'members' && (
|
||||
<button className="export-btn" onClick={handleExportMembers} disabled={functionLoading || isExportingMembers}>
|
||||
{isExportingMembers ? <Loader2 size={16} className="spin" /> : <Download size={16} />}
|
||||
<span>导出成员</span>
|
||||
</button>
|
||||
)}
|
||||
<button className="refresh-btn" onClick={handleRefresh} disabled={functionLoading}>
|
||||
<RefreshCw size={16} className={functionLoading ? 'spin' : ''} />
|
||||
</button>
|
||||
|
||||
5
src/types/electron.d.ts
vendored
5
src/types/electron.d.ts
vendored
@@ -232,6 +232,11 @@ export interface ElectronAPI {
|
||||
}
|
||||
error?: string
|
||||
}>
|
||||
exportGroupMembers: (chatroomId: string, outputPath: string) => Promise<{
|
||||
success: boolean
|
||||
count?: number
|
||||
error?: string
|
||||
}>
|
||||
}
|
||||
annualReport: {
|
||||
getAvailableYears: () => Promise<{
|
||||
|
||||
Reference in New Issue
Block a user