mirror of
https://github.com/hicccc77/WeFlow.git
synced 2026-03-25 15:25:50 +00:00
新的提交
This commit is contained in:
70
src/stores/analyticsStore.ts
Normal file
70
src/stores/analyticsStore.ts
Normal file
@@ -0,0 +1,70 @@
|
||||
import { create } from 'zustand'
|
||||
|
||||
interface ChatStatistics {
|
||||
totalMessages: number
|
||||
textMessages: number
|
||||
imageMessages: number
|
||||
voiceMessages: number
|
||||
videoMessages: number
|
||||
emojiMessages: number
|
||||
otherMessages: number
|
||||
sentMessages: number
|
||||
receivedMessages: number
|
||||
firstMessageTime: number | null
|
||||
lastMessageTime: number | null
|
||||
activeDays: number
|
||||
messageTypeCounts: Record<number, number>
|
||||
}
|
||||
|
||||
interface ContactRanking {
|
||||
username: string
|
||||
displayName: string
|
||||
avatarUrl?: string
|
||||
messageCount: number
|
||||
sentCount: number
|
||||
receivedCount: number
|
||||
lastMessageTime: number | null
|
||||
}
|
||||
|
||||
interface TimeDistribution {
|
||||
hourlyDistribution: Record<number, number>
|
||||
monthlyDistribution: Record<string, number>
|
||||
}
|
||||
|
||||
interface AnalyticsState {
|
||||
// 数据
|
||||
statistics: ChatStatistics | null
|
||||
rankings: ContactRanking[]
|
||||
timeDistribution: TimeDistribution | null
|
||||
|
||||
// 状态
|
||||
isLoaded: boolean
|
||||
lastLoadTime: number | null
|
||||
|
||||
// Actions
|
||||
setStatistics: (data: ChatStatistics) => void
|
||||
setRankings: (data: ContactRanking[]) => void
|
||||
setTimeDistribution: (data: TimeDistribution) => void
|
||||
markLoaded: () => void
|
||||
clearCache: () => void
|
||||
}
|
||||
|
||||
export const useAnalyticsStore = create<AnalyticsState>((set) => ({
|
||||
statistics: null,
|
||||
rankings: [],
|
||||
timeDistribution: null,
|
||||
isLoaded: false,
|
||||
lastLoadTime: null,
|
||||
|
||||
setStatistics: (data) => set({ statistics: data }),
|
||||
setRankings: (data) => set({ rankings: data }),
|
||||
setTimeDistribution: (data) => set({ timeDistribution: data }),
|
||||
markLoaded: () => set({ isLoaded: true, lastLoadTime: Date.now() }),
|
||||
clearCache: () => set({
|
||||
statistics: null,
|
||||
rankings: [],
|
||||
timeDistribution: null,
|
||||
isLoaded: false,
|
||||
lastLoadTime: null
|
||||
}),
|
||||
}))
|
||||
46
src/stores/appStore.ts
Normal file
46
src/stores/appStore.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
import { create } from 'zustand'
|
||||
|
||||
export interface AppState {
|
||||
// 数据库状态
|
||||
isDbConnected: boolean
|
||||
dbPath: string | null
|
||||
myWxid: string | null
|
||||
|
||||
// 加载状态
|
||||
isLoading: boolean
|
||||
loadingText: string
|
||||
|
||||
// 操作
|
||||
setDbConnected: (connected: boolean, path?: string) => void
|
||||
setMyWxid: (wxid: string) => void
|
||||
setLoading: (loading: boolean, text?: string) => void
|
||||
reset: () => void
|
||||
}
|
||||
|
||||
export const useAppStore = create<AppState>((set) => ({
|
||||
isDbConnected: false,
|
||||
dbPath: null,
|
||||
myWxid: null,
|
||||
isLoading: false,
|
||||
loadingText: '',
|
||||
|
||||
setDbConnected: (connected, path) => set({
|
||||
isDbConnected: connected,
|
||||
dbPath: path ?? null
|
||||
}),
|
||||
|
||||
setMyWxid: (wxid) => set({ myWxid: wxid }),
|
||||
|
||||
setLoading: (loading, text) => set({
|
||||
isLoading: loading,
|
||||
loadingText: text ?? ''
|
||||
}),
|
||||
|
||||
reset: () => set({
|
||||
isDbConnected: false,
|
||||
dbPath: null,
|
||||
myWxid: null,
|
||||
isLoading: false,
|
||||
loadingText: ''
|
||||
})
|
||||
}))
|
||||
116
src/stores/chatStore.ts
Normal file
116
src/stores/chatStore.ts
Normal file
@@ -0,0 +1,116 @@
|
||||
import { create } from 'zustand'
|
||||
import type { ChatSession, Message, Contact } from '../types/models'
|
||||
|
||||
export interface ChatState {
|
||||
// 连接状态
|
||||
isConnected: boolean
|
||||
isConnecting: boolean
|
||||
connectionError: string | null
|
||||
|
||||
// 会话列表
|
||||
sessions: ChatSession[]
|
||||
filteredSessions: ChatSession[]
|
||||
currentSessionId: string | null
|
||||
isLoadingSessions: boolean
|
||||
|
||||
// 消息
|
||||
messages: Message[]
|
||||
isLoadingMessages: boolean
|
||||
isLoadingMore: boolean
|
||||
hasMoreMessages: boolean
|
||||
|
||||
// 联系人缓存
|
||||
contacts: Map<string, Contact>
|
||||
|
||||
// 搜索
|
||||
searchKeyword: string
|
||||
|
||||
// 操作
|
||||
setConnected: (connected: boolean) => void
|
||||
setConnecting: (connecting: boolean) => void
|
||||
setConnectionError: (error: string | null) => void
|
||||
setSessions: (sessions: ChatSession[]) => void
|
||||
setFilteredSessions: (sessions: ChatSession[]) => void
|
||||
setCurrentSession: (sessionId: string | null) => void
|
||||
setLoadingSessions: (loading: boolean) => void
|
||||
setMessages: (messages: Message[]) => void
|
||||
appendMessages: (messages: Message[], prepend?: boolean) => void
|
||||
setLoadingMessages: (loading: boolean) => void
|
||||
setLoadingMore: (loading: boolean) => void
|
||||
setHasMoreMessages: (hasMore: boolean) => void
|
||||
setContacts: (contacts: Contact[]) => void
|
||||
addContact: (contact: Contact) => void
|
||||
setSearchKeyword: (keyword: string) => void
|
||||
reset: () => void
|
||||
}
|
||||
|
||||
export const useChatStore = create<ChatState>((set, get) => ({
|
||||
isConnected: false,
|
||||
isConnecting: false,
|
||||
connectionError: null,
|
||||
sessions: [],
|
||||
filteredSessions: [],
|
||||
currentSessionId: null,
|
||||
isLoadingSessions: false,
|
||||
messages: [],
|
||||
isLoadingMessages: false,
|
||||
isLoadingMore: false,
|
||||
hasMoreMessages: true,
|
||||
contacts: new Map(),
|
||||
searchKeyword: '',
|
||||
|
||||
setConnected: (connected) => set({ isConnected: connected }),
|
||||
setConnecting: (connecting) => set({ isConnecting: connecting }),
|
||||
setConnectionError: (error) => set({ connectionError: error }),
|
||||
|
||||
setSessions: (sessions) => set({ sessions, filteredSessions: sessions }),
|
||||
setFilteredSessions: (sessions) => set({ filteredSessions: sessions }),
|
||||
|
||||
setCurrentSession: (sessionId) => set({
|
||||
currentSessionId: sessionId,
|
||||
messages: [],
|
||||
hasMoreMessages: true
|
||||
}),
|
||||
|
||||
setLoadingSessions: (loading) => set({ isLoadingSessions: loading }),
|
||||
|
||||
setMessages: (messages) => set({ messages }),
|
||||
|
||||
appendMessages: (newMessages, prepend = false) => set((state) => ({
|
||||
messages: prepend
|
||||
? [...newMessages, ...state.messages]
|
||||
: [...state.messages, ...newMessages]
|
||||
})),
|
||||
|
||||
setLoadingMessages: (loading) => set({ isLoadingMessages: loading }),
|
||||
setLoadingMore: (loading) => set({ isLoadingMore: loading }),
|
||||
setHasMoreMessages: (hasMore) => set({ hasMoreMessages: hasMore }),
|
||||
|
||||
setContacts: (contacts) => set({
|
||||
contacts: new Map(contacts.map(c => [c.username, c]))
|
||||
}),
|
||||
|
||||
addContact: (contact) => set((state) => {
|
||||
const newContacts = new Map(state.contacts)
|
||||
newContacts.set(contact.username, contact)
|
||||
return { contacts: newContacts }
|
||||
}),
|
||||
|
||||
setSearchKeyword: (keyword) => set({ searchKeyword: keyword }),
|
||||
|
||||
reset: () => set({
|
||||
isConnected: false,
|
||||
isConnecting: false,
|
||||
connectionError: null,
|
||||
sessions: [],
|
||||
filteredSessions: [],
|
||||
currentSessionId: null,
|
||||
isLoadingSessions: false,
|
||||
messages: [],
|
||||
isLoadingMessages: false,
|
||||
isLoadingMore: false,
|
||||
hasMoreMessages: true,
|
||||
contacts: new Map(),
|
||||
searchKeyword: ''
|
||||
})
|
||||
}))
|
||||
173
src/stores/imageStore.ts
Normal file
173
src/stores/imageStore.ts
Normal file
@@ -0,0 +1,173 @@
|
||||
import { create } from 'zustand'
|
||||
|
||||
export interface ImageFileInfo {
|
||||
fileName: string
|
||||
filePath: string
|
||||
fileSize: number
|
||||
isDecrypted: boolean
|
||||
decryptedPath?: string
|
||||
version: number
|
||||
isDecrypting?: boolean
|
||||
}
|
||||
|
||||
export interface ImageDirectory {
|
||||
wxid: string
|
||||
path: string
|
||||
}
|
||||
|
||||
/**
|
||||
* 检测图片质量(原图/缩略图)
|
||||
* 逻辑来自原项目 app_state.dart 的 _detectImageQuality
|
||||
*/
|
||||
function detectImageQuality(img: ImageFileInfo): 'original' | 'thumbnail' {
|
||||
const fileNameLower = img.fileName.toLowerCase()
|
||||
const fileSize = img.fileSize
|
||||
|
||||
// 小于 50KB 是缩略图
|
||||
if (fileSize < 50 * 1024) return 'thumbnail'
|
||||
// 大于 500KB 是原图
|
||||
if (fileSize > 500 * 1024) return 'original'
|
||||
|
||||
// 文件名包含 thumb/small 关键词
|
||||
if (fileNameLower.includes('thumb') || fileNameLower.includes('small')) {
|
||||
return 'thumbnail'
|
||||
}
|
||||
|
||||
// 文件名以 _thumb.dat 或 _small.dat 结尾
|
||||
if (fileNameLower.endsWith('_thumb.dat') || fileNameLower.endsWith('_small.dat')) {
|
||||
return 'thumbnail'
|
||||
}
|
||||
|
||||
// 路径层级判断(通过 filePath 中的分隔符数量)
|
||||
const pathParts = img.filePath.split(/[/\\]/)
|
||||
// 找到账号目录后的相对路径层级
|
||||
// 如果层级太深,可能是缩略图
|
||||
if (pathParts.length > 10) return 'thumbnail'
|
||||
|
||||
return 'original'
|
||||
}
|
||||
|
||||
interface ImageState {
|
||||
// 图片列表
|
||||
images: ImageFileInfo[]
|
||||
// 目录列表
|
||||
directories: ImageDirectory[]
|
||||
// 当前选中的目录
|
||||
selectedDir: ImageDirectory | null
|
||||
// 扫描状态
|
||||
isScanning: boolean
|
||||
scanCompleted: boolean
|
||||
// 错误信息
|
||||
error: string | null
|
||||
|
||||
// 统计
|
||||
originalCount: number
|
||||
thumbnailCount: number
|
||||
decryptedCount: number
|
||||
|
||||
// 操作
|
||||
setDirectories: (dirs: ImageDirectory[]) => void
|
||||
setSelectedDir: (dir: ImageDirectory | null) => void
|
||||
setScanning: (scanning: boolean) => void
|
||||
setScanCompleted: (completed: boolean) => void
|
||||
setError: (error: string | null) => void
|
||||
addImages: (newImages: ImageFileInfo[]) => void
|
||||
clearImages: () => void
|
||||
updateImage: (index: number, updates: Partial<ImageFileInfo>) => void
|
||||
updateStats: () => void
|
||||
reset: () => void
|
||||
}
|
||||
|
||||
export const useImageStore = create<ImageState>((set, get) => ({
|
||||
images: [],
|
||||
directories: [],
|
||||
selectedDir: null,
|
||||
isScanning: false,
|
||||
scanCompleted: false,
|
||||
error: null,
|
||||
originalCount: 0,
|
||||
thumbnailCount: 0,
|
||||
decryptedCount: 0,
|
||||
|
||||
setDirectories: (dirs) => set({ directories: dirs }),
|
||||
|
||||
setSelectedDir: (dir) => set({ selectedDir: dir }),
|
||||
|
||||
setScanning: (scanning) => set({ isScanning: scanning }),
|
||||
|
||||
setScanCompleted: (completed) => set({ scanCompleted: completed }),
|
||||
|
||||
setError: (error) => set({ error }),
|
||||
|
||||
addImages: (newImages) => {
|
||||
set((state) => {
|
||||
const updated = [...state.images, ...newImages]
|
||||
// 计算统计
|
||||
let original = 0
|
||||
let thumbnail = 0
|
||||
let decrypted = 0
|
||||
for (const img of updated) {
|
||||
if (detectImageQuality(img) === 'original') {
|
||||
original++
|
||||
} else {
|
||||
thumbnail++
|
||||
}
|
||||
if (img.isDecrypted) decrypted++
|
||||
}
|
||||
return {
|
||||
images: updated,
|
||||
originalCount: original,
|
||||
thumbnailCount: thumbnail,
|
||||
decryptedCount: decrypted
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
clearImages: () => set({
|
||||
images: [],
|
||||
originalCount: 0,
|
||||
thumbnailCount: 0,
|
||||
decryptedCount: 0,
|
||||
scanCompleted: false
|
||||
}),
|
||||
|
||||
updateImage: (index, updates) => {
|
||||
set((state) => {
|
||||
const images = [...state.images]
|
||||
if (index >= 0 && index < images.length) {
|
||||
images[index] = { ...images[index], ...updates }
|
||||
}
|
||||
// 重新计算已解密数量
|
||||
const decryptedCount = images.filter(img => img.isDecrypted).length
|
||||
return { images, decryptedCount }
|
||||
})
|
||||
},
|
||||
|
||||
updateStats: () => {
|
||||
const { images } = get()
|
||||
let original = 0
|
||||
let thumbnail = 0
|
||||
let decrypted = 0
|
||||
for (const img of images) {
|
||||
if (detectImageQuality(img) === 'original') {
|
||||
original++
|
||||
} else {
|
||||
thumbnail++
|
||||
}
|
||||
if (img.isDecrypted) decrypted++
|
||||
}
|
||||
set({ originalCount: original, thumbnailCount: thumbnail, decryptedCount: decrypted })
|
||||
},
|
||||
|
||||
reset: () => set({
|
||||
images: [],
|
||||
directories: [],
|
||||
selectedDir: null,
|
||||
isScanning: false,
|
||||
scanCompleted: false,
|
||||
error: null,
|
||||
originalCount: 0,
|
||||
thumbnailCount: 0,
|
||||
decryptedCount: 0
|
||||
})
|
||||
}))
|
||||
79
src/stores/themeStore.ts
Normal file
79
src/stores/themeStore.ts
Normal file
@@ -0,0 +1,79 @@
|
||||
import { create } from 'zustand'
|
||||
import { persist } from 'zustand/middleware'
|
||||
|
||||
export type ThemeId = 'cloud-dancer' | 'corundum-blue' | 'kiwi-green' | 'spicy-red' | 'teal-water'
|
||||
export type ThemeMode = 'light' | 'dark'
|
||||
|
||||
export interface ThemeInfo {
|
||||
id: ThemeId
|
||||
name: string
|
||||
description: string
|
||||
primaryColor: string
|
||||
bgColor: string
|
||||
}
|
||||
|
||||
export const themes: ThemeInfo[] = [
|
||||
{
|
||||
id: 'cloud-dancer',
|
||||
name: '云上舞白',
|
||||
description: 'Pantone 2026 年度色',
|
||||
primaryColor: '#8B7355',
|
||||
bgColor: '#F0EEE9'
|
||||
},
|
||||
{
|
||||
id: 'corundum-blue',
|
||||
name: '刚玉蓝',
|
||||
description: 'RAL 220 40 10',
|
||||
primaryColor: '#4A6670',
|
||||
bgColor: '#E8EEF0'
|
||||
},
|
||||
{
|
||||
id: 'kiwi-green',
|
||||
name: '冰猕猴桃汁绿',
|
||||
description: 'RAL 120 90 20',
|
||||
primaryColor: '#7A9A5C',
|
||||
bgColor: '#E8F0E4'
|
||||
},
|
||||
{
|
||||
id: 'spicy-red',
|
||||
name: '辛辣红',
|
||||
description: 'RAL 030 40 40',
|
||||
primaryColor: '#8B4049',
|
||||
bgColor: '#F0E8E8'
|
||||
},
|
||||
{
|
||||
id: 'teal-water',
|
||||
name: '明水鸭色',
|
||||
description: 'RAL 180 80 10',
|
||||
primaryColor: '#5A8A8A',
|
||||
bgColor: '#E4F0F0'
|
||||
}
|
||||
]
|
||||
|
||||
interface ThemeState {
|
||||
currentTheme: ThemeId
|
||||
themeMode: ThemeMode
|
||||
setTheme: (theme: ThemeId) => void
|
||||
setThemeMode: (mode: ThemeMode) => void
|
||||
toggleThemeMode: () => void
|
||||
}
|
||||
|
||||
export const useThemeStore = create<ThemeState>()(
|
||||
persist(
|
||||
(set, get) => ({
|
||||
currentTheme: 'cloud-dancer',
|
||||
themeMode: 'light',
|
||||
setTheme: (theme) => set({ currentTheme: theme }),
|
||||
setThemeMode: (mode) => set({ themeMode: mode }),
|
||||
toggleThemeMode: () => set({ themeMode: get().themeMode === 'light' ? 'dark' : 'light' })
|
||||
}),
|
||||
{
|
||||
name: 'echotrace-theme'
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
// 获取当前主题信息
|
||||
export const getThemeInfo = (themeId: ThemeId): ThemeInfo => {
|
||||
return themes.find(t => t.id === themeId) || themes[0]
|
||||
}
|
||||
Reference in New Issue
Block a user