feat: gravity transaction logging + user-facing history popup
- New GravityTransaction schema tracks all gravity changes (registration, interview/optimize/download deduction, purchase, monthly topup, migration) - GravityTopUpService: bulk log for monthly VIP topup - PaymentController.activateMembership: log plan_set transactions - QuotaService: add logTransaction, wire into all gravity-modifying methods - UserService: log registration grants (phone/wx/email/password) - GET /user/gravity-transactions?page=&limit= API - Frontend: user.vue '明细' button + paginated popup - Docs: update PROJECT-STATUS v4.10, FEATURE-LIST, DEPLOYMENT
This commit is contained in:
@@ -53,7 +53,8 @@
|
||||
<view class="gravity-actions">
|
||||
<text class="gravity-btn share" @click="goSharePage">分享得引力值</text>
|
||||
<text class="gravity-btn contribute" @click="goContributePage">贡献面经</text>
|
||||
<text class="gravity-btn h5buy" @click="goH5Buy">购买引力值</text>
|
||||
<text class="gravity-btn h5buy" @click="goH5Buy">购买引力值</text>
|
||||
<text class="gravity-btn detail" @click="showGravityDetail = true">明细</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
@@ -158,11 +159,37 @@
|
||||
<text class="modal-close" @click="showGetGravityModal = false">关闭</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 引力值明细 -->
|
||||
<view class="modal-overlay" v-if="showGravityDetail" @click="showGravityDetail = false">
|
||||
<view class="modal-content detail-content" @click.stop>
|
||||
<text class="modal-title">引力值明细</text>
|
||||
<text class="modal-hint">购买、消耗、补给的引力值变动记录</text>
|
||||
<scroll-view class="detail-list" scroll-y>
|
||||
<view v-if="gravityTxs.length === 0" class="detail-empty">暂无记录</view>
|
||||
<view v-for="tx in gravityTxs" :key="tx._id" class="detail-item">
|
||||
<view class="detail-item-left">
|
||||
<text class="detail-item-desc">{{ tx.description }}</text>
|
||||
<text class="detail-item-time">{{ formatTime(tx.createdAt) }}</text>
|
||||
</view>
|
||||
<text :class="['detail-item-amount', tx.amount > 0 ? 'amount-positive' : 'amount-negative']">
|
||||
{{ tx.amount > 0 ? '+' : '' }}{{ tx.amount }}
|
||||
</text>
|
||||
</view>
|
||||
</scroll-view>
|
||||
<view v-if="gravityTxTotalPages > 1" class="detail-pagination">
|
||||
<text class="detail-page-btn" @click="loadGravityTxs(gravityTxPage - 1)" v-if="gravityTxPage > 1">上一页</text>
|
||||
<text class="detail-page-info">{{ gravityTxPage }} / {{ gravityTxTotalPages }}</text>
|
||||
<text class="detail-page-btn" @click="loadGravityTxs(gravityTxPage + 1)" v-if="gravityTxPage < gravityTxTotalPages">下一页</text>
|
||||
</view>
|
||||
<text class="modal-close" @click="showGravityDetail = false">关闭</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { ref, computed, watch, onMounted } from 'vue'
|
||||
// #ifdef MP-WEIXIN
|
||||
import { onShow, onShareAppMessage, onShareTimeline } from '@dcloudio/uni-app'
|
||||
// #endif
|
||||
@@ -290,6 +317,36 @@ const goH5Buy = () => {
|
||||
uni.navigateTo({ url: '/pages/member/member' })
|
||||
}
|
||||
|
||||
// 引力值明细
|
||||
const showGravityDetail = ref(false)
|
||||
const gravityTxs = ref([])
|
||||
const gravityTxPage = ref(1)
|
||||
const gravityTxTotalPages = ref(1)
|
||||
const loadGravityTxs = async (page = 1) => {
|
||||
try {
|
||||
const res = await uni.request({
|
||||
url: api(`/user/gravity-transactions?page=${page}&limit=20`),
|
||||
method: 'GET',
|
||||
header: { Authorization: `Bearer ${token.value}` },
|
||||
})
|
||||
if (res.statusCode >= 200 && res.statusCode < 300 && res.data) {
|
||||
gravityTxs.value = res.data.items || []
|
||||
gravityTxPage.value = res.data.page || 1
|
||||
gravityTxTotalPages.value = res.data.totalPages || 1
|
||||
} else if (checkAuth(res)) {
|
||||
return
|
||||
}
|
||||
} catch(e) { /* silent */ }
|
||||
}
|
||||
const formatTime = (t) => {
|
||||
if (!t) return ''
|
||||
const d = new Date(t)
|
||||
return `${d.getMonth() + 1}/${d.getDate()} ${String(d.getHours()).padStart(2, '0')}:${String(d.getMinutes()).padStart(2, '0')}`
|
||||
}
|
||||
|
||||
// 点击明细时打开弹窗并加载数据
|
||||
watch(showGravityDetail, (v) => { if (v) loadGravityTxs() })
|
||||
|
||||
const goCareer = () => uni.navigateTo({ url: '/pages/career/career' })
|
||||
const goHistory = () => uni.switchTab({ url: '/pages/history/history' })
|
||||
const goReviewReview = () => uni.navigateTo({ url: '/pages/review/review' })
|
||||
@@ -357,6 +414,7 @@ const doLogout = () => {
|
||||
.gravity-btn.share { background: rgba(255,255,255,0.2); color: #FFFFFF; border: 2rpx solid rgba(255,255,255,0.3); }
|
||||
.gravity-btn.h5buy { background: #FFFFFF; color: #667eea; }
|
||||
.gravity-btn.contribute { background: rgba(255,255,255,0.15); color: #FFFFFF; border: 2rpx solid rgba(255,255,255,0.2); }
|
||||
.gravity-btn.detail { background: rgba(255,255,255,0.15); color: #FFFFFF; border: 2rpx solid rgba(255,255,255,0.2); font-size: 22rpx; padding: 18rpx 16rpx; flex: 0.5; }
|
||||
.gravity-btn:active { transform: scale(0.96); }
|
||||
|
||||
.menu-area { padding: 0 32rpx 32rpx; margin-top: 8rpx; }
|
||||
@@ -404,4 +462,21 @@ const doLogout = () => {
|
||||
.gp-method-name { font-size: 26rpx; font-weight: 600; color: var(--color-text); }
|
||||
.gp-method-desc { font-size: 20rpx; color: #6B7280; line-height: 1.4; }
|
||||
.gp-method-arrow { font-size: 32rpx; color: #D1D5DB; }
|
||||
|
||||
/* 引力值明细弹窗 */
|
||||
.detail-content { width: 650rpx; max-height: 70vh; }
|
||||
.detail-list { width: 100%; max-height: 500rpx; }
|
||||
.detail-empty { text-align: center; padding: 40rpx 0; font-size: 24rpx; color: #9CA3AF; }
|
||||
.detail-item { display: flex; align-items: center; justify-content: space-between; width: 100%; padding: 20rpx 0; border-bottom: 1rpx solid #F3F4F6; }
|
||||
.detail-item:last-child { border-bottom: none; }
|
||||
.detail-item-left { display: flex; flex-direction: column; gap: 4rpx; flex: 1; min-width: 0; }
|
||||
.detail-item-desc { font-size: 26rpx; color: var(--color-text); font-weight: 500; }
|
||||
.detail-item-time { font-size: 20rpx; color: #9CA3AF; }
|
||||
.detail-item-amount { font-size: 30rpx; font-weight: 700; flex-shrink: 0; margin-left: 16rpx; }
|
||||
.amount-positive { color: #10B981; }
|
||||
.amount-negative { color: #EF4444; }
|
||||
.detail-pagination { display: flex; align-items: center; justify-content: center; gap: 24rpx; width: 100%; margin-top: 8rpx; }
|
||||
.detail-page-btn { font-size: 24rpx; color: var(--color-primary); padding: 8rpx 16rpx; }
|
||||
.detail-page-btn:active { opacity: 0.6; }
|
||||
.detail-page-info { font-size: 24rpx; color: #6B7280; }
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user