feat: 用户信息完善 + 一起记功能

- users 表添加 nickname、avatar_url 字段
- 用户信息 API(GET/PUT /me)+ 头像上传(multer)
- 头像通过 API 路由获取(公开,不经过 auth)
- 登录接口返回用户昵称和头像
- 用户信息 Store + 个人资料编辑页面
- 我的页面和首页显示真实用户信息
- 群组表(groups、group_members)+ transactions 添加 group_id
- 群组 API(创建/加入/退出/解散)
- 交易和统计 API 支持 group_id 视图切换
- 群组客户端 Store + 身份切换
- 群组管理页面
- App 初始化群组 Store
- UPLOAD_DIR 配置化
- Node.js 备份替代 mysqldump
- 统一前端 BASE_URL(config.ts)
- uploads 加入 .gitignore
- 修复 trust proxy 警告
This commit is contained in:
2026-06-03 17:04:22 +08:00
parent 9162b2c87c
commit 21f4d81959
45 changed files with 3145 additions and 348 deletions

View File

@@ -22,6 +22,7 @@
<view class="amount-display">
<text class="currency">¥</text>
<text class="digits">{{ displayAmount }}</text>
<text class="cursor">|</text>
</view>
<scroll-view class="category-scroll" scroll-y :scroll-into-view="scrollToCat">
@@ -46,7 +47,7 @@
</view>
</view>
<Numpad @input="onInput" @delete="onDelete" @confirm="save" />
<Numpad v-model="amountStr" @confirm="save" />
<SaveSuccess v-model:visible="showSuccess" :text="editId ? '修改成功' : '记账成功'" />
</view>
@@ -86,8 +87,8 @@ const dateLabel = computed(() => {
})
const displayAmount = computed(() => {
const num = parseFloat(amountStr.value) || 0
return num.toFixed(2)
if (!amountStr.value) return ''
return amountStr.value
})
const currentCategories = computed(() => catStore.getByType(type.value))
@@ -151,33 +152,6 @@ function onDateChange(e: any) {
selectedDate.value = e.detail.value
}
function onInput(key: string) {
if (key === '.' && amountStr.value.includes('.')) return
// When current value is '0', replace with new key (but '0' and '00' should stay as '0')
let next: string
if (amountStr.value === '0') {
next = (key === '0' || key === '00') ? '0' : key
} else {
next = amountStr.value + key
}
const num = parseFloat(next)
if (!isNaN(num) && num > 9999999.99) return
// Check decimal limit on the resulting value (handles "00" adding 2 chars)
if (next.includes('.')) {
const decimals = next.split('.')[1]
if (decimals && decimals.length > 2) return
}
amountStr.value = next
}
function onDelete() {
if (amountStr.value.length > 1) {
amountStr.value = amountStr.value.slice(0, -1)
} else {
amountStr.value = '0'
}
}
async function save() {
if (saving.value) return
const amount = Math.round(parseFloat(amountStr.value) * 100)
@@ -329,6 +303,18 @@ function goBack() {
font-variant-numeric: tabular-nums;
}
.cursor {
font-family: 'Fredoka', monospace;
font-size: 88rpx;
font-weight: 300;
color: #FF8C69;
animation: blink 1s step-end infinite;
}
@keyframes blink {
50% { opacity: 0; }
}
.category-scroll {
max-height: 320rpx;
}