fix: 清理死代码 + 修复群组模式预算支出计算
- 清理 store 中未使用的 loading 状态(stats/budget/category/user) - 清理 stats store 冗余类型重导出和 catch 重置逻辑 - 清理 transaction store 未使用的 currentPage - 清理 api/group 未使用的 GroupDetail/GroupMember/getGroupDetail - 删除无引用的 api/index.ts barrel 导出文件 - 删除后端未调用的 GET /groups/:id 群组详情路由 - 合并 stats 页面重复的 loadTabData 函数 - 修复预算页群组模式下只算个人支出(不传 group_id)
This commit is contained in:
@@ -12,21 +12,6 @@ export interface Group {
|
|||||||
member_count: number
|
member_count: number
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 群组详情(含成员) */
|
|
||||||
export interface GroupDetail extends Group {
|
|
||||||
members: GroupMember[]
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 群组成员 */
|
|
||||||
export interface GroupMember {
|
|
||||||
user_id: number
|
|
||||||
role: 'owner' | 'member'
|
|
||||||
nickname: string
|
|
||||||
user_nickname: string
|
|
||||||
avatar_url: string
|
|
||||||
joined_at: string
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 创建群组 */
|
/** 创建群组 */
|
||||||
export function createGroup(data: { name: string }) {
|
export function createGroup(data: { name: string }) {
|
||||||
return request<{ id: number; invite_code: string }>({ url: '/groups', method: 'POST', data })
|
return request<{ id: number; invite_code: string }>({ url: '/groups', method: 'POST', data })
|
||||||
@@ -37,11 +22,6 @@ export function getGroups() {
|
|||||||
return request<Group[]>({ url: '/groups' })
|
return request<Group[]>({ url: '/groups' })
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 获取群组详情 */
|
|
||||||
export function getGroupDetail(id: number) {
|
|
||||||
return request<GroupDetail>({ url: `/groups/${id}` })
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 加入群组 */
|
/** 加入群组 */
|
||||||
export function joinGroup(invite_code: string) {
|
export function joinGroup(invite_code: string) {
|
||||||
return request<{ group_id: number; name: string }>({ url: '/groups/join', method: 'POST', data: { invite_code } })
|
return request<{ group_id: number; name: string }>({ url: '/groups/join', method: 'POST', data: { invite_code } })
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
export * from './auth'
|
|
||||||
export * from './transaction'
|
|
||||||
export * from './category'
|
|
||||||
export * from './budget'
|
|
||||||
export * from './stats'
|
|
||||||
@@ -77,8 +77,8 @@
|
|||||||
import { ref, onMounted } from 'vue'
|
import { ref, onMounted } from 'vue'
|
||||||
import { onShow } from '@dcloudio/uni-app'
|
import { onShow } from '@dcloudio/uni-app'
|
||||||
import { useBudgetStore } from '@/stores/budget'
|
import { useBudgetStore } from '@/stores/budget'
|
||||||
import { useStatsStore } from '@/stores/stats'
|
|
||||||
import { useGroupStore } from '@/stores/group'
|
import { useGroupStore } from '@/stores/group'
|
||||||
|
import { getOverview } from '@/api/stats'
|
||||||
import { waitForReady } from '@/utils/app-ready'
|
import { waitForReady } from '@/utils/app-ready'
|
||||||
import { formatAmount, getCurrentMonth } from '@/utils/format'
|
import { formatAmount, getCurrentMonth } from '@/utils/format'
|
||||||
import { statusBarHeight } from '@/utils/system'
|
import { statusBarHeight } from '@/utils/system'
|
||||||
@@ -87,7 +87,6 @@ import Numpad from '@/components/Numpad/Numpad.vue'
|
|||||||
import Icon from '@/components/Icon/Icon.vue'
|
import Icon from '@/components/Icon/Icon.vue'
|
||||||
|
|
||||||
const budgetStore = useBudgetStore()
|
const budgetStore = useBudgetStore()
|
||||||
const statsStore = useStatsStore()
|
|
||||||
const groupStore = useGroupStore()
|
const groupStore = useGroupStore()
|
||||||
|
|
||||||
const currentMonth = getCurrentMonth()
|
const currentMonth = getCurrentMonth()
|
||||||
@@ -106,7 +105,6 @@ function formatPreset(val: number) {
|
|||||||
|
|
||||||
function syncBudgetData() {
|
function syncBudgetData() {
|
||||||
budget.value = budgetStore.budget
|
budget.value = budgetStore.budget
|
||||||
monthExpense.value = statsStore.overview.expense
|
|
||||||
// 群组模式用 myAmount(个人预算),个人模式用 amount
|
// 群组模式用 myAmount(个人预算),个人模式用 amount
|
||||||
budgetAmount.value = budget.value
|
budgetAmount.value = budget.value
|
||||||
? (groupStore.isGroupMode ? (budget.value.myAmount || 0) : budget.value.amount)
|
? (groupStore.isGroupMode ? (budget.value.myAmount || 0) : budget.value.amount)
|
||||||
@@ -116,13 +114,19 @@ function syncBudgetData() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 获取个人支出(不传 group_id,始终只算自己的消费) */
|
||||||
|
async function fetchMyExpense() {
|
||||||
|
const data = await getOverview({ month: currentMonth, group_id: null })
|
||||||
|
monthExpense.value = Number(data?.expense) || 0
|
||||||
|
}
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
await waitForReady()
|
await waitForReady()
|
||||||
loading.value = true
|
loading.value = true
|
||||||
try {
|
try {
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
budgetStore.fetchBudget(currentMonth),
|
budgetStore.fetchBudget(currentMonth),
|
||||||
statsStore.fetchOverview(currentMonth)
|
fetchMyExpense()
|
||||||
])
|
])
|
||||||
syncBudgetData()
|
syncBudgetData()
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -139,7 +143,7 @@ onShow(async () => {
|
|||||||
try {
|
try {
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
budgetStore.fetchBudget(currentMonth),
|
budgetStore.fetchBudget(currentMonth),
|
||||||
statsStore.fetchOverview(currentMonth)
|
fetchMyExpense()
|
||||||
])
|
])
|
||||||
syncBudgetData()
|
syncBudgetData()
|
||||||
} catch {}
|
} catch {}
|
||||||
|
|||||||
@@ -236,7 +236,7 @@ onShow(() => {
|
|||||||
|
|
||||||
// Only re-fetch category/trend on tab switch; re-fetch all on month change
|
// Only re-fetch category/trend on tab switch; re-fetch all on month change
|
||||||
watch(currentMonth, () => loadData())
|
watch(currentMonth, () => loadData())
|
||||||
watch(tabType, () => loadTabData())
|
watch(tabType, () => loadData(true))
|
||||||
|
|
||||||
async function loadData(silent = false) {
|
async function loadData(silent = false) {
|
||||||
const seq = ++loadSeq
|
const seq = ++loadSeq
|
||||||
@@ -260,26 +260,6 @@ async function loadData(silent = false) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Re-fetch all data when tab switches
|
|
||||||
async function loadTabData() {
|
|
||||||
const seq = ++loadSeq
|
|
||||||
loadError.value = false
|
|
||||||
try {
|
|
||||||
await Promise.all([
|
|
||||||
statsStore.fetchOverview(currentMonth.value),
|
|
||||||
statsStore.fetchCategoryStats(currentMonth.value, tabType.value),
|
|
||||||
statsStore.fetchTrend(currentMonth.value, tabType.value),
|
|
||||||
fetchMaxSingle(),
|
|
||||||
fetchPrevMonthData()
|
|
||||||
])
|
|
||||||
if (seq !== loadSeq) return
|
|
||||||
} catch (e) {
|
|
||||||
if (seq !== loadSeq) return
|
|
||||||
console.error('Stats tab load error:', e)
|
|
||||||
loadError.value = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function fetchMaxSingle() {
|
async function fetchMaxSingle() {
|
||||||
try {
|
try {
|
||||||
const [y, m] = currentMonth.value.split('-').map(Number)
|
const [y, m] = currentMonth.value.split('-').map(Number)
|
||||||
|
|||||||
@@ -4,24 +4,12 @@ import * as api from '@/api/budget'
|
|||||||
import { getCurrentMonth } from '@/utils/format'
|
import { getCurrentMonth } from '@/utils/format'
|
||||||
import { useGroupStore } from '@/stores/group'
|
import { useGroupStore } from '@/stores/group'
|
||||||
|
|
||||||
// 重新导出类型以保持向后兼容
|
|
||||||
export type Budget = api.Budget
|
|
||||||
|
|
||||||
export const useBudgetStore = defineStore('budget', () => {
|
export const useBudgetStore = defineStore('budget', () => {
|
||||||
const budget = ref<api.Budget | null>(null)
|
const budget = ref<api.Budget | null>(null)
|
||||||
const loading = ref(false)
|
|
||||||
|
|
||||||
async function fetchBudget(month?: string) {
|
async function fetchBudget(month?: string) {
|
||||||
loading.value = true
|
|
||||||
try {
|
|
||||||
const groupStore = useGroupStore()
|
const groupStore = useGroupStore()
|
||||||
budget.value = await api.getBudget(month || getCurrentMonth(), groupStore.currentGroupId)
|
budget.value = await api.getBudget(month || getCurrentMonth(), groupStore.currentGroupId)
|
||||||
} catch (err) {
|
|
||||||
budget.value = null
|
|
||||||
throw err
|
|
||||||
} finally {
|
|
||||||
loading.value = false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function setBudget(amount: number, month?: string) {
|
async function setBudget(amount: number, month?: string) {
|
||||||
@@ -29,5 +17,5 @@ export const useBudgetStore = defineStore('budget', () => {
|
|||||||
await fetchBudget(month)
|
await fetchBudget(month)
|
||||||
}
|
}
|
||||||
|
|
||||||
return { budget, loading, fetchBudget, setBudget }
|
return { budget, fetchBudget, setBudget }
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -7,17 +7,9 @@ export type Category = api.Category
|
|||||||
|
|
||||||
export const useCategoryStore = defineStore('category', () => {
|
export const useCategoryStore = defineStore('category', () => {
|
||||||
const categories = ref<api.Category[]>([])
|
const categories = ref<api.Category[]>([])
|
||||||
const loading = ref(false)
|
|
||||||
|
|
||||||
async function fetchCategories() {
|
async function fetchCategories() {
|
||||||
loading.value = true
|
|
||||||
try {
|
|
||||||
categories.value = await api.getCategories()
|
categories.value = await api.getCategories()
|
||||||
} catch {
|
|
||||||
categories.value = []
|
|
||||||
} finally {
|
|
||||||
loading.value = false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getByType(type: 'expense' | 'income') {
|
function getByType(type: 'expense' | 'income') {
|
||||||
@@ -53,5 +45,5 @@ export const useCategoryStore = defineStore('category', () => {
|
|||||||
await fetchCategories()
|
await fetchCategories()
|
||||||
}
|
}
|
||||||
|
|
||||||
return { categories, loading, fetchCategories, getByType, getById, addCategory, updateCategory, deleteCategory, migrateCategory, sortCategories }
|
return { categories, fetchCategories, getByType, getById, addCategory, updateCategory, deleteCategory, migrateCategory, sortCategories }
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -4,24 +4,26 @@ import * as api from '@/api/stats'
|
|||||||
import { getCurrentMonth } from '@/utils/format'
|
import { getCurrentMonth } from '@/utils/format'
|
||||||
import { useGroupStore } from '@/stores/group'
|
import { useGroupStore } from '@/stores/group'
|
||||||
|
|
||||||
// 重新导出类型以保持向后兼容
|
/** 默认概览数据,避免模板中访问属性报错 */
|
||||||
export type Overview = api.Overview
|
const EMPTY_OVERVIEW: api.Overview = {
|
||||||
export type CategoryStat = api.CategoryStat
|
expense: 0, income: 0, expenseCount: 0, incomeCount: 0,
|
||||||
export type TrendPoint = api.TrendPoint
|
count: 0, daily: 0, dailyIncome: 0
|
||||||
|
}
|
||||||
|
|
||||||
export const useStatsStore = defineStore('stats', () => {
|
export const useStatsStore = defineStore('stats', () => {
|
||||||
const overview = ref<api.Overview>({ expense: 0, income: 0, expenseCount: 0, incomeCount: 0, count: 0, daily: 0, dailyIncome: 0 })
|
const overview = ref<api.Overview>({ ...EMPTY_OVERVIEW })
|
||||||
const categoryStats = ref<api.CategoryStat[]>([])
|
const categoryStats = ref<api.CategoryStat[]>([])
|
||||||
const trendData = ref<api.TrendPoint[]>([])
|
const trendData = ref<api.TrendPoint[]>([])
|
||||||
const loading = ref(false)
|
|
||||||
|
/** 获取当前 group_id,统一收口 */
|
||||||
|
function getGroupId() {
|
||||||
|
return useGroupStore().currentGroupId
|
||||||
|
}
|
||||||
|
|
||||||
async function fetchOverview(month?: string) {
|
async function fetchOverview(month?: string) {
|
||||||
loading.value = true
|
|
||||||
try {
|
|
||||||
const groupStore = useGroupStore()
|
|
||||||
const data = await api.getOverview({
|
const data = await api.getOverview({
|
||||||
month: month || getCurrentMonth(),
|
month: month || getCurrentMonth(),
|
||||||
group_id: groupStore.currentGroupId
|
group_id: getGroupId()
|
||||||
})
|
})
|
||||||
overview.value = {
|
overview.value = {
|
||||||
expense: Number(data?.expense) || 0,
|
expense: Number(data?.expense) || 0,
|
||||||
@@ -32,42 +34,24 @@ export const useStatsStore = defineStore('stats', () => {
|
|||||||
daily: Number(data?.daily) || 0,
|
daily: Number(data?.daily) || 0,
|
||||||
dailyIncome: Number(data?.dailyIncome) || 0
|
dailyIncome: Number(data?.dailyIncome) || 0
|
||||||
}
|
}
|
||||||
} catch (err) {
|
|
||||||
overview.value = { expense: 0, income: 0, expenseCount: 0, incomeCount: 0, count: 0, daily: 0, dailyIncome: 0 }
|
|
||||||
throw err
|
|
||||||
} finally {
|
|
||||||
loading.value = false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function fetchCategoryStats(month?: string, type: string = 'expense') {
|
async function fetchCategoryStats(month?: string, type: string = 'expense') {
|
||||||
try {
|
const data = await api.getCategoryStats(month || getCurrentMonth(), type, getGroupId())
|
||||||
const groupStore = useGroupStore()
|
|
||||||
const data = await api.getCategoryStats(month || getCurrentMonth(), type, groupStore.currentGroupId)
|
|
||||||
categoryStats.value = Array.isArray(data) ? data.map(item => ({
|
categoryStats.value = Array.isArray(data) ? data.map(item => ({
|
||||||
...item,
|
...item,
|
||||||
amount: Number(item.amount) || 0,
|
amount: Number(item.amount) || 0,
|
||||||
count: Number(item.count) || 0
|
count: Number(item.count) || 0
|
||||||
})) : []
|
})) : []
|
||||||
} catch (err) {
|
|
||||||
categoryStats.value = []
|
|
||||||
throw err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function fetchTrend(month?: string, type: string = 'expense') {
|
async function fetchTrend(month?: string, type: string = 'expense') {
|
||||||
try {
|
const data = await api.getTrend(month || getCurrentMonth(), type, getGroupId())
|
||||||
const groupStore = useGroupStore()
|
|
||||||
const data = await api.getTrend(month || getCurrentMonth(), type, groupStore.currentGroupId)
|
|
||||||
trendData.value = Array.isArray(data) ? data.map(item => ({
|
trendData.value = Array.isArray(data) ? data.map(item => ({
|
||||||
...item,
|
...item,
|
||||||
amount: Number(item.amount) || 0
|
amount: Number(item.amount) || 0
|
||||||
})) : []
|
})) : []
|
||||||
} catch (err) {
|
|
||||||
trendData.value = []
|
|
||||||
throw err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return { overview, categoryStats, trendData, loading, fetchOverview, fetchCategoryStats, fetchTrend }
|
return { overview, categoryStats, trendData, fetchOverview, fetchCategoryStats, fetchTrend }
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ export const useTransactionStore = defineStore('transaction', () => {
|
|||||||
const transactions = ref<api.Transaction[]>([])
|
const transactions = ref<api.Transaction[]>([])
|
||||||
const total = ref(0)
|
const total = ref(0)
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
const currentPage = ref(1)
|
|
||||||
|
|
||||||
async function fetchTransactions(params?: api.TransactionParams) {
|
async function fetchTransactions(params?: api.TransactionParams) {
|
||||||
loading.value = true
|
loading.value = true
|
||||||
@@ -67,5 +66,5 @@ export const useTransactionStore = defineStore('transaction', () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return { transactions, total, loading, currentPage, fetchTransactions, addTransaction, updateTransaction, deleteTransaction, fetchTransactionById }
|
return { transactions, total, loading, fetchTransactions, addTransaction, updateTransaction, deleteTransaction, fetchTransactionById }
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import { API_BASE } from '@/config'
|
|||||||
|
|
||||||
export const useUserStore = defineStore('user', () => {
|
export const useUserStore = defineStore('user', () => {
|
||||||
const userInfo = ref<api.UserInfo | null>(null)
|
const userInfo = ref<api.UserInfo | null>(null)
|
||||||
const loading = ref(false)
|
|
||||||
|
|
||||||
const nickname = computed(() => {
|
const nickname = computed(() => {
|
||||||
return userInfo.value?.nickname || uni.getStorageSync('xc:nickname') || '小菜'
|
return userInfo.value?.nickname || uni.getStorageSync('xc:nickname') || '小菜'
|
||||||
@@ -21,19 +20,11 @@ export const useUserStore = defineStore('user', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
async function fetchUserInfo() {
|
async function fetchUserInfo() {
|
||||||
loading.value = true
|
|
||||||
try {
|
|
||||||
userInfo.value = await api.getUserInfo()
|
userInfo.value = await api.getUserInfo()
|
||||||
if (userInfo.value) {
|
if (userInfo.value) {
|
||||||
uni.setStorageSync('xc:nickname', userInfo.value.nickname)
|
uni.setStorageSync('xc:nickname', userInfo.value.nickname)
|
||||||
uni.setStorageSync('xc:avatar_url', userInfo.value.avatar_url)
|
uni.setStorageSync('xc:avatar_url', userInfo.value.avatar_url)
|
||||||
}
|
}
|
||||||
} catch (e) {
|
|
||||||
console.error('[UserStore] fetchUserInfo error:', e)
|
|
||||||
throw e
|
|
||||||
} finally {
|
|
||||||
loading.value = false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function updateProfile(nickname: string) {
|
async function updateProfile(nickname: string) {
|
||||||
@@ -49,5 +40,5 @@ export const useUserStore = defineStore('user', () => {
|
|||||||
return avatarUrl.value
|
return avatarUrl.value
|
||||||
}
|
}
|
||||||
|
|
||||||
return { userInfo, loading, nickname, avatarUrl, fetchUserInfo, updateProfile, uploadAvatar }
|
return { userInfo, nickname, avatarUrl, fetchUserInfo, updateProfile, uploadAvatar }
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -86,45 +86,6 @@ router.get('/', async (req: AuthRequest, res: Response) => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
/** 获取群组详情 + 成员列表 */
|
|
||||||
router.get('/:id', async (req: AuthRequest, res: Response) => {
|
|
||||||
try {
|
|
||||||
// 验证用户是否是群组成员
|
|
||||||
const [memberCheck] = await pool.query(
|
|
||||||
'SELECT role FROM group_members WHERE group_id = ? AND user_id = ?',
|
|
||||||
[req.params.id, req.userId]
|
|
||||||
)
|
|
||||||
if ((memberCheck as any[]).length === 0) {
|
|
||||||
return res.status(403).json({ code: 40300, message: '无权访问此群组' })
|
|
||||||
}
|
|
||||||
|
|
||||||
const [groupRows] = await pool.query(
|
|
||||||
'SELECT id, name, invite_code, created_by, created_at FROM `groups` WHERE id = ?',
|
|
||||||
[req.params.id]
|
|
||||||
)
|
|
||||||
const group = (groupRows as any[])[0]
|
|
||||||
if (!group) {
|
|
||||||
return res.status(404).json({ code: 40400, message: '群组不存在' })
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取成员列表
|
|
||||||
const [members] = await pool.query(
|
|
||||||
`SELECT gm.user_id, gm.role, gm.nickname, gm.created_at as joined_at,
|
|
||||||
u.nickname as user_nickname, u.avatar_url
|
|
||||||
FROM group_members gm
|
|
||||||
LEFT JOIN users u ON gm.user_id = u.id
|
|
||||||
WHERE gm.group_id = ?
|
|
||||||
ORDER BY gm.role DESC, gm.created_at ASC`,
|
|
||||||
[req.params.id]
|
|
||||||
)
|
|
||||||
|
|
||||||
res.json({ code: 0, data: { ...group, members } })
|
|
||||||
} catch (err) {
|
|
||||||
console.error('[Group] GET by ID error:', err)
|
|
||||||
res.status(500).json({ code: 50000, message: '服务器错误' })
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
/** 通过邀请码加入群组 */
|
/** 通过邀请码加入群组 */
|
||||||
router.post('/join', async (req: AuthRequest, res: Response) => {
|
router.post('/join', async (req: AuthRequest, res: Response) => {
|
||||||
try {
|
try {
|
||||||
|
|||||||
Reference in New Issue
Block a user