Files
Mycontent/lib/store.ts

670 lines
20 KiB
TypeScript
Raw Normal View History

2025-12-29 14:01:37 +08:00
"use client"
import { create } from "zustand"
import { persist } from "zustand/middleware"
import { getFullBookPrice } from "./book-data"
export interface User {
id: string
phone: string
nickname: string
isAdmin: boolean
purchasedSections: string[]
hasFullBook: boolean
referralCode: string
referredBy?: string
earnings: number
pendingEarnings: number
withdrawnEarnings: number
referralCount: number
createdAt: string
}
export interface Withdrawal {
id: string
userId: string
amount: number
method: "wechat" | "alipay"
account: string
name: string
status: "pending" | "completed" | "rejected"
createdAt: string
completedAt?: string
}
export interface Purchase {
id: string
userId: string
userPhone?: string
userNickname?: string
type: "section" | "fullbook"
sectionId?: string
sectionTitle?: string
amount: number
paymentMethod?: string
referralCode?: string
referrerEarnings?: number
status: "pending" | "completed" | "refunded"
createdAt: string
}
export interface LiveQRCodeConfig {
id: string
name: string
urls: string[] // 多个URL随机跳转
imageUrl: string
redirectType: "random" | "sequential" | "weighted"
weights?: number[]
clickCount: number
enabled: boolean
}
export interface QRCodeConfig {
id: string
name: string
url: string
imageUrl: string
weight: number
enabled: boolean
}
export interface PaymentAccountConfig {
wechat: {
enabled: boolean
qrCode: string
account: string
websiteAppId?: string
websiteAppSecret?: string
serviceAppId?: string
serviceAppSecret?: string
mpVerifyCode?: string
merchantId?: string
apiKey?: string
}
alipay: {
enabled: boolean
qrCode: string
account: string
partnerId?: string // PID
securityKey?: string
mobilePayEnabled?: boolean
paymentInterface?: string
}
usdt: {
enabled: boolean
network: "TRC20" | "ERC20" | "BEP20"
address: string
exchangeRate: number
}
paypal: {
enabled: boolean
email: string
exchangeRate: number
}
}
export interface FeishuSyncConfig {
enabled: boolean
docUrl: string
lastSyncAt?: string
autoSync: boolean
syncInterval: number // 分钟
}
export interface Settings {
distributorShare: number
authorShare: number
paymentMethods: PaymentAccountConfig
sectionPrice: number
baseBookPrice: number
pricePerSection: number
qrCodes: QRCodeConfig[]
liveQRCodes: LiveQRCodeConfig[]
feishuSync: FeishuSyncConfig
authorInfo: {
name: string
description: string
liveTime: string
platform: string
}
}
interface StoreState {
user: User | null
isLoggedIn: boolean
purchases: Purchase[]
withdrawals: Withdrawal[]
settings: Settings
login: (phone: string, code: string) => Promise<boolean>
logout: () => void
register: (phone: string, nickname: string, referralCode?: string) => Promise<boolean>
purchaseSection: (sectionId: string, sectionTitle?: string, paymentMethod?: string) => Promise<boolean>
purchaseFullBook: (paymentMethod?: string) => Promise<boolean>
hasPurchased: (sectionId: string) => boolean
adminLogin: (username: string, password: string) => boolean
updateSettings: (newSettings: Partial<Settings>) => void
getAllUsers: () => User[]
getAllPurchases: () => Purchase[]
addUser: (user: Partial<User>) => User
updateUser: (userId: string, updates: Partial<User>) => void
deleteUser: (userId: string) => void
addPurchase: (purchase: Omit<Purchase, "id" | "createdAt">) => void
requestWithdrawal: (amount: number, method: "wechat" | "alipay", account: string, name: string) => void
completeWithdrawal: (id: string) => void
updateLiveQRCode: (config: Partial<LiveQRCodeConfig>) => void
getRandomQRCode: () => QRCodeConfig | null
getLiveQRCodeUrl: (qrId: string) => string | null
exportData: () => string
fetchSettings: () => Promise<void>
}
const defaultSettings: Settings = {
distributorShare: 90,
authorShare: 10,
paymentMethods: {
wechat: {
enabled: true,
qrCode: "",
account: "",
websiteAppId: "",
websiteAppSecret: "",
serviceAppId: "",
serviceAppSecret: "",
mpVerifyCode: "",
merchantId: "",
apiKey: "",
},
alipay: {
enabled: true,
qrCode: "",
account: "",
partnerId: "",
securityKey: "",
mobilePayEnabled: true,
paymentInterface: "official",
},
usdt: {
enabled: true,
network: "TRC20",
address: "",
exchangeRate: 7.2,
},
paypal: {
enabled: false,
email: "",
exchangeRate: 7.2,
},
},
sectionPrice: 1,
baseBookPrice: 9.9,
pricePerSection: 1,
qrCodes: [
{
id: "default",
name: "Soul派对群",
url: "https://soul.cn/party",
imageUrl: "/images/image.png",
weight: 1,
enabled: true,
},
],
liveQRCodes: [
{
id: "party-group",
name: "派对群活码",
urls: ["https://soul.cn/party1", "https://soul.cn/party2", "https://soul.cn/party3"],
imageUrl: "/images/image.png",
redirectType: "random",
clickCount: 0,
enabled: true,
},
],
feishuSync: {
enabled: false,
docUrl: "",
autoSync: false,
syncInterval: 60,
},
authorInfo: {
name: "卡若",
description: "连续创业者,私域运营专家,每天早上6-9点在Soul派对房分享真实商业故事",
liveTime: "06:00-09:00",
platform: "Soul派对房",
},
}
export const useStore = create<StoreState>()(
persist(
(set, get) => ({
user: null,
isLoggedIn: false,
purchases: [],
withdrawals: [],
settings: defaultSettings,
login: async (phone: string, code: string) => {
if (code !== "123456") {
return false
}
const users = JSON.parse(localStorage.getItem("users") || "[]") as User[]
const existingUser = users.find((u) => u.phone === phone)
if (existingUser) {
set({ user: existingUser, isLoggedIn: true })
return true
}
return false
},
logout: () => {
set({ user: null, isLoggedIn: false })
},
register: async (phone: string, nickname: string, referralCode?: string) => {
const users = JSON.parse(localStorage.getItem("users") || "[]") as User[]
if (users.find((u) => u.phone === phone)) {
return false
}
const newUser: User = {
id: `user_${Date.now()}`,
phone,
nickname,
isAdmin: false,
purchasedSections: [],
hasFullBook: false,
referralCode: `REF${Date.now().toString(36).toUpperCase()}`,
referredBy: referralCode,
earnings: 0,
pendingEarnings: 0,
withdrawnEarnings: 0,
referralCount: 0,
createdAt: new Date().toISOString(),
}
if (referralCode) {
const referrer = users.find((u) => u.referralCode === referralCode)
if (referrer) {
referrer.referralCount = (referrer.referralCount || 0) + 1
localStorage.setItem("users", JSON.stringify(users))
}
}
users.push(newUser)
localStorage.setItem("users", JSON.stringify(users))
set({ user: newUser, isLoggedIn: true })
return true
},
purchaseSection: async (sectionId: string, sectionTitle?: string, paymentMethod?: string) => {
const { user, settings } = get()
if (!user) return false
const amount = settings.sectionPrice
const purchase: Purchase = {
id: `purchase_${Date.now()}`,
userId: user.id,
userPhone: user.phone,
userNickname: user.nickname,
type: "section",
sectionId,
sectionTitle,
amount,
paymentMethod,
referralCode: user.referredBy,
status: "completed",
createdAt: new Date().toISOString(),
}
const updatedUser = {
...user,
purchasedSections: [...user.purchasedSections, sectionId],
}
const users = JSON.parse(localStorage.getItem("users") || "[]") as User[]
const userIndex = users.findIndex((u) => u.id === user.id)
if (userIndex !== -1) {
users[userIndex] = updatedUser
localStorage.setItem("users", JSON.stringify(users))
}
if (user.referredBy) {
const referrer = users.find((u) => u.referralCode === user.referredBy)
if (referrer) {
const referrerEarnings = amount * (settings.distributorShare / 100)
referrer.earnings += referrerEarnings
referrer.pendingEarnings = (referrer.pendingEarnings || 0) + referrerEarnings
purchase.referrerEarnings = referrerEarnings
localStorage.setItem("users", JSON.stringify(users))
}
}
const purchases = JSON.parse(localStorage.getItem("all_purchases") || "[]") as Purchase[]
purchases.push(purchase)
localStorage.setItem("all_purchases", JSON.stringify(purchases))
set({ user: updatedUser, purchases: [...get().purchases, purchase] })
return true
},
purchaseFullBook: async (paymentMethod?: string) => {
const { user, settings } = get()
if (!user) return false
const fullBookPrice = getFullBookPrice()
const purchase: Purchase = {
id: `purchase_${Date.now()}`,
userId: user.id,
userPhone: user.phone,
userNickname: user.nickname,
type: "fullbook",
amount: fullBookPrice,
paymentMethod,
referralCode: user.referredBy,
status: "completed",
createdAt: new Date().toISOString(),
}
const updatedUser = { ...user, hasFullBook: true }
const users = JSON.parse(localStorage.getItem("users") || "[]") as User[]
const userIndex = users.findIndex((u) => u.id === user.id)
if (userIndex !== -1) {
users[userIndex] = updatedUser
localStorage.setItem("users", JSON.stringify(users))
}
if (user.referredBy) {
const referrer = users.find((u) => u.referralCode === user.referredBy)
if (referrer) {
const referrerEarnings = fullBookPrice * (settings.distributorShare / 100)
referrer.earnings += referrerEarnings
referrer.pendingEarnings = (referrer.pendingEarnings || 0) + referrerEarnings
purchase.referrerEarnings = referrerEarnings
localStorage.setItem("users", JSON.stringify(users))
}
}
const purchases = JSON.parse(localStorage.getItem("all_purchases") || "[]") as Purchase[]
purchases.push(purchase)
localStorage.setItem("all_purchases", JSON.stringify(purchases))
set({ user: updatedUser, purchases: [...get().purchases, purchase] })
return true
},
hasPurchased: (sectionId: string) => {
const { user } = get()
if (!user) return false
if (user.hasFullBook) return true
return user.purchasedSections.includes(sectionId)
},
adminLogin: (username: string, password: string) => {
if (username.toLowerCase() === "admin" && password === "key123456") {
const adminUser: User = {
id: "admin",
phone: "admin",
nickname: "管理员",
isAdmin: true,
purchasedSections: [],
hasFullBook: true,
referralCode: "ADMIN",
earnings: 0,
pendingEarnings: 0,
withdrawnEarnings: 0,
referralCount: 0,
createdAt: new Date().toISOString(),
}
set({ user: adminUser, isLoggedIn: true })
return true
}
return false
},
updateSettings: (newSettings: Partial<Settings>) => {
const { settings } = get()
const updatedSettings = { ...settings, ...newSettings }
if (newSettings.distributorShare !== undefined) {
updatedSettings.authorShare = 100 - newSettings.distributorShare
}
set({ settings: updatedSettings })
if (typeof window !== "undefined") {
localStorage.setItem("app_settings", JSON.stringify(updatedSettings))
}
},
getAllUsers: () => {
if (typeof window === "undefined") return []
return JSON.parse(localStorage.getItem("users") || "[]") as User[]
},
getAllPurchases: () => {
if (typeof window === "undefined") return []
return JSON.parse(localStorage.getItem("all_purchases") || "[]") as Purchase[]
},
addUser: (userData: Partial<User>) => {
if (typeof window === "undefined") return { id: "temp", ...userData } as User
const users = JSON.parse(localStorage.getItem("users") || "[]") as User[]
const newUser: User = {
id: `user_${Date.now()}`,
phone: userData.phone || "",
nickname: userData.nickname || "新用户",
isAdmin: false,
purchasedSections: [],
hasFullBook: false,
referralCode: `REF${Date.now().toString(36).toUpperCase()}`,
earnings: 0,
pendingEarnings: 0,
withdrawnEarnings: 0,
referralCount: 0,
createdAt: new Date().toISOString(),
...userData,
}
users.push(newUser)
localStorage.setItem("users", JSON.stringify(users))
return newUser
},
updateUser: (userId: string, updates: Partial<User>) => {
if (typeof window === "undefined") return
const users = JSON.parse(localStorage.getItem("users") || "[]") as User[]
const index = users.findIndex((u) => u.id === userId)
if (index !== -1) {
users[index] = { ...users[index], ...updates }
localStorage.setItem("users", JSON.stringify(users))
}
},
deleteUser: (userId: string) => {
if (typeof window === "undefined") return
const users = JSON.parse(localStorage.getItem("users") || "[]") as User[]
const filtered = users.filter((u) => u.id !== userId)
localStorage.setItem("users", JSON.stringify(filtered))
},
addPurchase: (purchaseData) =>
set((state) => {
const newPurchase: Purchase = {
id: Math.random().toString(36).substring(2, 9),
createdAt: new Date().toISOString(),
...purchaseData,
}
// 如果是全书购买,更新用户状态
if (state.user && purchaseData.userId === state.user.id) {
const updatedUser = { ...state.user }
if (purchaseData.type === "fullbook") {
updatedUser.hasFullBook = true
} else if (purchaseData.sectionId) {
updatedUser.purchasedSections = [
...updatedUser.purchasedSections,
purchaseData.sectionId,
]
}
// 更新 users 数组
const updatedUsers = state.users?.map((u) =>
u.id === updatedUser.id ? updatedUser : u
) || []
return {
purchases: [...state.purchases, newPurchase],
user: updatedUser,
}
}
return {
purchases: [...state.purchases, newPurchase],
}
}),
requestWithdrawal: (amount, method, account, name) =>
set((state) => {
if (!state.user) return {}
if (state.user.earnings < amount) return {}
const newWithdrawal: Withdrawal = {
id: Math.random().toString(36).substring(2, 9),
userId: state.user.id,
amount,
method,
account,
name,
status: "pending",
createdAt: new Date().toISOString()
}
// 扣除余额,增加冻结/提现中金额
const updatedUser = {
...state.user,
earnings: state.user.earnings - amount,
pendingEarnings: state.user.pendingEarnings + amount
}
return {
withdrawals: [...(state.withdrawals || []), newWithdrawal],
user: updatedUser,
}
}),
completeWithdrawal: (id) =>
set((state) => {
const withdrawals = state.withdrawals || []
const withdrawalIndex = withdrawals.findIndex(w => w.id === id)
if (withdrawalIndex === -1) return {}
const withdrawal = withdrawals[withdrawalIndex]
if (withdrawal.status !== "pending") return {}
const updatedWithdrawals = [...withdrawals]
updatedWithdrawals[withdrawalIndex] = {
...withdrawal,
status: "completed",
completedAt: new Date().toISOString()
}
// 这里我们只是更新状态,资金已经在申请时扣除了
// 实际场景中可能需要确认转账成功
return {
withdrawals: updatedWithdrawals,
}
}),
updateLiveQRCode: (config) =>
set((state) => {
const { settings } = state
const updatedLiveQRCodes = settings.liveQRCodes.map((qr) =>
qr.id === config.id ? { ...qr, ...config } : qr
)
// 如果不存在且有id则添加? 暂时只支持更新
return {
settings: { ...settings, liveQRCodes: updatedLiveQRCodes },
}
}),
getRandomQRCode: () => {
const { settings } = get()
const enabledQRs = settings.qrCodes.filter((qr) => qr.enabled)
if (enabledQRs.length === 0) return null
const totalWeight = enabledQRs.reduce((sum, qr) => sum + qr.weight, 0)
let random = Math.random() * totalWeight
for (const qr of enabledQRs) {
random -= qr.weight
if (random <= 0) return qr
}
return enabledQRs[0]
},
getLiveQRCodeUrl: (qrId: string) => {
const { settings } = get()
const liveQR = settings.liveQRCodes.find((qr) => qr.id === qrId && qr.enabled)
if (!liveQR || liveQR.urls.length === 0) return null
// 更新点击次数
liveQR.clickCount++
if (liveQR.redirectType === "random") {
const randomIndex = Math.floor(Math.random() * liveQR.urls.length)
return liveQR.urls[randomIndex]
} else if (liveQR.redirectType === "sequential") {
const index = liveQR.clickCount % liveQR.urls.length
return liveQR.urls[index]
} else if (liveQR.redirectType === "weighted" && liveQR.weights) {
const totalWeight = liveQR.weights.reduce((sum, w) => sum + w, 0)
let random = Math.random() * totalWeight
for (let i = 0; i < liveQR.urls.length; i++) {
random -= liveQR.weights[i] || 1
if (random <= 0) return liveQR.urls[i]
}
}
return liveQR.urls[0]
},
exportData: () => {
const { user, purchases, settings } = get()
const data = {
user,
purchases,
settings,
exportDate: new Date().toISOString(),
}
return JSON.stringify(data, null, 2)
},
fetchSettings: async () => {
try {
const res = await fetch("/api/config")
if (!res.ok) throw new Error("Failed to fetch config")
const data = await res.json()
const { settings } = get()
// Deep merge payment methods to preserve existing defaults if API is partial
const mergedPaymentMethods = {
...settings.paymentMethods,
wechat: { ...settings.paymentMethods.wechat, ...data.paymentMethods?.wechat },
alipay: { ...settings.paymentMethods.alipay, ...data.paymentMethods?.alipay },
usdt: { ...settings.paymentMethods.usdt, ...data.paymentMethods?.usdt },
paypal: { ...settings.paymentMethods.paypal, ...data.paymentMethods?.paypal },
}
const newSettings: Partial<Settings> = {
paymentMethods: mergedPaymentMethods,
authorInfo: { ...settings.authorInfo, ...data.authorInfo },
}
set({ settings: { ...settings, ...newSettings } })
} catch (error) {
console.error("Failed to sync settings:", error)
}
},
}),
{
name: "soul-experiment-storage",
},
),
)