180 lines
4.3 KiB
JavaScript
180 lines
4.3 KiB
JavaScript
import { reactive } from 'vue'
|
|
import { createDefaultData } from './constants'
|
|
|
|
const STORAGE_KEY = 'bill-helper-miniapp-v1'
|
|
|
|
function deepClone(value) {
|
|
return JSON.parse(JSON.stringify(value))
|
|
}
|
|
|
|
function normalizeData(raw) {
|
|
const fallback = createDefaultData()
|
|
const source = raw || {}
|
|
|
|
return {
|
|
categories: {
|
|
expense: Array.isArray(source.categories?.expense) && source.categories.expense.length
|
|
? source.categories.expense
|
|
: fallback.categories.expense,
|
|
income: Array.isArray(source.categories?.income) && source.categories.income.length
|
|
? source.categories.income
|
|
: fallback.categories.income
|
|
},
|
|
accounts: Array.isArray(source.accounts) && source.accounts.length ? source.accounts : fallback.accounts,
|
|
bills: Array.isArray(source.bills) ? source.bills : fallback.bills,
|
|
budgets: {
|
|
total: Number(source.budgets?.total) || fallback.budgets.total,
|
|
categoryBudgets: source.budgets?.categoryBudgets || fallback.budgets.categoryBudgets
|
|
},
|
|
settings: {
|
|
theme: source.settings?.theme || fallback.settings.theme,
|
|
profile: {
|
|
authorized: Boolean(source.settings?.profile?.authorized),
|
|
nickname: source.settings?.profile?.nickname || fallback.settings.profile.nickname,
|
|
avatarUrl: source.settings?.profile?.avatarUrl || ''
|
|
},
|
|
lastBackupAt: source.settings?.lastBackupAt || ''
|
|
}
|
|
}
|
|
}
|
|
|
|
function loadData() {
|
|
try {
|
|
const raw = uni.getStorageSync(STORAGE_KEY)
|
|
if (!raw) {
|
|
const seeded = createDefaultData()
|
|
uni.setStorageSync(STORAGE_KEY, seeded)
|
|
return seeded
|
|
}
|
|
return normalizeData(raw)
|
|
} catch (error) {
|
|
return createDefaultData()
|
|
}
|
|
}
|
|
|
|
const state = reactive(normalizeData(loadData()))
|
|
|
|
function patchState(nextState) {
|
|
state.categories.expense.splice(0, state.categories.expense.length, ...nextState.categories.expense)
|
|
state.categories.income.splice(0, state.categories.income.length, ...nextState.categories.income)
|
|
state.accounts.splice(0, state.accounts.length, ...nextState.accounts)
|
|
state.bills.splice(0, state.bills.length, ...nextState.bills)
|
|
state.budgets.total = Number(nextState.budgets.total) || 0
|
|
state.budgets.categoryBudgets = { ...nextState.budgets.categoryBudgets }
|
|
state.settings.theme = nextState.settings.theme
|
|
state.settings.profile = { ...nextState.settings.profile }
|
|
state.settings.lastBackupAt = nextState.settings.lastBackupAt || ''
|
|
}
|
|
|
|
function persist() {
|
|
uni.setStorageSync(STORAGE_KEY, deepClone(state))
|
|
}
|
|
|
|
function buildBillPayload(payload) {
|
|
return {
|
|
id: payload.id || `bill-${Date.now()}`,
|
|
type: payload.type || 'expense',
|
|
amount: Number(payload.amount) || 0,
|
|
categoryId: payload.categoryId || '',
|
|
accountId: payload.accountId || '',
|
|
note: payload.note || '',
|
|
date: payload.date,
|
|
createdAt: payload.createdAt || Date.now()
|
|
}
|
|
}
|
|
|
|
export function useAppStore() {
|
|
function saveBill(payload) {
|
|
const nextBill = buildBillPayload(payload)
|
|
const index = state.bills.findIndex((item) => item.id === nextBill.id)
|
|
|
|
if (index === -1) {
|
|
state.bills.unshift(nextBill)
|
|
} else {
|
|
state.bills.splice(index, 1, nextBill)
|
|
}
|
|
|
|
persist()
|
|
}
|
|
|
|
function deleteBill(id) {
|
|
state.bills.splice(
|
|
0,
|
|
state.bills.length,
|
|
...state.bills.filter((item) => item.id !== id)
|
|
)
|
|
persist()
|
|
}
|
|
|
|
function deleteBills(ids) {
|
|
const idSet = new Set(ids)
|
|
state.bills.splice(
|
|
0,
|
|
state.bills.length,
|
|
...state.bills.filter((item) => !idSet.has(item.id))
|
|
)
|
|
persist()
|
|
}
|
|
|
|
function setBudgetTotal(value) {
|
|
state.budgets.total = Number(value) || 0
|
|
persist()
|
|
}
|
|
|
|
function setCategoryBudget(categoryId, value) {
|
|
state.budgets.categoryBudgets = {
|
|
...state.budgets.categoryBudgets,
|
|
[categoryId]: Number(value) || 0
|
|
}
|
|
persist()
|
|
}
|
|
|
|
function setTheme(theme) {
|
|
state.settings.theme = theme
|
|
persist()
|
|
}
|
|
|
|
function setProfile(profile) {
|
|
state.settings.profile = {
|
|
...state.settings.profile,
|
|
...profile
|
|
}
|
|
persist()
|
|
}
|
|
|
|
function markBackup(timeLabel) {
|
|
state.settings.lastBackupAt = timeLabel
|
|
persist()
|
|
}
|
|
|
|
function exportBackup() {
|
|
return JSON.stringify(deepClone(state), null, 2)
|
|
}
|
|
|
|
function importBackup(payload) {
|
|
const parsed = normalizeData(JSON.parse(payload))
|
|
patchState(parsed)
|
|
persist()
|
|
}
|
|
|
|
function resetAll() {
|
|
patchState(createDefaultData())
|
|
persist()
|
|
}
|
|
|
|
return {
|
|
state,
|
|
saveBill,
|
|
deleteBill,
|
|
deleteBills,
|
|
setBudgetTotal,
|
|
setCategoryBudget,
|
|
setTheme,
|
|
setProfile,
|
|
markBackup,
|
|
exportBackup,
|
|
importBackup,
|
|
resetAll
|
|
}
|
|
}
|