重命名前端项目目录:web-flowith -> web

- 将前端项目目录从 web-flowith 重命名为 web,使目录结构更简洁
- 保持所有前端代码和配置文件不变
- 统一项目目录命名规范
This commit is contained in:
2025-07-24 22:20:19 +08:00
parent ca42a7d9a4
commit bbe8fcd776
57 changed files with 0 additions and 0 deletions
+65
View File
@@ -0,0 +1,65 @@
import { defineStore } from 'pinia'
import { ref } from 'vue'
import type { ThemeConfig } from '@/types'
export const useAppStore = defineStore('app', () => {
// 应用状态
const loading = ref(false)
const mobileMenuVisible = ref(false)
const theme = ref<ThemeConfig>({
primaryColor: '#4A90E2',
secondaryColor: '#F5A623',
backgroundColor: '#F7F8FA',
textColor: '#333333',
borderRadius: '8px'
})
// 设备信息
const isMobile = ref(false)
const screenWidth = ref(window.innerWidth)
// 方法
const setLoading = (value: boolean) => {
loading.value = value
}
const toggleMobileMenu = () => {
mobileMenuVisible.value = !mobileMenuVisible.value
}
const closeMobileMenu = () => {
mobileMenuVisible.value = false
}
const updateScreenWidth = () => {
screenWidth.value = window.innerWidth
isMobile.value = window.innerWidth < 768
}
const setTheme = (newTheme: Partial<ThemeConfig>) => {
theme.value = { ...theme.value, ...newTheme }
}
// 初始化
const init = () => {
updateScreenWidth()
window.addEventListener('resize', updateScreenWidth)
}
return {
// 状态
loading,
mobileMenuVisible,
theme,
isMobile,
screenWidth,
// 方法
setLoading,
toggleMobileMenu,
closeMobileMenu,
updateScreenWidth,
setTheme,
init
}
})
+353
View File
@@ -0,0 +1,353 @@
import { defineStore } from 'pinia'
import { ref, watch } from 'vue'
import type { ChatMessage, ChatSession } from '@/types'
import webSocketService, { type WebSocketMessage, type ConnectionStatus } from '@/services/websocket'
import { useUserStore } from './user'
import { chatApi } from '@/services/chat'
export const useChatStore = defineStore('chat', () => {
const userStore = useUserStore()
// 聊天状态
const currentSession = ref<ChatSession | null>(null)
const messages = ref<ChatMessage[]>([])
const sessions = ref<ChatSession[]>([])
const isTyping = ref(false)
const isConnected = ref(false)
const connectionStatus = ref<ConnectionStatus>('DISCONNECTED')
const wsConnected = ref(false)
// 方法
const addMessage = (message: Omit<ChatMessage, 'id' | 'timestamp'>) => {
const newMessage: ChatMessage = {
...message,
id: Date.now().toString(),
timestamp: new Date().toISOString(),
status: message.type === 'user' ? 'sending' : 'sent'
}
messages.value.push(newMessage)
return newMessage
}
// 更新消息状态
const updateMessageStatus = (messageId: string, status: ChatMessage['status'], error?: string) => {
const message = messages.value.find(m => m.id === messageId)
if (message) {
message.status = status
if (error) {
message.error = error
}
}
}
// 发送消息:WebSocket推送+数据库保存
const sendMessage = async (content: string) => {
if (!wsConnected.value) {
console.error('WebSocket未连接,无法发送消息')
addMessage({
content: '连接已断开,请刷新页面重试。',
type: 'ai',
sessionId: currentSession.value?.id
})
return
}
// 添加用户消息
const userMessage = addMessage({
content,
type: 'user',
sessionId: currentSession.value?.id
})
try {
// WebSocket推送
webSocketService.sendChatMessage(content, currentSession.value?.id)
// 更新消息状态为已发送
updateMessageStatus(userMessage.id, 'sent')
// 数据库保存
if (currentSession.value?.id && userStore.user?.id) {
await chatApi.createMessage({
conversationId: currentSession.value.id,
userId: userStore.user.id,
content,
contentType: 'TEXT',
senderType: 'USER',
senderId: userStore.user.id
})
// 更新消息状态为已送达
updateMessageStatus(userMessage.id, 'delivered')
}
} catch (error) {
console.error('消息发送或保存失败:', error)
// 更新消息状态为失败
updateMessageStatus(userMessage.id, 'failed', '发送失败')
addMessage({
content: '抱歉,消息发送失败,请稍后重试。',
type: 'ai',
sessionId: currentSession.value?.id
})
}
return userMessage
}
// 创建会话:同步后端
const createSession = async (title?: string) => {
let newSession: ChatSession
if (userStore.user?.id) {
newSession = await chatApi.createSession(userStore.user.id, title || `对话${sessions.value.length + 1}`)
} else {
newSession = {
id: Date.now().toString(),
title: title || `对话${sessions.value.length + 1}`,
createTime: new Date().toISOString(),
updateTime: new Date().toISOString(),
messageCount: 0
}
}
sessions.value.unshift(newSession)
currentSession.value = newSession
messages.value = []
// 如果WebSocket已连接,设置新的会话ID
if (wsConnected.value) {
webSocketService.setConversationId(newSession.id)
}
return newSession
}
// 切换会话:加载消息
const switchSession = async (sessionId: string) => {
const session = sessions.value.find(s => s.id === sessionId)
if (session) {
currentSession.value = session
await loadSessionMessages(sessionId)
// 如果WebSocket已连接,更新会话ID
if (wsConnected.value) {
webSocketService.setConversationId(sessionId)
}
}
}
// 加载会话消息:从后端获取
const loadSessionMessages = async (sessionId: string) => {
try {
const msgs = await chatApi.getAllSessionMessages(sessionId)
messages.value = msgs
} catch (error) {
console.error('Failed to load session messages:', error)
messages.value = []
}
}
// 删除会话:同步后端
const deleteSession = async (sessionId: string) => {
try {
await chatApi.deleteSession(sessionId)
const index = sessions.value.findIndex(s => s.id === sessionId)
if (index > -1) {
sessions.value.splice(index, 1)
if (currentSession.value?.id === sessionId) {
currentSession.value = sessions.value[0] || null
if (currentSession.value) {
await loadSessionMessages(currentSession.value.id)
} else {
messages.value = []
}
}
}
} catch (error) {
console.error('删除会话失败:', error)
}
}
const clearMessages = () => {
messages.value = []
}
const searchMessages = (keyword: string) => {
return messages.value.filter(message =>
message.content.toLowerCase().includes(keyword.toLowerCase())
)
}
// 分割AI回复为多条消息
const splitAiReply = (content: string): string[] => {
// 先按 \n\n 分割,再按 \n 分割
const segments = content.split(/\n\n|\n/).filter(segment => segment.trim().length > 0)
return segments
}
// 添加AI回复消息(支持分段显示)
const addAiReplyMessages = (content: string, delay: number = 1000) => {
const segments = splitAiReply(content)
segments.forEach((segment, index) => {
setTimeout(() => {
addMessage({
content: segment.trim(),
type: 'ai',
sessionId: currentSession.value?.id
})
// 最后一条消息后停止输入状态
if (index === segments.length - 1) {
isTyping.value = false
}
}, index * delay)
})
}
// WebSocket消息处理
const handleWebSocketMessage = (wsMessage: WebSocketMessage) => {
console.log('处理WebSocket消息:', wsMessage)
switch (wsMessage.type) {
case 'TEXT':
if (wsMessage.senderType === 'AI') {
// AI回复消息 - 支持分段显示
addAiReplyMessages(wsMessage.content)
}
break
case 'AI_THINKING':
// AI正在思考
isTyping.value = true
break
case 'CONNECTION':
// 连接状态消息
console.log('WebSocket连接状态:', wsMessage.content)
break
case 'ERROR':
// 错误消息
addMessage({
content: wsMessage.content,
type: 'ai',
sessionId: currentSession.value?.id
})
isTyping.value = false
break
case 'SYSTEM':
// 系统消息
addMessage({
content: wsMessage.content,
type: 'ai',
sessionId: currentSession.value?.id
})
break
default:
console.log('未处理的消息类型:', wsMessage.type)
}
}
// WebSocket连接管理
const connectWebSocket = async () => {
try {
// 优先使用userInfo中的用户ID,如果没有则使用user中的ID
const userId = userStore.userInfo?.id || userStore.user?.id || undefined
await webSocketService.connect(userId, {
onMessage: handleWebSocketMessage,
onConnect: () => {
console.log('WebSocket连接成功')
wsConnected.value = true
isConnected.value = true
// 设置会话ID
if (currentSession.value?.id) {
webSocketService.setConversationId(currentSession.value.id)
}
},
onDisconnect: () => {
console.log('WebSocket连接断开')
wsConnected.value = false
isConnected.value = false
isTyping.value = false
},
onError: (error) => {
console.error('WebSocket错误:', error)
wsConnected.value = false
isConnected.value = false
isTyping.value = false
// 显示用户友好的错误信息
if (error.userMessage) {
addMessage({
content: error.userMessage,
type: 'ai',
sessionId: currentSession.value?.id
})
}
},
onStatusChange: (status) => {
connectionStatus.value = status
isConnected.value = status === 'CONNECTED'
}
})
} catch (error) {
console.error('WebSocket连接失败:', error)
wsConnected.value = false
isConnected.value = false
}
}
const disconnectWebSocket = () => {
webSocketService.disconnect()
wsConnected.value = false
isConnected.value = false
isTyping.value = false
}
// 初始化
const initChat = async () => {
// 如果没有会话,创建一个默认会话
if (sessions.value.length === 0) {
await createSession('与开开的对话')
}
// 连接WebSocket
await connectWebSocket()
}
// 监听会话变化,更新WebSocket会话ID
watch(currentSession, (newSession) => {
if (newSession?.id && wsConnected.value) {
webSocketService.setConversationId(newSession.id)
}
})
return {
// 状态
currentSession,
messages,
sessions,
isTyping,
isConnected,
connectionStatus,
wsConnected,
// 方法
addMessage,
sendMessage,
createSession,
switchSession,
loadSessionMessages,
deleteSession,
clearMessages,
searchMessages,
initChat,
connectWebSocket,
disconnectWebSocket,
handleWebSocketMessage
}
})
+157
View File
@@ -0,0 +1,157 @@
import { defineStore } from 'pinia'
import { ref } from 'vue'
import type { DiaryEntry } from '@/types'
export const useDiaryStore = defineStore('diary', () => {
// 日记状态
const entries = ref<DiaryEntry[]>([])
const currentEntry = ref<DiaryEntry | null>(null)
const isLoading = ref(false)
// 方法
const addEntry = async (content: string, mood?: string, tags?: string[]) => {
isLoading.value = true
try {
const newEntry: DiaryEntry = {
id: Date.now().toString(),
content,
mood,
tags: tags || [],
createTime: new Date().toISOString(),
updateTime: new Date().toISOString()
}
// TODO: 调用API保存日记
// const response = await diaryApi.createEntry(newEntry)
// 模拟AI回复
setTimeout(() => {
newEntry.aiReply = generateAIReply(content, mood)
entries.value.unshift(newEntry)
isLoading.value = false
}, 1000)
return newEntry
} catch (error) {
console.error('Failed to add diary entry:', error)
isLoading.value = false
throw error
}
}
const updateEntry = async (id: string, updates: Partial<DiaryEntry>) => {
const index = entries.value.findIndex(entry => entry.id === id)
if (index > -1) {
entries.value[index] = {
...entries.value[index],
...updates,
updateTime: new Date().toISOString()
}
// TODO: 调用API更新日记
// await diaryApi.updateEntry(id, updates)
}
}
const deleteEntry = async (id: string) => {
const index = entries.value.findIndex(entry => entry.id === id)
if (index > -1) {
entries.value.splice(index, 1)
// TODO: 调用API删除日记
// await diaryApi.deleteEntry(id)
}
}
const getEntry = (id: string) => {
return entries.value.find(entry => entry.id === id)
}
const searchEntries = (keyword: string) => {
return entries.value.filter(entry =>
entry.content.toLowerCase().includes(keyword.toLowerCase()) ||
entry.tags?.some(tag => tag.toLowerCase().includes(keyword.toLowerCase()))
)
}
const getEntriesByMood = (mood: string) => {
return entries.value.filter(entry => entry.mood === mood)
}
const getEntriesByDateRange = (startDate: string, endDate: string) => {
return entries.value.filter(entry => {
const entryDate = new Date(entry.createTime).toISOString().split('T')[0]
return entryDate >= startDate && entryDate <= endDate
})
}
const loadEntries = async () => {
isLoading.value = true
try {
// TODO: 从API加载日记列表
// const response = await diaryApi.getEntries()
// entries.value = response.data
// 临时模拟数据
entries.value = [
{
id: '1',
content: '今天天气很好,心情也不错。和朋友一起去公园散步,看到了很多美丽的花朵。',
mood: 'happy',
tags: ['散步', '朋友', '公园'],
createTime: new Date(Date.now() - 86400000).toISOString(),
updateTime: new Date(Date.now() - 86400000).toISOString(),
aiReply: '听起来你度过了美好的一天!和朋友一起在大自然中放松是很棒的体验。这样的时光能让我们感受到生活的美好。'
}
]
isLoading.value = false
} catch (error) {
console.error('Failed to load diary entries:', error)
isLoading.value = false
}
}
// 生成AI回复的辅助函数
const generateAIReply = (content: string, mood?: string) => {
const replies = {
happy: [
'很高兴看到你心情愉快!继续保持这份美好的心情吧。',
'你的快乐感染了我!希望这份喜悦能持续下去。',
'看到你开心,我也很开心。愿你每天都有这样的好心情!'
],
sad: [
'我能感受到你的难过。记住,这只是暂时的,一切都会好起来的。',
'每个人都会有低落的时候,这很正常。我会陪伴你度过这段时光。',
'虽然现在感到难过,但请相信明天会更好。我一直在这里支持你。'
],
neutral: [
'感谢你分享今天的经历。每一天都是独特的,值得被记录。',
'生活就是这样平凡而珍贵。感谢你让我了解你的日常。',
'平静的日子也有它的美好。希望你能在平凡中发现小确幸。'
]
}
const moodReplies = replies[mood as keyof typeof replies] || replies.neutral
return moodReplies[Math.floor(Math.random() * moodReplies.length)]
}
return {
// 状态
entries,
currentEntry,
isLoading,
// 方法
addEntry,
updateEntry,
deleteEntry,
getEntry,
searchEntries,
getEntriesByMood,
getEntriesByDateRange,
loadEntries
}
})
+11
View File
@@ -0,0 +1,11 @@
import { createPinia } from 'pinia'
const pinia = createPinia()
export default pinia
// 导出所有store
export { useUserStore } from './user'
export { useChatStore } from './chat'
export { useDiaryStore } from './diary'
export { useAppStore } from './app'
+157
View File
@@ -0,0 +1,157 @@
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
import { authService, authUtils } from '@/services/auth'
import type { User } from '@/types'
import type { UserInfo, LoginRequest } from '@/types/auth'
export const useUserStore = defineStore('user', () => {
// 用户状态
const user = ref<User | null>(null)
const userInfo = ref<UserInfo | null>(null)
const token = ref<string>('')
const isLoading = ref(false)
const isLoggedIn = computed(() => !!token.value && (!!user.value || !!userInfo.value))
// 方法
const setUser = (userData: User) => {
user.value = userData
}
const setToken = (tokenValue: string) => {
token.value = tokenValue
// 存储到localStorage
if (tokenValue) {
localStorage.setItem('token', tokenValue)
} else {
localStorage.removeItem('token')
}
}
const setUserInfo = (userInfoData: UserInfo | null) => {
userInfo.value = userInfoData
// 存储到localStorage
if (userInfoData) {
localStorage.setItem('userInfo', JSON.stringify(userInfoData))
} else {
localStorage.removeItem('userInfo')
}
}
// 新的登录方法,支持认证服务
const loginWithAuth = async (loginData: LoginRequest) => {
isLoading.value = true
try {
const data = await authService.login(loginData)
setToken(data.accessToken)
setUserInfo(data.userInfo)
return data
} catch (error: any) {
throw error
} finally {
isLoading.value = false
}
}
const login = async (credentials: { username: string; password: string }) => {
try {
// TODO: 调用登录API
// const response = await authApi.login(credentials)
// setToken(response.data.token)
// setUser(response.data.user)
// 临时模拟登录
setToken('mock-token')
setUser({
id: '1',
username: credentials.username,
email: 'user@example.com',
nickname: '用户',
createTime: new Date().toISOString(),
updateTime: new Date().toISOString()
})
return true
} catch (error) {
console.error('Login failed:', error)
return false
}
}
const logout = async () => {
try {
await authService.logout()
} catch (error) {
console.error('Logout error:', error)
} finally {
// 清除状态和本地存储
user.value = null
userInfo.value = null
setToken('')
authUtils.clearAuth()
}
}
const updateProfile = (profileData: Partial<User>) => {
if (user.value) {
user.value = { ...user.value, ...profileData }
// TODO: 调用更新API
}
}
// 初始化用户状态
const initUser = () => {
const savedToken = authUtils.getToken()
const savedUserInfo = authUtils.getUserInfo()
console.log('初始化用户状态:', { savedToken: !!savedToken, savedUserInfo })
if (savedToken) {
setToken(savedToken)
}
if (savedUserInfo) {
setUserInfo(savedUserInfo)
}
console.log('用户状态初始化完成:', {
token: !!token.value,
userInfo: userInfo.value,
isLoggedIn: isLoggedIn.value
})
}
// 刷新用户信息
const refreshUserInfo = async () => {
if (!token.value) return
try {
const response = await authService.getUserInfo()
if (response.success) {
userInfo.value = response.data
authUtils.setUserInfo(response.data)
}
} catch (error) {
console.error('Refresh user info error:', error)
}
}
return {
// 状态
user,
userInfo,
token,
isLoading,
isLoggedIn,
// 方法
setUser,
setToken,
setUserInfo,
login,
loginWithAuth,
logout,
updateProfile,
initUser,
refreshUserInfo
}
})