| import { create } from 'zustand'; |
| import { persist } from 'zustand/middleware'; |
| import { UserAccount, LeadData, AdminConfig, WeddingStyle, GeneratedResult, CompositionMode, Resolution, SubjectType, GenerationStatus, Language, PointHistory, FeedbackItem, GenerationLog } from './types'; |
|
|
| |
| const DEFAULT_CONFIG: AdminConfig = { |
| promoText: "🎉 浪漫一生春季摄影节:在线试衣预约享 500元 现金券 + 首席化妆师试妆!", |
| promoEnds: new Date(Date.now() + 86400000).toISOString(), |
| contactPhone: '0592-8888888', |
| showBanner: true, |
| footerAddress: "厦门市思明区环岛路188号", |
| logoUrl: "", |
| shareTitle: "帮我看看哪套婚纱最适合我?", |
| shareDesc: "我在浪漫一生 AI 试衣间发现了超美的婚纱风格,快来帮我打分!", |
| shareImage: "", |
| redPacketMax: 2000, |
| slashDifficulty: 0.98, |
| crmApiUrl: '', |
| geminiApiKey: '', |
| geminiApiUrl: '', |
| pointsShare: 10, |
| pointsInvite: 50, |
| pointsBook: 100, |
| pointsVipCost: 100, |
| |
| qrCodeUrl: "https://api.qrserver.com/v1/create-qr-code/?size=150x150&data=https://ai.xmloveai.com" |
| }; |
|
|
| |
| interface UserState { |
| currentUser: UserAccount | undefined; |
| allUsers: UserAccount[]; |
| leads: LeadData[]; |
| feedback: FeedbackItem[]; |
| guestFavorites: string[]; |
| logs: GenerationLog[]; |
| |
| login: (phone: string, password?: string) => { success: boolean; msg: string }; |
| register: (phone: string, name: string, pass: string) => { success: boolean; msg: string }; |
| logout: () => void; |
| updateUser: (id: string, updates: Partial<UserAccount>) => void; |
| resetPassword: (phone: string, newPass: string) => boolean; |
| addPoints: (amount: number, reason: string) => void; |
| toggleFavorite: (styleId: string) => void; |
| |
| addLead: (lead: LeadData) => void; |
| updateLeadStatus: (id: string, status: LeadData['status']) => void; |
| deleteLead: (id: string) => void; |
| setLeads: (leads: LeadData[]) => void; |
| addFeedback: (item: FeedbackItem) => void; |
| addLog: (log: GenerationLog) => void; |
| } |
|
|
| export const useUserStore = create<UserState>()( |
| persist( |
| (set, get) => ({ |
| currentUser: undefined, |
| allUsers: [], |
| leads: [], |
| feedback: [], |
| guestFavorites: [], |
| logs: [], |
|
|
| login: (phone, password) => { |
| const { allUsers } = get(); |
| const user = allUsers.find(u => u.phone === phone); |
| if (user) { |
| if (user.password && password && user.password !== password) { |
| return { success: false, msg: "密码错误" }; |
| } |
| set({ currentUser: user }); |
| return { success: true, msg: `欢迎回来, ${user.name}` }; |
| } |
| return { success: false, msg: "用户不存在" }; |
| }, |
|
|
| register: (phone, name, pass) => { |
| const { allUsers } = get(); |
| if (allUsers.find(u => u.phone === phone)) { |
| return { success: false, msg: "该手机号已注册" }; |
| } |
| const newUser: UserAccount = { |
| id: 'user_' + Date.now(), |
| name, |
| phone, |
| password: pass, |
| points: 100, |
| isVip: false, |
| joinDate: Date.now(), |
| history: [], |
| role: 'user', |
| redPacketBalance: 1980, |
| slashProgress: {}, |
| favorites: [] |
| }; |
| set({ allUsers: [...allUsers, newUser], currentUser: newUser }); |
| return { success: true, msg: "注册成功" }; |
| }, |
|
|
| logout: () => set({ currentUser: undefined }), |
|
|
| updateUser: (id, updates) => { |
| const { allUsers, currentUser } = get(); |
| const newAllUsers = allUsers.map(u => u.id === id ? { ...u, ...updates } : u); |
| let newCurrentUser = currentUser; |
| if (currentUser && currentUser.id === id) { |
| newCurrentUser = { ...currentUser, ...updates }; |
| } |
| set({ allUsers: newAllUsers, currentUser: newCurrentUser }); |
| }, |
|
|
| resetPassword: (phone, newPass) => { |
| const { allUsers } = get(); |
| const index = allUsers.findIndex(u => u.phone === phone); |
| if (index !== -1) { |
| const updatedUsers = [...allUsers]; |
| updatedUsers[index] = { ...updatedUsers[index], password: newPass }; |
| set({ allUsers: updatedUsers }); |
| return true; |
| } |
| return false; |
| }, |
|
|
| addPoints: (amount, reason) => { |
| const { currentUser, updateUser } = get(); |
| if (!currentUser) return; |
| const newHistory: PointHistory = { |
| id: Date.now().toString(), |
| action: 'share', |
| points: amount, |
| timestamp: Date.now(), |
| desc: reason |
| }; |
| updateUser(currentUser.id, { |
| points: currentUser.points + amount, |
| history: [...currentUser.history, newHistory] |
| }); |
| }, |
|
|
| toggleFavorite: (styleId) => { |
| const { currentUser, updateUser, guestFavorites } = get(); |
| if (currentUser) { |
| const currentFavs = currentUser.favorites || []; |
| const newFavs = currentFavs.includes(styleId) |
| ? currentFavs.filter(id => id !== styleId) |
| : [...currentFavs, styleId]; |
| updateUser(currentUser.id, { favorites: newFavs }); |
| } else { |
| const newFavs = guestFavorites.includes(styleId) |
| ? guestFavorites.filter(id => id !== styleId) |
| : [...guestFavorites, styleId]; |
| set({ guestFavorites: newFavs }); |
| } |
| }, |
|
|
| addLead: (lead) => set(state => ({ leads: [lead, ...state.leads] })), |
| updateLeadStatus: (id, status) => set(state => ({ leads: state.leads.map(l => l.id === id ? { ...l, status } : l) })), |
| deleteLead: (id) => set(state => ({ leads: state.leads.filter(l => l.id !== id) })), |
| setLeads: (leads) => set({ leads }), |
| addFeedback: (item) => set(state => ({ feedback: [item, ...state.feedback] })), |
| addLog: (log) => set(state => ({ logs: [log, ...state.logs].slice(0, 2000) })), |
| }), |
| { |
| name: 'rl_user_storage', |
| } |
| ) |
| ); |
|
|
| |
| interface UIState { |
| language: Language; |
| adminConfig: AdminConfig; |
| modals: { |
| auth: boolean; |
| userCenter: boolean; |
| admin: boolean; |
| about: boolean; |
| feedback: boolean; |
| share: boolean; |
| consult: boolean; |
| analysis: boolean; |
| redPacket: boolean; |
| slash: boolean; |
| }; |
| setLanguage: (lang: Language) => void; |
| setAdminConfig: (config: AdminConfig) => void; |
| toggleModal: (modal: keyof UIState['modals'], isOpen: boolean) => void; |
| closeAllModals: () => void; |
| toastMsg: string | null; |
| showToast: (msg: string) => void; |
| } |
|
|
| export const useUIStore = create<UIState>()( |
| persist( |
| (set) => ({ |
| language: 'zh', |
| adminConfig: DEFAULT_CONFIG, |
| modals: { |
| auth: false, |
| userCenter: false, |
| admin: false, |
| about: false, |
| feedback: false, |
| share: false, |
| consult: false, |
| analysis: false, |
| redPacket: false, |
| slash: false, |
| }, |
| toastMsg: null, |
| setLanguage: (lang) => set({ language: lang }), |
| setAdminConfig: (config) => set({ adminConfig: config }), |
| toggleModal: (modal, isOpen) => set(state => ({ modals: { ...state.modals, [modal]: isOpen } })), |
| closeAllModals: () => set(state => { |
| const closed = Object.keys(state.modals).reduce((acc, key) => ({...acc, [key]: false}), {} as any); |
| return { modals: closed }; |
| }), |
| showToast: (msg) => { |
| set({ toastMsg: msg }); |
| setTimeout(() => set({ toastMsg: null }), 3000); |
| } |
| }), |
| { |
| name: 'rl_ui_storage', |
| } |
| ) |
| ); |
|
|
| |
| interface GenerationState { |
| uploadedImages: string[]; |
| selectedStyle: WeddingStyle | null; |
| customStyleImage: string | null; |
| filter: string; |
| blurAmount: number; |
| compositionMode: CompositionMode; |
| resolution: Resolution; |
| subjectType: SubjectType; |
| customPrompt: string; |
| results: Record<string, GeneratedResult>; |
| status: GenerationStatus; |
| progress: { current: number, total: number, statusMsg?: string } | null; |
| errorMsg: string | null; |
| isScanning: boolean; |
| scanStep: number; |
| recommendedStyleIds: string[]; |
| analysisResult: { faceShape: string; skinTone: string; bestVibe: string; }; |
| setUploadedImages: (images: string[]) => void; |
| setSelectedStyle: (style: WeddingStyle | null) => void; |
| setCustomStyleImage: (img: string | null) => void; |
| setGenerationConfig: (config: Partial<{ filter: string, blurAmount: number, compositionMode: CompositionMode, resolution: Resolution, subjectType: SubjectType, customPrompt: string }>) => void; |
| setStatus: (status: GenerationStatus) => void; |
| setProgress: (progress: GenerationState['progress']) => void; |
| setErrorMsg: (msg: string | null) => void; |
| addResult: (result: GeneratedResult) => void; |
| setResults: (results: Record<string, GeneratedResult>) => void; |
| resetGeneration: () => void; |
| startScanning: () => void; |
| setScanStep: (step: number) => void; |
| setAnalysisData: (recs: string[], result: { faceShape: string; skinTone: string; bestVibe: string; }) => void; |
| stopScanning: () => void; |
| } |
|
|
| export const useGenerationStore = create<GenerationState>()((set) => ({ |
| uploadedImages: [], |
| selectedStyle: null, |
| customStyleImage: null, |
| filter: 'none', |
| blurAmount: 0, |
| compositionMode: 'classic', |
| resolution: 'standard', |
| subjectType: 'female', |
| customPrompt: '', |
| results: {}, |
| status: 'idle', |
| progress: null, |
| errorMsg: null, |
| isScanning: false, |
| scanStep: 0, |
| recommendedStyleIds: [], |
| analysisResult: { faceShape: '', skinTone: '', bestVibe: '' }, |
| setUploadedImages: (images) => set({ uploadedImages: images }), |
| setSelectedStyle: (style) => set({ selectedStyle: style }), |
| setCustomStyleImage: (img) => set({ customStyleImage: img }), |
| setGenerationConfig: (config) => set(state => ({ ...state, ...config })), |
| setStatus: (status) => set({ status }), |
| setProgress: (progress) => set({ progress }), |
| setErrorMsg: (errorMessage) => set({ errorMsg: errorMessage }), |
| addResult: (result) => set(state => ({ results: { ...state.results, [result.styleId]: result } })), |
| setResults: (results) => set({ results }), |
| resetGeneration: () => set({ |
| status: 'idle', |
| results: {}, |
| uploadedImages: [], |
| selectedStyle: null, |
| customStyleImage: null, |
| filter: 'none', |
| blurAmount: 0, |
| customPrompt: '', |
| compositionMode: 'classic', |
| resolution: 'standard', |
| progress: null, |
| recommendedStyleIds: [] |
| }), |
| startScanning: () => set({ isScanning: true, scanStep: 0 }), |
| setScanStep: (step) => set({ scanStep: step }), |
| setAnalysisData: (recs, result) => set({ recommendedStyleIds: recs, analysisResult: result }), |
| stopScanning: () => set({ isScanning: false }) |
| })); |
|
|