This commit is contained in:
2026-01-18 20:17:59 +08:00
parent 7e05f5e76f
commit de9c14f070
23 changed files with 1825 additions and 71 deletions

View File

@@ -33,6 +33,7 @@
v-model="form.amount"
placeholder="0.00"
placeholder-style="color: #ddd"
@input="onAmountInput"
/>
</view>
</view>
@@ -145,6 +146,16 @@ export default {
onDateChange(e) {
this.form.date = e.detail.value
},
onAmountInput(e) {
// 限制小数点后两位
let value = e.detail.value
if (value.includes('.')) {
const parts = value.split('.')
if (parts[1] && parts[1].length > 2) {
this.form.amount = `${parts[0]}.${parts[1].substring(0, 2)}`
}
}
},
async handleSubmit() {
// 验证
if (!this.form.amount || parseFloat(this.form.amount) <= 0) {
@@ -163,6 +174,14 @@ export default {
return
}
if (!this.form.date) {
uni.showToast({
title: '请选择日期',
icon: 'none'
})
return
}
this.loading = true
try {
@@ -170,11 +189,11 @@ export default {
type: this.form.type,
amount: parseFloat(this.form.amount),
category_id: this.form.categoryId,
remark: this.form.remark,
remark: this.form.remark || undefined,
date: this.form.date
})
if (res.code === 200) {
if (res && res.code === 1) {
uni.showToast({
title: '保存成功',
icon: 'success'
@@ -184,14 +203,14 @@ export default {
}, 1500)
} else {
uni.showToast({
title: res.message || '保存失败',
title: res?.message || '保存失败',
icon: 'none'
})
}
} catch (error) {
console.error('保存账单失败', error)
uni.showToast({
title: '保存失败,请重试',
title: error?.message || '保存失败,请重试',
icon: 'none'
})
} finally {

View File

@@ -48,7 +48,7 @@
<view v-else class="bill-group" v-for="(group, date) in groupedBills" :key="date">
<view class="group-header">
<text class="group-date">{{ date }}</text>
<text class="group-date">{{ group.formattedDate }}</text>
<text class="group-amount">支出: ¥{{ group.expense.toFixed(2) }} 收入: ¥{{ group.income.toFixed(2) }}</text>
</view>
@@ -76,6 +76,8 @@
</template>
<script>
import tool from '@/utils/tool'
export default {
data() {
return {
@@ -112,7 +114,8 @@ export default {
groups[date] = {
bills: [],
expense: 0,
income: 0
income: 0,
formattedDate: this.formatDate(date, 'MM月dd日')
}
}
groups[date].bills.push(bill)
@@ -156,7 +159,7 @@ export default {
month: parseInt(month)
})
if (res.code === 200) {
if (res && res.code === 1) {
this.billList = res.data?.list || []
this.monthExpense = res.data?.month_expense || 0
this.monthIncome = res.data?.month_income || 0
@@ -164,7 +167,7 @@ export default {
} catch (error) {
console.error('加载账单列表失败', error)
uni.showToast({
title: '加载失败,请重试',
title: error?.message || '加载失败,请重试',
icon: 'none'
})
} finally {
@@ -191,6 +194,9 @@ export default {
},
getCategoryColor(categoryId) {
return this.categoryMap[categoryId]?.color || '#BDC3C7'
},
formatDate(date, fmt) {
return tool.dateFormat(date, fmt)
}
}
}

View File

@@ -122,6 +122,8 @@
</template>
<script>
import tool from '@/utils/tool'
export default {
data() {
return {
@@ -173,14 +175,19 @@ export default {
this.loading = true
try {
const [year, month] = this.currentMonth.split('-')
console.log('加载统计数据 - 年:', year, '月:', month)
// 获取统计概览
const overviewRes = await this.$api.statistics.overview.get({
year: parseInt(year),
month: parseInt(month)
})
if (overviewRes.code === 200) {
console.log('概览接口返回:', overviewRes)
if (overviewRes && overviewRes.code === 1) {
this.overview = overviewRes.data || { income: 0, expense: 0, balance: 0 }
console.log('概览数据:', this.overview)
} else {
console.error('概览接口返回错误:', overviewRes?.message)
}
// 获取分类统计
@@ -188,8 +195,12 @@ export default {
year: parseInt(year),
month: parseInt(month)
})
if (categoryRes.code === 200) {
console.log('分类接口返回:', categoryRes)
if (categoryRes && categoryRes.code === 1) {
this.processCategoryData(categoryRes.data || [])
console.log('分类数据:', this.categoryList)
} else {
console.error('分类接口返回错误:', categoryRes?.message)
}
// 获取收支趋势
@@ -197,13 +208,17 @@ export default {
year: parseInt(year),
month: parseInt(month)
})
if (trendRes.code === 200) {
console.log('趋势接口返回:', trendRes)
if (trendRes && trendRes.code === 1) {
this.processTrendData(trendRes.data || [])
console.log('趋势数据:', this.trendList)
} else {
console.error('趋势接口返回错误:', trendRes?.message)
}
} catch (error) {
console.error('加载统计数据失败', error)
uni.showToast({
title: '加载失败,请重试',
title: error?.message || '加载失败,请重试',
icon: 'none'
})
} finally {
@@ -211,6 +226,7 @@ export default {
}
},
processCategoryData(data) {
console.log('处理分类数据:', data)
let total = 0
const list = data.map(item => {
total += item.amount
@@ -226,8 +242,10 @@ export default {
percent: total > 0 ? Math.round((item.amount / total) * 100) : 0
}
}).sort((a, b) => b.amount - a.amount)
console.log('处理后的分类列表:', this.categoryList)
},
processTrendData(data) {
console.log('处理趋势数据:', data)
// 找出最大值用于计算高度百分比
let maxIncome = 0
let maxExpense = 0
@@ -238,8 +256,7 @@ export default {
})
this.trendList = data.map(item => {
const date = new Date(item.date)
const dateLabel = `${date.getMonth() + 1}/${date.getDate()}`
const dateLabel = this.formatDate(item.date, 'MM/dd')
return {
date: item.date,
@@ -250,6 +267,10 @@ export default {
expenseHeight: maxExpense > 0 ? (item.expense / maxExpense) * 100 : 0
}
})
console.log('处理后的趋势列表:', this.trendList)
},
formatDate(date, fmt) {
return tool.dateFormat(date, fmt)
}
}
}

View File

@@ -63,8 +63,8 @@ export default {
this.loading = true
try {
const res = await this.$store.dispatch('family/createFamily', this.familyName.trim())
if (res.code === 200) {
const res = await this.$store.dispatch('createFamily', this.familyName.trim())
if (res && res.code === 1) {
uni.showToast({
title: '创建成功',
icon: 'success'
@@ -74,14 +74,14 @@ export default {
}, 1500)
} else {
uni.showToast({
title: res.message || '创建失败',
title: res?.message || '创建失败',
icon: 'none'
})
}
} catch (error) {
console.error('创建家庭失败', error)
uni.showToast({
title: '创建失败,请重试',
title: error?.message || '创建失败,请重试',
icon: 'none'
})
} finally {

View File

@@ -120,7 +120,7 @@ export default {
if (!this.$store || !this.$store.getters) {
return false
}
return this.$store.getters['family/hasFamily'] || false
return this.$store.getters.hasFamily || false
},
isOwner() {
if (!this.$store || !this.$store.state || !this.$store.state.family) {
@@ -146,7 +146,7 @@ export default {
try {
// 获取家庭信息
const familyRes = await this.$api.family.info.get()
if (familyRes.code === 200) {
if (familyRes && familyRes.code === 1) {
this.familyInfo = familyRes.data
this.$store.commit('family/setFamilyInfo', familyRes.data)
}
@@ -154,14 +154,14 @@ export default {
// 获取邀请码(仅家主)
if (this.isOwner) {
const codeRes = await this.$api.family.inviteCode.get()
if (codeRes.code === 200) {
if (codeRes && codeRes.code === 1) {
this.inviteCode = codeRes.data.invite_code
}
}
// 获取家庭成员列表
const membersRes = await this.$api.family.members.get()
if (membersRes.code === 200) {
if (membersRes && membersRes.code === 1) {
this.memberList = membersRes.data || []
}
} catch (error) {
@@ -194,17 +194,22 @@ export default {
this.regenerating = true
try {
const res = await this.$api.family.regenerateInviteCode.post()
if (res.code === 200) {
if (res && res.code === 1) {
this.inviteCode = res.data.invite_code
uni.showToast({
title: '邀请码已更新',
icon: 'success'
})
} else {
uni.showToast({
title: res?.message || '操作失败',
icon: 'none'
})
}
} catch (error) {
console.error('重新生成邀请码失败', error)
uni.showToast({
title: '操作失败,请重试',
title: error?.message || '操作失败,请重试',
icon: 'none'
})
} finally {
@@ -224,17 +229,22 @@ export default {
const result = await this.$api.family.removeMember.post({
user_id: member.id
})
if (result.code === 200) {
if (result && result.code === 1) {
uni.showToast({
title: '移除成功',
icon: 'success'
})
this.loadFamilyData()
} else {
uni.showToast({
title: result?.message || '操作失败',
icon: 'none'
})
}
} catch (error) {
console.error('移除成员失败', error)
uni.showToast({
title: '操作失败,请重试',
title: error?.message || '操作失败,请重试',
icon: 'none'
})
}
@@ -255,9 +265,9 @@ export default {
content: '退出家庭后,您将无法查看和记录家庭账单,确定要退出吗?',
success: async (res) => {
if (res.confirm) {
try {
const result = await this.$store.dispatch('family/leaveFamily')
if (result.code === 200) {
try {
const result = await this.$store.dispatch('leaveFamily')
if (result && result.code === 1) {
uni.showToast({
title: '已退出家庭',
icon: 'success'
@@ -265,11 +275,16 @@ export default {
setTimeout(() => {
uni.navigateBack()
}, 1500)
} else {
uni.showToast({
title: result?.message || '操作失败',
icon: 'none'
})
}
} catch (error) {
console.error('退出家庭失败', error)
uni.showToast({
title: '操作失败,请重试',
title: error?.message || '操作失败,请重试',
icon: 'none'
})
}

View File

@@ -70,8 +70,8 @@ export default {
this.loading = true
try {
const res = await this.$store.dispatch('family/joinFamily', this.inviteCode.trim())
if (res.code === 200) {
const res = await this.$store.dispatch('joinFamily', this.inviteCode.trim())
if (res && res.code === 1) {
uni.showToast({
title: '加入成功',
icon: 'success'
@@ -81,14 +81,14 @@ export default {
}, 1500)
} else {
uni.showToast({
title: res.message || '加入失败',
title: res?.message || '加入失败',
icon: 'none'
})
}
} catch (error) {
console.error('加入家庭失败', error)
uni.showToast({
title: '加入失败,请重试',
title: error?.message || '加入失败,请重试',
icon: 'none'
})
} finally {

View File

@@ -31,7 +31,7 @@
</view>
<view class="overview-data">
<text class="overview-label">收入</text>
<text class="overview-amount">¥0.00</text>
<text class="overview-amount">¥{{ overview.income.toFixed(2) }}</text>
</view>
</view>
<view class="overview-divider"></view>
@@ -41,7 +41,7 @@
</view>
<view class="overview-data">
<text class="overview-label">支出</text>
<text class="overview-amount">¥0.00</text>
<text class="overview-amount">¥{{ overview.expense.toFixed(2) }}</text>
</view>
</view>
</view>
@@ -81,7 +81,12 @@
<uni-icons type="right" size="14" color="#667eea"></uni-icons>
</view>
</view>
<view class="empty-list">
<view v-if="loading" class="loading-state">
<uni-load-more status="loading"></uni-load-more>
</view>
<view v-else-if="recentBills.length === 0" class="empty-list">
<view class="empty-icon">
<uni-icons type="list" size="80" color="#ddd"></uni-icons>
</view>
@@ -91,6 +96,21 @@
<text>记一笔</text>
</button>
</view>
<view v-else class="bill-list">
<view class="bill-item" v-for="bill in recentBills" :key="bill.id" @tap="viewBill(bill)">
<view class="bill-icon" :style="{background: getCategoryColor(bill.category_id)}">
<text>{{ getCategoryIcon(bill.category_id) }}</text>
</view>
<view class="bill-info">
<text class="bill-category">{{ getCategoryName(bill.category_id) }}</text>
<text class="bill-date">{{ formatDate(bill.date, 'MM/dd') }}</text>
</view>
<view class="bill-amount" :class="bill.type">
<text>{{ bill.type === 'expense' ? '-' : '+' }}¥{{ bill.amount.toFixed(2) }}</text>
</view>
</view>
</view>
</view>
<!-- 家庭管理 -->
@@ -120,11 +140,34 @@
</template>
<script>
import tool from '@/utils/tool'
export default {
data() {
return {
currentDate: '',
currentMonth: ''
currentMonth: '',
overview: {
income: 0,
expense: 0
},
recentBills: [],
loading: false,
categoryMap: {
1: { name: '餐饮', icon: '🍜', color: '#FF6B6B' },
2: { name: '交通', icon: '🚗', color: '#4ECDC4' },
3: { name: '购物', icon: '🛒', color: '#45B7D1' },
4: { name: '娱乐', icon: '🎮', color: '#96CEB4' },
5: { name: '医疗', icon: '💊', color: '#FFEAA7' },
6: { name: '教育', icon: '📚', color: '#DDA0DD' },
7: { name: '居住', icon: '🏠', color: '#98D8C8' },
8: { name: '其他', icon: '📦', color: '#BDC3C7' },
101: { name: '工资', icon: '💰', color: '#2ECC71' },
102: { name: '奖金', icon: '🎁', color: '#E74C3C' },
103: { name: '投资', icon: '📈', color: '#3498DB' },
104: { name: '兼职', icon: '💼', color: '#9B59B6' },
105: { name: '其他', icon: '💎', color: '#1ABC9C' }
}
}
},
computed: {
@@ -132,7 +175,7 @@ export default {
if (!this.$store || !this.$store.getters) {
return false
}
return this.$store.getters['family/hasFamily'] || false
return this.$store.getters.hasFamily || false
},
userName() {
if (!this.$store || !this.$store.state || !this.$store.state.user || !this.$store.state.user.userInfo) {
@@ -143,9 +186,18 @@ export default {
},
onLoad() {
this.updateDate()
this.loadFamilyInfo()
this.loadData()
},
onShow() {
this.updateDate()
this.loadFamilyInfo()
this.loadData()
},
onPullDownRefresh() {
this.loadData().then(() => {
uni.stopPullDownRefresh()
})
},
methods: {
updateDate() {
@@ -158,6 +210,39 @@ export default {
this.currentDate = `${month}${date}${day}`
this.currentMonth = `${month}`
},
async loadFamilyInfo() {
try {
await this.$store.dispatch('getFamilyInfo')
} catch (error) {
console.error('获取家庭信息失败', error)
}
},
async loadData() {
this.loading = true
try {
const now = new Date()
const year = now.getFullYear()
const month = now.getMonth() + 1
// 并行加载数据
const [overviewRes, billsRes] = await Promise.all([
this.$api.statistics.overview.get({ year, month }),
this.$api.bill.list.get({ year, month, limit: 5 })
])
if (overviewRes && overviewRes.code === 1) {
this.overview = overviewRes.data || { income: 0, expense: 0 }
}
if (billsRes && billsRes.code === 1) {
this.recentBills = billsRes.data?.list || []
}
} catch (error) {
console.error('加载数据失败', error)
} finally {
this.loading = false
}
},
handleTabChange(path) {
console.log('Tab changed to:', path)
},
@@ -165,6 +250,24 @@ export default {
uni.navigateTo({
url: url
})
},
viewBill(bill) {
// TODO: 实现查看账单详情
uni.navigateTo({
url: `/pages/account/bill/detail?id=${bill.id}`
})
},
getCategoryName(categoryId) {
return this.categoryMap[categoryId]?.name || '未知'
},
getCategoryIcon(categoryId) {
return this.categoryMap[categoryId]?.icon || '📦'
},
getCategoryColor(categoryId) {
return this.categoryMap[categoryId]?.color || '#BDC3C7'
},
formatDate(date, fmt) {
return tool.dateFormat(date, fmt)
}
}
}
@@ -173,9 +276,8 @@ export default {
<style lang="scss" scoped>
.page-content {
padding: 30rpx;
padding-bottom: 150rpx;
padding-bottom: 100rpx;
background: linear-gradient(180deg, #F5F7FA 0%, #FFFFFF 100%);
min-height: 100vh;
position: relative;
&::before {
@@ -563,6 +665,10 @@ export default {
}
}
.loading-state {
padding: 60rpx 0;
}
.empty-list {
display: flex;
flex-direction: column;
@@ -607,6 +713,64 @@ export default {
}
}
.bill-list {
.bill-item {
display: flex;
align-items: center;
padding: 24rpx 0;
border-bottom: 2rpx solid #f5f5f5;
&:last-child {
border-bottom: none;
}
.bill-icon {
width: 60rpx;
height: 60rpx;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
margin-right: 20rpx;
text {
font-size: 28rpx;
}
}
.bill-info {
flex: 1;
display: flex;
flex-direction: column;
.bill-category {
font-size: 28rpx;
color: #333;
font-weight: bold;
margin-bottom: 6rpx;
}
.bill-date {
font-size: 22rpx;
color: #999;
}
}
.bill-amount {
font-size: 28rpx;
font-weight: bold;
&.expense {
color: #FF6B6B;
}
&.income {
color: #2ECC71;
}
}
}
}
.family-card {
display: flex;
align-items: center;

View File

@@ -178,13 +178,40 @@ export default {
},
// 加载用户统计数据
loadUserStats() {
// 这里应该调用API获取实际数据
// 暂时使用模拟数据
this.stats = {
billCount: 128,
days: 45,
familyMembers: 3
async loadUserStats() {
try {
// 获取账单总数
const billRes = await this.$api.bill.list.get({
page: 1,
limit: 1
})
if (billRes && billRes.code === 1) {
this.stats.billCount = billRes.data?.total || 0
}
// 获取家庭成员数
const membersRes = await this.$api.family.members.get()
if (membersRes && membersRes.code === 1) {
this.stats.familyMembers = membersRes.data?.length || 0
}
// 获取记账天数(计算从第一个账单到现在的天数)
const firstBillRes = await this.$api.bill.list.get({
page: 1,
limit: 1,
order: 'asc'
})
if (firstBillRes && firstBillRes.code === 1 && firstBillRes.data?.list?.length > 0) {
const firstBill = firstBillRes.data.list[0]
const firstDate = new Date(firstBill.date)
const now = new Date()
const diffTime = Math.abs(now - firstDate)
const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24))
this.stats.days = diffDays
}
} catch (error) {
console.error('加载统计数据失败', error)
// 失败时保持默认值
}
},
@@ -201,24 +228,26 @@ export default {
// 页面跳转
navigateTo(url) {
if (!this.isLogin && url !== '/pages/ucenter/login/index') {
if (!this.isLogin) {
uni.navigateTo({
url: '/pages/ucenter/login/index'
})
return
}
// 功能开发中提示
uni.showToast({
title: '功能开发中',
icon: 'none',
duration: 2000
// 直接跳转,不显示"功能开发中"
uni.navigateTo({
url: url
})
},
// 编辑个人资料
handleEditProfile() {
this.navigateTo('/pages/ucenter/profile')
uni.showToast({
title: '功能开发中',
icon: 'none',
duration: 2000
})
},
// 数据导出

View File

@@ -43,7 +43,7 @@ export default{
async getFamilyInfo({commit}){
try {
const res = await api.family.info.get()
if(res.data){
if(res && res.code === 1 && res.data){
commit('setFamilyInfo', res.data)
commit('setIsOwner', res.data.is_owner || false)
}
@@ -56,7 +56,7 @@ export default{
async createFamily({commit}, familyName){
try {
const res = await api.family.create.post({name: familyName})
if(res.data){
if(res && res.code === 1 && res.data){
commit('setFamilyInfo', res.data)
commit('setIsOwner', true)
}
@@ -69,7 +69,7 @@ export default{
async joinFamily({commit}, inviteCode){
try {
const res = await api.family.join.post({invite_code: inviteCode})
if(res.data){
if(res && res.code === 1 && res.data){
commit('setFamilyInfo', res.data)
commit('setIsOwner', res.data.is_owner || false)
}
@@ -82,7 +82,7 @@ export default{
async leaveFamily({commit}){
try {
const res = await api.family.leave.post()
if(res.code === 200){
if(res && res.code === 1){
commit('clearFamily')
}
return res