diff --git a/resources/mobile/androidPrivacy.json b/resources/mobile/androidPrivacy.json new file mode 100644 index 0000000..12df862 --- /dev/null +++ b/resources/mobile/androidPrivacy.json @@ -0,0 +1,3 @@ +{ + "prompt": "template" +} diff --git a/resources/mobile/manifest.json b/resources/mobile/manifest.json index ae2d977..32f7790 100644 --- a/resources/mobile/manifest.json +++ b/resources/mobile/manifest.json @@ -1,7 +1,7 @@ { - "name" : "mobile", + "name" : "A记账", "appid" : "__UNI__F0A4488", - "description" : "", + "description" : "记账APP", "versionName" : "1.0.0", "versionCode" : "100", "transformPx" : false, @@ -17,7 +17,10 @@ "delay" : 0 }, /* 模块配置 */ - "modules" : {}, + "modules" : { + "Camera" : {}, + "Push" : {} + }, /* 应用发布信息 */ "distribute" : { /* android打包配置 */ @@ -38,12 +41,20 @@ "", "", "" - ] + ], + "abiFilters" : [ "armeabi-v7a", "arm64-v8a", "x86" ] }, /* ios打包配置 */ - "ios" : {}, + "ios" : { + "dSYMs" : false + }, /* SDK配置 */ - "sdkConfigs" : {} + "sdkConfigs" : { + "push" : {} + }, + "splashscreen" : { + "useOriginalMsgbox" : true + } } }, /* 快应用特有相关 */ @@ -68,5 +79,10 @@ "uniStatistics" : { "enable" : false }, - "vueVersion" : "3" + "vueVersion" : "3", + "app-harmony" : { + "distribute" : { + "bundleName" : "cn.tensent.aacount" + } + } } diff --git a/resources/mobile/pages/account/bill/add.vue b/resources/mobile/pages/account/bill/add.vue index 43327f5..6969c38 100644 --- a/resources/mobile/pages/account/bill/add.vue +++ b/resources/mobile/pages/account/bill/add.vue @@ -63,6 +63,25 @@ + + + 支付方式 + + + + + + {{ payment.name }} + + + + 备注 @@ -111,9 +130,17 @@ export default { type: 'expense', amount: '', categoryId: '', + paymentMethod: 1, remark: '', date: this.getTodayDate() }, + paymentMethods: [ + { id: 1, name: '微信', icon: 'weixin', color: '#07C160' }, + { id: 2, name: '支付宝', icon: 'redo', color: '#1677FF' }, + { id: 3, name: '银行卡', icon: 'bankcard', color: '#FF6B6B' }, + { id: 4, name: '现金', icon: 'wallet', color: '#FFA500' }, + { id: 5, name: '其他', icon: 'more', color: '#999999' } + ], expenseCategories: [ { id: 1, name: '餐饮', icon: '🍜', color: '#FF6B6B' }, { id: 2, name: '交通', icon: '🚗', color: '#4ECDC4' }, @@ -133,6 +160,9 @@ export default { ] } }, + selectPaymentMethod(payment) { + this.form.paymentMethod = payment.id + }, computed: { currentCategories() { return this.form.type === 'expense' ? this.expenseCategories : this.incomeCategories @@ -171,6 +201,7 @@ export default { this.form.type = bill.type this.form.amount = bill.amount.toString() this.form.categoryId = this.getCategoryIdByCategoryName(bill.category, bill.type) + this.form.paymentMethod = this.getPaymentMethodId(bill.payment_method || '微信') this.form.remark = bill.remark || '' this.form.date = bill.bill_date } @@ -207,6 +238,26 @@ export default { } return map[categoryName] || 8 } + }, + getPaymentMethodId(paymentMethodName) { + const map = { + '微信': 1, + '支付宝': 2, + '银行卡': 3, + '现金': 4, + '其他': 5 + } + return map[paymentMethodName] || 1 + }, + getPaymentMethodName(paymentMethodId) { + const map = { + 1: '微信', + 2: '支付宝', + 3: '银行卡', + 4: '现金', + 5: '其他' + } + return map[paymentMethodId] || '微信' }, getTodayDate() { const today = new Date() @@ -279,6 +330,12 @@ export default { params.id = this.billId } + // 获取支付方式名称 + const paymentMethodName = this.getPaymentMethodName(this.form.paymentMethod) + + // 添加支付方式到参数 + params.payment_method = paymentMethodName + const res = await this.$api.bill[apiUrl].post(params) if (res && res.code === 1) { @@ -449,6 +506,49 @@ export default { } } +.payment-grid { + display: grid; + grid-template-columns: repeat(5, 1fr); + gap: 16rpx; + + .payment-item { + display: flex; + flex-direction: column; + align-items: center; + padding: 16rpx 0; + + .payment-icon { + width: 64rpx; + height: 64rpx; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + margin-bottom: 10rpx; + opacity: 0.6; + transition: all 0.3s; + } + + .payment-name { + font-size: 22rpx; + color: #666; + } + + &.active { + .payment-icon { + opacity: 1; + transform: scale(1.1); + box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.15); + } + + .payment-name { + color: #667eea; + font-weight: bold; + } + } + } +} + .textarea-wrapper { position: relative; diff --git a/resources/mobile/pages/account/bill/index.vue b/resources/mobile/pages/account/bill/index.vue index a457506..518ef3a 100644 --- a/resources/mobile/pages/account/bill/index.vue +++ b/resources/mobile/pages/account/bill/index.vue @@ -1,27 +1,14 @@ @@ -121,6 +131,7 @@ export default { loading: false, currentMonth: this.getCurrentMonth(), billList: [], + currentBill: null, monthExpense: 0, monthIncome: 0, dataType: 'family', // family 或 personal @@ -150,6 +161,13 @@ export default { 103: { name: '投资', icon: '📈', color: '#3498DB' }, 104: { name: '兼职', icon: '💼', color: '#9B59B6' }, 105: { name: '其他', icon: '💎', color: '#1ABC9C' } + }, + paymentMap: { + '微信': { name: '微信', icon: 'weixin', color: '#07C160' }, + '支付宝': { name: '支付宝', icon: 'redo', color: '#1677FF' }, + '银行卡': { name: '银行卡', icon: 'bankcard', color: '#FF6B6B' }, + '现金': { name: '现金', icon: 'wallet', color: '#FFA500' }, + '其他': { name: '其他', icon: 'more', color: '#999999' } } } }, @@ -157,6 +175,14 @@ export default { hasFamily() { return this.$store.getters.hasFamily }, + currentUserId() { + return this.$store.state.user.userInfo?.id + }, + canDeleteBill() { + if (!this.currentBill || !this.currentUserId) return false + // 只能删除自己创建的账单 + return this.currentBill.user_id === this.currentUserId + }, groupedBills() { const groups = {} this.billList.forEach(bill => { @@ -287,11 +313,81 @@ export default { }) }, editBill(bill) { - // 跳转到编辑页面 + this.currentBill = bill + // 如果是自己的账单,显示操作菜单 + if (bill.user_id === this.currentUserId) { + this.$refs.actionPopup.open() + } else { + // 不是自己的账单,直接查看详情或提示 + uni.navigateTo({ + url: `/pages/account/bill/add?id=${bill.id}` + }) + } + }, + closeActionSheet() { + this.$refs.actionPopup.close() + this.currentBill = null + }, + editCurrentBill() { + if (!this.currentBill) return + this.closeActionSheet() uni.navigateTo({ - url: `/pages/account/bill/add?id=${bill.id}` + url: `/pages/account/bill/add?id=${this.currentBill.id}` }) }, + deleteCurrentBill() { + if (!this.currentBill) return + + uni.showModal({ + title: '删除确认', + content: '确定要删除这条账单记录吗?删除后无法恢复。', + confirmColor: '#FF6B6B', + success: async (res) => { + if (res.confirm) { + await this.performDelete() + } + } + }) + }, + async performDelete() { + if (!this.currentBill) return + + this.loading = true + try { + const res = await this.$api.bill.delete.post({ + id: this.currentBill.id + }) + + if (res && res.code === 1) { + uni.showToast({ + title: '删除成功', + icon: 'success', + duration: 1500 + }) + + // 关闭弹窗 + this.closeActionSheet() + + // 刷新列表 + setTimeout(() => { + this.loadBillList(true) + }, 1500) + } else { + uni.showToast({ + title: res?.message || '删除失败', + icon: 'none' + }) + } + } catch (error) { + console.error('删除账单失败', error) + uni.showToast({ + title: error?.message || '删除失败,请重试', + icon: 'none' + }) + } finally { + this.loading = false + } + }, getCategoryName(categoryId) { return this.categoryMap[categoryId]?.name || '未知' }, @@ -301,6 +397,15 @@ export default { getCategoryColor(categoryId) { return this.categoryMap[categoryId]?.color || '#BDC3C7' }, + getPaymentName(paymentMethod) { + return this.paymentMap[paymentMethod]?.name || '其他' + }, + getPaymentIcon(paymentMethod) { + return this.paymentMap[paymentMethod]?.icon || 'more' + }, + getPaymentColor(paymentMethod) { + return this.paymentMap[paymentMethod]?.color || '#999999' + }, formatDate(date, fmt) { return tool.dateFormat(date, fmt) } @@ -523,9 +628,34 @@ export default { margin-bottom: 8rpx; } - .bill-remark { - font-size: 24rpx; - color: #999; + .bill-detail { + display: flex; + align-items: center; + gap: 16rpx; + + .bill-payment { + display: flex; + align-items: center; + background: rgba(0, 0, 0, 0.05); + border-radius: 20rpx; + padding: 4rpx 12rpx; + font-size: 20rpx; + color: #666; + + uni-icons { + margin-right: 6rpx; + } + + text { + color: #666; + } + } + + .bill-remark { + font-size: 24rpx; + color: #999; + flex: 1; + } } } @@ -558,4 +688,86 @@ export default { box-shadow: 0 8rpx 24rpx rgba(102, 126, 234, 0.5); z-index: 100; } + +.action-sheet { + background: #f8f8f8; + border-radius: 24rpx 24rpx 0 0; + overflow: hidden; + + .action-header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 30rpx; + border-bottom: 1rpx solid #eee; + + .action-title { + font-size: 32rpx; + font-weight: bold; + color: #333; + } + } + + .action-list { + padding: 20rpx 0; + + .action-item { + display: flex; + align-items: center; + padding: 30rpx 40rpx; + background: #fff; + border-bottom: 1rpx solid #f5f5f5; + transition: all 0.3s; + + &:active { + background: #f8f8f8; + } + + uni-icons { + margin-right: 20rpx; + } + + text { + font-size: 28rpx; + color: #333; + } + + &.edit { + uni-icons { + color: #667eea; + } + } + + &.delete { + uni-icons { + color: #FF6B6B; + } + + text { + color: #FF6B6B; + } + } + } + } + + .action-cancel { + display: flex; + align-items: center; + justify-content: center; + padding: 30rpx; + background: #fff; + margin-top: 10rpx; + transition: all 0.3s; + + &:active { + background: #f8f8f8; + } + + text { + font-size: 28rpx; + color: #666; + font-weight: bold; + } + } +} diff --git a/resources/mobile/utils/request.js b/resources/mobile/utils/request.js index 1395c22..b3efb9a 100644 --- a/resources/mobile/utils/request.js +++ b/resources/mobile/utils/request.js @@ -1,6 +1,6 @@ import Request from 'luch-request' import sysConfig from '@/config' -import store from '@/store' +import tool from '@/utils/tool' const request = new Request() @@ -33,8 +33,8 @@ request.setConfig((config) => { }) request.interceptors.request.use((config) => { - // 从本地存储获取 token - const token = store.state.user.token || '' + // 从本地存储获取 token(避免循环依赖) + const token = tool.data.get('token') || '' config.header.Authorization = `Bearer ${token}` return config @@ -53,8 +53,14 @@ request.interceptors.response.use((response) => { duration: 1500 }) setTimeout(() => { - store.dispatch('userLogout') - }, 100) + // 清除本地存储并跳转登录页 + uni.removeStorageSync('token') + uni.removeStorageSync('is-login') + uni.removeStorageSync('userInfo') + uni.reLaunch({ + url: '/pages/ucenter/login/index' + }) + }, 1500) return Promise.reject(response.data) } @@ -64,7 +70,13 @@ request.interceptors.response.use((response) => { const errorMessage = HTTP_ERROR_MESSAGES[statusCode] || errMsg || '请求失败,请稍后重试' if (statusCode === 401) { - store.dispatch('userLogout') + // 清除本地存储并跳转登录页 + uni.removeStorageSync('token') + uni.removeStorageSync('is-login') + uni.removeStorageSync('userInfo') + uni.reLaunch({ + url: '/pages/ucenter/login/index' + }) } return Promise.reject(error)