diff --git a/client/src/api/group.ts b/client/src/api/group.ts index 48212fd..2846a88 100644 --- a/client/src/api/group.ts +++ b/client/src/api/group.ts @@ -12,21 +12,6 @@ export interface Group { 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 }) { return request<{ id: number; invite_code: string }>({ url: '/groups', method: 'POST', data }) @@ -37,11 +22,6 @@ export function getGroups() { return request({ url: '/groups' }) } -/** 获取群组详情 */ -export function getGroupDetail(id: number) { - return request({ url: `/groups/${id}` }) -} - /** 加入群组 */ export function joinGroup(invite_code: string) { return request<{ group_id: number; name: string }>({ url: '/groups/join', method: 'POST', data: { invite_code } }) diff --git a/client/src/api/index.ts b/client/src/api/index.ts deleted file mode 100644 index 1243ea8..0000000 --- a/client/src/api/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -export * from './auth' -export * from './transaction' -export * from './category' -export * from './budget' -export * from './stats' diff --git a/client/src/pages/budget/index.vue b/client/src/pages/budget/index.vue index 959b7f8..15d11d8 100644 --- a/client/src/pages/budget/index.vue +++ b/client/src/pages/budget/index.vue @@ -77,8 +77,8 @@ import { ref, onMounted } from 'vue' import { onShow } from '@dcloudio/uni-app' import { useBudgetStore } from '@/stores/budget' -import { useStatsStore } from '@/stores/stats' import { useGroupStore } from '@/stores/group' +import { getOverview } from '@/api/stats' import { waitForReady } from '@/utils/app-ready' import { formatAmount, getCurrentMonth } from '@/utils/format' import { statusBarHeight } from '@/utils/system' @@ -87,7 +87,6 @@ import Numpad from '@/components/Numpad/Numpad.vue' import Icon from '@/components/Icon/Icon.vue' const budgetStore = useBudgetStore() -const statsStore = useStatsStore() const groupStore = useGroupStore() const currentMonth = getCurrentMonth() @@ -106,7 +105,6 @@ function formatPreset(val: number) { function syncBudgetData() { budget.value = budgetStore.budget - monthExpense.value = statsStore.overview.expense // 群组模式用 myAmount(个人预算),个人模式用 amount budgetAmount.value = budget.value ? (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 () => { await waitForReady() loading.value = true try { await Promise.all([ budgetStore.fetchBudget(currentMonth), - statsStore.fetchOverview(currentMonth) + fetchMyExpense() ]) syncBudgetData() } catch (e) { @@ -139,7 +143,7 @@ onShow(async () => { try { await Promise.all([ budgetStore.fetchBudget(currentMonth), - statsStore.fetchOverview(currentMonth) + fetchMyExpense() ]) syncBudgetData() } catch {} diff --git a/client/src/pages/stats/index.vue b/client/src/pages/stats/index.vue index cc9b128..b2ad535 100644 --- a/client/src/pages/stats/index.vue +++ b/client/src/pages/stats/index.vue @@ -236,7 +236,7 @@ onShow(() => { // Only re-fetch category/trend on tab switch; re-fetch all on month change watch(currentMonth, () => loadData()) -watch(tabType, () => loadTabData()) +watch(tabType, () => loadData(true)) async function loadData(silent = false) { 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() { try { const [y, m] = currentMonth.value.split('-').map(Number) diff --git a/client/src/stores/budget.ts b/client/src/stores/budget.ts index 7fa3655..e3faf14 100644 --- a/client/src/stores/budget.ts +++ b/client/src/stores/budget.ts @@ -4,24 +4,12 @@ import * as api from '@/api/budget' import { getCurrentMonth } from '@/utils/format' import { useGroupStore } from '@/stores/group' -// 重新导出类型以保持向后兼容 -export type Budget = api.Budget - export const useBudgetStore = defineStore('budget', () => { const budget = ref(null) - const loading = ref(false) async function fetchBudget(month?: string) { - loading.value = true - try { - const groupStore = useGroupStore() - budget.value = await api.getBudget(month || getCurrentMonth(), groupStore.currentGroupId) - } catch (err) { - budget.value = null - throw err - } finally { - loading.value = false - } + const groupStore = useGroupStore() + budget.value = await api.getBudget(month || getCurrentMonth(), groupStore.currentGroupId) } async function setBudget(amount: number, month?: string) { @@ -29,5 +17,5 @@ export const useBudgetStore = defineStore('budget', () => { await fetchBudget(month) } - return { budget, loading, fetchBudget, setBudget } + return { budget, fetchBudget, setBudget } }) diff --git a/client/src/stores/category.ts b/client/src/stores/category.ts index 04e13fd..e379d21 100644 --- a/client/src/stores/category.ts +++ b/client/src/stores/category.ts @@ -7,17 +7,9 @@ export type Category = api.Category export const useCategoryStore = defineStore('category', () => { const categories = ref([]) - const loading = ref(false) async function fetchCategories() { - loading.value = true - try { - categories.value = await api.getCategories() - } catch { - categories.value = [] - } finally { - loading.value = false - } + categories.value = await api.getCategories() } function getByType(type: 'expense' | 'income') { @@ -53,5 +45,5 @@ export const useCategoryStore = defineStore('category', () => { await fetchCategories() } - return { categories, loading, fetchCategories, getByType, getById, addCategory, updateCategory, deleteCategory, migrateCategory, sortCategories } + return { categories, fetchCategories, getByType, getById, addCategory, updateCategory, deleteCategory, migrateCategory, sortCategories } }) diff --git a/client/src/stores/stats.ts b/client/src/stores/stats.ts index cd56412..ecce02d 100644 --- a/client/src/stores/stats.ts +++ b/client/src/stores/stats.ts @@ -4,70 +4,54 @@ import * as api from '@/api/stats' import { getCurrentMonth } from '@/utils/format' import { useGroupStore } from '@/stores/group' -// 重新导出类型以保持向后兼容 -export type Overview = api.Overview -export type CategoryStat = api.CategoryStat -export type TrendPoint = api.TrendPoint +/** 默认概览数据,避免模板中访问属性报错 */ +const EMPTY_OVERVIEW: api.Overview = { + expense: 0, income: 0, expenseCount: 0, incomeCount: 0, + count: 0, daily: 0, dailyIncome: 0 +} export const useStatsStore = defineStore('stats', () => { - const overview = ref({ expense: 0, income: 0, expenseCount: 0, incomeCount: 0, count: 0, daily: 0, dailyIncome: 0 }) + const overview = ref({ ...EMPTY_OVERVIEW }) const categoryStats = ref([]) const trendData = ref([]) - const loading = ref(false) + + /** 获取当前 group_id,统一收口 */ + function getGroupId() { + return useGroupStore().currentGroupId + } async function fetchOverview(month?: string) { - loading.value = true - try { - const groupStore = useGroupStore() - const data = await api.getOverview({ - month: month || getCurrentMonth(), - group_id: groupStore.currentGroupId - }) - overview.value = { - expense: Number(data?.expense) || 0, - income: Number(data?.income) || 0, - expenseCount: Number(data?.expenseCount) || 0, - incomeCount: Number(data?.incomeCount) || 0, - count: Number(data?.count) || 0, - daily: Number(data?.daily) || 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 + const data = await api.getOverview({ + month: month || getCurrentMonth(), + group_id: getGroupId() + }) + overview.value = { + expense: Number(data?.expense) || 0, + income: Number(data?.income) || 0, + expenseCount: Number(data?.expenseCount) || 0, + incomeCount: Number(data?.incomeCount) || 0, + count: Number(data?.count) || 0, + daily: Number(data?.daily) || 0, + dailyIncome: Number(data?.dailyIncome) || 0 } } async function fetchCategoryStats(month?: string, type: string = 'expense') { - try { - const groupStore = useGroupStore() - const data = await api.getCategoryStats(month || getCurrentMonth(), type, groupStore.currentGroupId) - categoryStats.value = Array.isArray(data) ? data.map(item => ({ - ...item, - amount: Number(item.amount) || 0, - count: Number(item.count) || 0 - })) : [] - } catch (err) { - categoryStats.value = [] - throw err - } + const data = await api.getCategoryStats(month || getCurrentMonth(), type, getGroupId()) + categoryStats.value = Array.isArray(data) ? data.map(item => ({ + ...item, + amount: Number(item.amount) || 0, + count: Number(item.count) || 0 + })) : [] } async function fetchTrend(month?: string, type: string = 'expense') { - try { - const groupStore = useGroupStore() - const data = await api.getTrend(month || getCurrentMonth(), type, groupStore.currentGroupId) - trendData.value = Array.isArray(data) ? data.map(item => ({ - ...item, - amount: Number(item.amount) || 0 - })) : [] - } catch (err) { - trendData.value = [] - throw err - } + const data = await api.getTrend(month || getCurrentMonth(), type, getGroupId()) + trendData.value = Array.isArray(data) ? data.map(item => ({ + ...item, + amount: Number(item.amount) || 0 + })) : [] } - return { overview, categoryStats, trendData, loading, fetchOverview, fetchCategoryStats, fetchTrend } + return { overview, categoryStats, trendData, fetchOverview, fetchCategoryStats, fetchTrend } }) diff --git a/client/src/stores/transaction.ts b/client/src/stores/transaction.ts index bd212b5..36a365e 100644 --- a/client/src/stores/transaction.ts +++ b/client/src/stores/transaction.ts @@ -7,7 +7,6 @@ export const useTransactionStore = defineStore('transaction', () => { const transactions = ref([]) const total = ref(0) const loading = ref(false) - const currentPage = ref(1) async function fetchTransactions(params?: api.TransactionParams) { 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 } }) diff --git a/client/src/stores/user.ts b/client/src/stores/user.ts index 9d9ac4c..c226cb4 100644 --- a/client/src/stores/user.ts +++ b/client/src/stores/user.ts @@ -5,7 +5,6 @@ import { API_BASE } from '@/config' export const useUserStore = defineStore('user', () => { const userInfo = ref(null) - const loading = ref(false) const nickname = computed(() => { return userInfo.value?.nickname || uni.getStorageSync('xc:nickname') || '小菜' @@ -21,18 +20,10 @@ export const useUserStore = defineStore('user', () => { }) async function fetchUserInfo() { - loading.value = true - try { - userInfo.value = await api.getUserInfo() - if (userInfo.value) { - uni.setStorageSync('xc:nickname', userInfo.value.nickname) - uni.setStorageSync('xc:avatar_url', userInfo.value.avatar_url) - } - } catch (e) { - console.error('[UserStore] fetchUserInfo error:', e) - throw e - } finally { - loading.value = false + userInfo.value = await api.getUserInfo() + if (userInfo.value) { + uni.setStorageSync('xc:nickname', userInfo.value.nickname) + uni.setStorageSync('xc:avatar_url', userInfo.value.avatar_url) } } @@ -49,5 +40,5 @@ export const useUserStore = defineStore('user', () => { return avatarUrl.value } - return { userInfo, loading, nickname, avatarUrl, fetchUserInfo, updateProfile, uploadAvatar } + return { userInfo, nickname, avatarUrl, fetchUserInfo, updateProfile, uploadAvatar } }) diff --git a/server/src/routes/group.ts b/server/src/routes/group.ts index c7cb8a1..fd8006a 100644 --- a/server/src/routes/group.ts +++ b/server/src/routes/group.ts @@ -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) => { try {