fix: 全面修复 22 项问题
严重 Bug: - NaN 金额校验绕过(add 页面 parseFloat 空字符串) - 4 个页面补充 waitForReady(add/budget/category-manage/group-manage) - 导出 Loading 遮罩不消失(profile exportData 缺 hideLoading) - Store 吞掉请求错误不 re-throw(transaction/stats/budget/user) - 分类排序 SQL 拼接改参数化查询(category PUT /sort) - 备份接口添加管理员权限检查(backup requireAdmin) 中等问题: - 群组加入竞态条件改为 INSERT IGNORE - 统计页切月/切Tab添加 loadSeq 防竞态 - stats store 补全 loading 状态 + Overview 初始值补全 7 字段 - 保存成功后返回不再弹「放弃修改」确认框 - 群组管理创建/加入添加 saving 双重提交保护 - 预算页添加 onShow 刷新 - 服务端输入校验(name/color/invite_code/微信code) UI/样式: - 触摸目标: bell/nav-back/nav-save→88rpx, avatar-badge→64rpx, action-btn→80rpx - :active 反馈: bell/profile-card/identity-card/id-manage/arrow/action-btn/group-btn/cat-info - 内联样式改动态绑定(4处 spacer 使用 capsuleRight) - FAB 文字+改用 Icon 组件 - prefers-reduced-motion 动画降级(Skeleton/profile/budget/add) - 胶囊适配(add/category-manage/group-manage) - CategoryIcon !important 改为 iconStyle 计算属性 文档: - CLAUDE.md 补充 SaveSuccess/ChartWrapper 组件 + category/budget/index API
This commit is contained in:
@@ -8,7 +8,13 @@
|
||||
@touchmove="!disableSwipe && onTouchMove($event)"
|
||||
@touchend="!disableSwipe && onTouchEnd()"
|
||||
>
|
||||
<!-- 头像(群组模式下显示他人创建者) -->
|
||||
<view v-if="isOtherCreator" class="creator-avatar">
|
||||
<image v-if="creatorAvatarUrl" :src="creatorAvatarUrl" class="creator-img" mode="aspectFill" />
|
||||
<text v-else class="creator-initial">{{ item.creator_nickname?.[0] }}</text>
|
||||
</view>
|
||||
<CategoryIcon
|
||||
v-else
|
||||
:label="item.category_name?.[0] || '?'"
|
||||
:color="item.category_color || '#BFB3B3'"
|
||||
:bg-color="(item.category_color || '#BFB3B3') + '20'"
|
||||
@@ -16,7 +22,10 @@
|
||||
/>
|
||||
<view class="info">
|
||||
<text class="name">{{ item.note || item.category_name || '未分类' }}</text>
|
||||
<text class="meta">{{ item.category_name || '未分类' }}{{ hideDate ? '' : ' · ' + formatDate(item.date) }}</text>
|
||||
<text class="meta">
|
||||
<text v-if="isOtherCreator" class="creator-name">{{ item.creator_nickname }} · </text>
|
||||
{{ item.category_name || '未分类' }}{{ hideDate ? '' : ' · ' + formatDate(item.date) }}
|
||||
</text>
|
||||
</view>
|
||||
<text class="amount" :class="item.type">{{ item.type === 'expense' ? '-' : '+' }}{{ formatAmount(item.amount) }}</text>
|
||||
</view>
|
||||
@@ -27,14 +36,16 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { ref, computed } from 'vue'
|
||||
import { formatAmount, formatDate } from '@/utils/format'
|
||||
import { API_BASE } from '@/config'
|
||||
import Icon from '@/components/Icon/Icon.vue'
|
||||
import CategoryIcon from '@/components/CategoryIcon/CategoryIcon.vue'
|
||||
|
||||
withDefaults(defineProps<{
|
||||
const props = withDefaults(defineProps<{
|
||||
item: {
|
||||
id: number
|
||||
user_id: number
|
||||
amount: number
|
||||
type: 'expense' | 'income'
|
||||
note?: string
|
||||
@@ -42,14 +53,33 @@ withDefaults(defineProps<{
|
||||
category_name?: string
|
||||
category_icon?: string
|
||||
category_color?: string
|
||||
creator_nickname?: string
|
||||
creator_avatar?: string
|
||||
}
|
||||
hideDate?: boolean
|
||||
/** 禁用滑动删除(首页预览等场景) */
|
||||
disableSwipe?: boolean
|
||||
}>(), { hideDate: false, disableSwipe: false })
|
||||
/** 是否显示创建者信息(群组模式) */
|
||||
showCreator?: boolean
|
||||
/** 当前用户 ID,用于判断是否为本人记录 */
|
||||
currentUserId?: number
|
||||
}>(), { hideDate: false, disableSwipe: false, showCreator: false, currentUserId: 0 })
|
||||
|
||||
/** 是否为他人的记录(群组模式下,非本人的才显示创建者信息) */
|
||||
const isOtherCreator = computed(() => {
|
||||
if (!props.showCreator || !props.item.creator_nickname) return false
|
||||
return !props.currentUserId || props.item.user_id !== props.currentUserId
|
||||
})
|
||||
|
||||
defineEmits(['item-tap', 'delete'])
|
||||
|
||||
const creatorAvatarUrl = computed(() => {
|
||||
if (!props.item.creator_avatar) return ''
|
||||
if (props.item.creator_avatar.startsWith('http')) return props.item.creator_avatar
|
||||
// 截取服务端根地址(去掉 /api)
|
||||
const base = API_BASE.replace(/\/api$/, '')
|
||||
return base + '/api/user/avatar/' + props.item.creator_avatar
|
||||
})
|
||||
|
||||
const startX = ref(0)
|
||||
const offsetX = ref(0)
|
||||
const showDelete = ref(false)
|
||||
@@ -121,6 +151,29 @@ function onTouchEnd() {
|
||||
}
|
||||
}
|
||||
|
||||
.creator-avatar {
|
||||
width: 80rpx;
|
||||
height: 80rpx;
|
||||
border-radius: 50%;
|
||||
background: #FFF0E6;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.creator-img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.creator-initial {
|
||||
font-size: 28rpx;
|
||||
font-weight: 600;
|
||||
color: #FF8C69;
|
||||
}
|
||||
|
||||
.info {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
@@ -143,6 +196,11 @@ function onTouchEnd() {
|
||||
margin-top: 4rpx;
|
||||
}
|
||||
|
||||
.creator-name {
|
||||
color: #FF8C69;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.amount {
|
||||
font-family: 'Fredoka', sans-serif;
|
||||
font-size: 32rpx;
|
||||
|
||||
Reference in New Issue
Block a user