APP端更新

This commit is contained in:
2026-01-19 11:19:44 +08:00
parent 9431f395af
commit f661373824
5 changed files with 391 additions and 48 deletions

View File

@@ -0,0 +1,3 @@
{
"prompt": "template"
}

View File

@@ -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 @@
"<uses-permission android:name=\"android.permission.FLASHLIGHT\"/>",
"<uses-feature android:name=\"android.hardware.camera\"/>",
"<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>"
]
],
"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"
}
}
}

View File

@@ -63,6 +63,25 @@
</view>
</view>
<!-- 支付方式 -->
<view class="form-section">
<view class="section-title">支付方式</view>
<view class="payment-grid">
<view
v-for="payment in paymentMethods"
:key="payment.id"
class="payment-item"
:class="{active: form.paymentMethod === payment.id}"
@tap="selectPaymentMethod(payment)"
>
<view class="payment-icon" :style="{background: payment.color}">
<uni-icons :type="payment.icon" size="24" color="#fff"></uni-icons>
</view>
<text class="payment-name">{{ payment.name }}</text>
</view>
</view>
</view>
<!-- 备注 -->
<view class="form-section">
<view class="section-title">备注</view>
@@ -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;

View File

@@ -1,27 +1,14 @@
<template>
<un-pages
:show-nav-bar="true"
nav-bar-title="账单"
:show-back="false"
:show-tab-bar="true"
@tab-change="handleTabChange"
>
<un-pages :show-nav-bar="true" nav-bar-title="账单" :show-back="false" :show-tab-bar="true"
@tab-change="handleTabChange">
<view class="page-content">
<!-- 数据类型切换 -->
<view v-if="hasFamily" class="data-type-switch">
<view
class="switch-item"
:class="{active: dataType === 'family'}"
@tap="switchDataType('family')"
>
<view class="switch-item" :class="{ active: dataType === 'family' }" @tap="switchDataType('family')">
<uni-icons type="home" size="16" :color="dataType === 'family' ? '#fff' : '#999'"></uni-icons>
<text>家庭</text>
</view>
<view
class="switch-item"
:class="{active: dataType === 'personal'}"
@tap="switchDataType('personal')"
>
<view class="switch-item" :class="{ active: dataType === 'personal' }" @tap="switchDataType('personal')">
<uni-icons type="person" size="16" :color="dataType === 'personal' ? '#fff' : '#999'"></uni-icons>
<text>个人</text>
</view>
@@ -52,12 +39,7 @@
</view>
<!-- 账单列表 -->
<scroll-view
class="bill-scroll"
scroll-y
@scrolltolower="loadMore"
lower-threshold="100"
>
<scroll-view class="bill-scroll" scroll-y @scrolltolower="loadMore" lower-threshold="100">
<view class="bill-list-content">
<view v-if="loading && billList.length === 0" class="loading-state">
<uni-load-more status="loading"></uni-load-more>
@@ -76,16 +58,24 @@
<view class="bill-group" v-for="(group, date) in groupedBills" :key="date">
<view class="group-header">
<text class="group-date">{{ group.formattedDate }}</text>
<text class="group-amount">支出: ¥{{ group.expense.toFixed(2) }} 收入: ¥{{ group.income.toFixed(2) }}</text>
<text class="group-amount">支出: ¥{{ group.expense.toFixed(2) }} 收入: ¥{{
group.income.toFixed(2) }}</text>
</view>
<view class="bill-item" v-for="bill in group.bills" :key="bill.id" @tap="editBill(bill)">
<view class="bill-icon" :style="{background: getCategoryColor(bill.category_id)}">
<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-remark" v-if="bill.remark">{{ bill.remark }}</text>
<view class="bill-detail">
<text class="bill-payment" v-if="bill.payment_method">
<uni-icons :type="getPaymentIcon(bill.payment_method)" size="14"
:color="getPaymentColor(bill.payment_method)"></uni-icons>
<text>{{ getPaymentName(bill.payment_method) }}</text>
</text>
<text class="bill-remark" v-if="bill.remark">{{ bill.remark }}</text>
</view>
</view>
<view class="bill-amount" :class="bill.type">
<text>{{ bill.type === 'expense' ? '-' : '+' }}¥{{ bill.amount.toFixed(2) }}</text>
@@ -96,10 +86,7 @@
<!-- 加载更多状态 -->
<view v-if="billList.length > 0" class="load-more-state">
<uni-load-more
:status="loadMoreStatus"
:content-text="loadMoreText"
></uni-load-more>
<uni-load-more :status="loadMoreStatus" :content-text="loadMoreText"></uni-load-more>
</view>
</view>
</scroll-view>
@@ -108,6 +95,29 @@
<view class="fab-button" @tap="addBill">
<uni-icons type="plus" size="24" color="#fff"></uni-icons>
</view>
<!-- 操作弹窗 -->
<uni-popup ref="actionPopup" type="bottom">
<view class="action-sheet">
<view class="action-header">
<text class="action-title">选择操作</text>
<uni-icons type="close" size="20" color="#999" @tap="closeActionSheet"></uni-icons>
</view>
<view class="action-list">
<view class="action-item edit" @tap="editCurrentBill">
<uni-icons type="compose" size="20" color="#667eea"></uni-icons>
<text>编辑</text>
</view>
<view class="action-item delete" @tap="deleteCurrentBill" v-if="canDeleteBill">
<uni-icons type="trash" size="20" color="#FF6B6B"></uni-icons>
<text>删除</text>
</view>
</view>
<view class="action-cancel" @tap="closeActionSheet">
<text>取消</text>
</view>
</view>
</uni-popup>
</view>
</un-pages>
</template>
@@ -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;
}
}
}
</style>

View File

@@ -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)