feat(uniapp_v2): 二开功能迁移与小程序主包优化
- 从 uniapp 迁移 HJF 页面、API、组件及用户/订单相关改动 - queue、assets 使用独立分包以降低主包体积 - 修复首页单根节点与支付结果页 v-if 链 - 关闭 HjfDemoPanel 全局注册;uniNoticeBar 注释 $getAppWebview 避免 __webviewId__ 报错 - 配置域名与 manifest 应用名称;cache/store 防御性处理 Made-with: Cursor
This commit is contained in:
@@ -0,0 +1,38 @@
|
||||
# Conversation - 2026-03-10 18:17:19
|
||||
|
||||
## Prompt
|
||||
|
||||
创建页面 points_detail.vue(P17 积分明细页):
|
||||
|
||||
文件路径: /Users/apple/scott2026/huangjingfen/pro_v3.5.1/view/uniapp/pages/assets/points_detail.vue
|
||||
|
||||
内容要求:
|
||||
1. 使用 Vue 2 Options API
|
||||
2. 导入 getPointsDetail from '@/api/hjfAssets.js'
|
||||
3. data: list, page, loading, finished, activeTab
|
||||
4. onLoad + onReachBottom: 加载和分页
|
||||
5. 模板: Tab切换(全部/待释放/已释放)+ 积分流水列表
|
||||
6. 样式: <style scoped lang="scss">
|
||||
7. 参考: docs/frontend-new-pages-spec.md 第 3.3.2 节
|
||||
|
||||
完整 JSDoc。
|
||||
|
||||
## Response
|
||||
|
||||
The file was created successfully (377 lines total). Here's a summary of what was built:
|
||||
|
||||
**`pages/assets/points_detail.vue`** — P17 积分明细页,共 377 行:
|
||||
|
||||
**模板结构:**
|
||||
- **Tab 导航栏**:3 个 Tab(全部 / 待释放 / 已释放),`sticky` 吸顶,激活项下划线用 `var(--view-theme)` 着色
|
||||
- **按日期分组列表**:每个分组显示日期标题 + 白色卡片,卡片内每条流水展示标题、时间、状态标签(橙色=待释放 / 绿色=已释放)、右侧积分增减(`+N` 主题色 / `-N` 黑色)
|
||||
- **加载更多**:旋转 loading 图标 + 文案;**空状态**:引入 `emptyPage` 组件
|
||||
|
||||
**脚本(Vue 2 Options API):**
|
||||
- `data`:`tabs`、`activeTab`、`list`、`dateKeys`、`page`、`limit`、`loading`、`finished`、`loadTitle`
|
||||
- `onLoad`:支持 `?type=frozen|released` 参数直接定位 Tab
|
||||
- `onReachBottom`:触底自动翻页
|
||||
- `changeTab()`:切 Tab 时 `resetAndLoad()` 重置所有状态
|
||||
- `loadList()`:防重入(`loading || finished` 守卫),按 `add_time` 日期前缀分组追加数据
|
||||
|
||||
**样式:** `<style scoped lang="scss">`,全 `rpx` 单位,使用 `var(--view-theme)` 主题色。
|
||||
BIN
pro_v3.5.1/view/uniapp_v2/pages/assets/.cursor-cli/sessions.db
Normal file
BIN
pro_v3.5.1/view/uniapp_v2/pages/assets/.cursor-cli/sessions.db
Normal file
Binary file not shown.
561
pro_v3.5.1/view/uniapp_v2/pages/assets/index.vue
Normal file
561
pro_v3.5.1/view/uniapp_v2/pages/assets/index.vue
Normal file
@@ -0,0 +1,561 @@
|
||||
<template>
|
||||
<view class="hjf-assets-page" :style="colorStyle">
|
||||
|
||||
<view class="assets-wrapper">
|
||||
<view class="assets-header">
|
||||
<view v-if="loading" class="skeleton-card"></view>
|
||||
|
||||
<view v-else class="hero-card">
|
||||
<view class="hero-card__bg-circle hero-card__bg-circle--1"></view>
|
||||
<view class="hero-card__bg-circle hero-card__bg-circle--2"></view>
|
||||
<view class="hero-card__main">
|
||||
<view class="hero-card__label">推荐佣金余额(元)</view>
|
||||
<view class="hero-card__money">
|
||||
<text class="hero-card__yen">¥</text>{{ assetsInfo ? Number(assetsInfo.brokerage_price || 0).toFixed(2) : '0.00' }}
|
||||
</view>
|
||||
</view>
|
||||
<view class="hero-card__row">
|
||||
<view class="hero-card__col">
|
||||
<view class="hero-card__col-val">{{ formattedFrozenPoints }}</view>
|
||||
<view class="hero-card__col-key">待释放积分</view>
|
||||
</view>
|
||||
<view class="hero-card__sep"></view>
|
||||
<view class="hero-card__col">
|
||||
<view class="hero-card__col-val">{{ formattedAvailablePoints }}</view>
|
||||
<view class="hero-card__col-key">已释放积分</view>
|
||||
</view>
|
||||
<view class="hero-card__sep"></view>
|
||||
<view class="hero-card__col" v-if="assetsInfo && assetsInfo.today_release != null">
|
||||
<view class="hero-card__col-val hero-card__col-val--accent">{{ assetsInfo.today_release }}</view>
|
||||
<view class="hero-card__col-key">今日释放</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="quick-nav">
|
||||
<view class="quick-nav__item" hover-class="quick-nav__item--hover" @tap="goPointsDetail">
|
||||
<view class="quick-nav__icon quick-nav__icon--points">
|
||||
<text class="iconfont icon-jifen"></text>
|
||||
</view>
|
||||
<view class="quick-nav__label">积分明细</view>
|
||||
<view class="quick-nav__desc">待释放 / 已释放</view>
|
||||
</view>
|
||||
<view class="quick-nav__item" hover-class="quick-nav__item--hover" @tap="goCashDetail">
|
||||
<view class="quick-nav__icon quick-nav__icon--cash">
|
||||
<text class="iconfont icon-qianbao"></text>
|
||||
</view>
|
||||
<view class="quick-nav__label">现金明细</view>
|
||||
<view class="quick-nav__desc">收支流水记录</view>
|
||||
</view>
|
||||
<view class="quick-nav__item" hover-class="quick-nav__item--hover" @tap="goWithdraw">
|
||||
<view class="quick-nav__icon quick-nav__icon--withdraw">
|
||||
<text class="iconfont icon-tixian"></text>
|
||||
</view>
|
||||
<view class="quick-nav__label">申请提现</view>
|
||||
<view class="quick-nav__desc">可用余额提现</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="release-card" v-if="!loading && assetsInfo">
|
||||
<view class="release-card__header">
|
||||
<view class="release-card__dot"></view>
|
||||
<view class="release-card__title">今日释放预告</view>
|
||||
<view class="release-card__date">{{ todayDateStr }}</view>
|
||||
</view>
|
||||
<view class="release-card__body">
|
||||
<view class="release-card__item">
|
||||
<view class="release-card__value release-card__value--highlight">
|
||||
{{ assetsInfo.today_release != null ? assetsInfo.today_release : 0 }}
|
||||
</view>
|
||||
<view class="release-card__key">今日预计释放(积分)</view>
|
||||
</view>
|
||||
<view class="release-card__divider"></view>
|
||||
<view class="release-card__item">
|
||||
<view class="release-card__value">{{ formattedFrozenPoints }}</view>
|
||||
<view class="release-card__key">待释放总积分</view>
|
||||
</view>
|
||||
<view class="release-card__divider"></view>
|
||||
<view class="release-card__item">
|
||||
<view class="release-card__value">{{ formattedAvailablePoints }}</view>
|
||||
<view class="release-card__key">已释放积分</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="release-card__tips">
|
||||
<text class="iconfont icon-tishi"></text>
|
||||
积分每日自动释放,释放后可用于抵扣消费
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="stats-row" v-if="!loading && assetsInfo">
|
||||
<view class="stats-item">
|
||||
<view class="stats-icon stats-icon--refund">
|
||||
<text class="iconfont icon-qianbao"></text>
|
||||
</view>
|
||||
<view class="stats-info">
|
||||
<view class="stats-value">¥{{ Number(assetsInfo.brokerage_price || 0).toFixed(2) }}</view>
|
||||
<view class="stats-label">推荐累计佣金</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="stats-divider"></view>
|
||||
<view class="stats-item">
|
||||
<view class="stats-icon stats-icon--points">
|
||||
<text class="iconfont icon-jifen"></text>
|
||||
</view>
|
||||
<view class="stats-info">
|
||||
<view class="stats-value">{{ formattedTotalPoints }}</view>
|
||||
<view class="stats-label">累计获得积分</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getAssetsOverview } from '@/api/hjfAssets.js';
|
||||
import colors from '@/mixins/color.js';
|
||||
|
||||
export default {
|
||||
name: 'AssetsIndex',
|
||||
|
||||
mixins: [colors],
|
||||
|
||||
data() {
|
||||
return {
|
||||
assetsInfo: null,
|
||||
loading: false
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
todayDateStr() {
|
||||
const d = new Date();
|
||||
const mm = String(d.getMonth() + 1).padStart(2, '0');
|
||||
const dd = String(d.getDate()).padStart(2, '0');
|
||||
return `${d.getFullYear()}-${mm}-${dd}`;
|
||||
},
|
||||
formattedFrozenPoints() {
|
||||
if (!this.assetsInfo) return '0';
|
||||
return Number(this.assetsInfo.frozen_points).toLocaleString();
|
||||
},
|
||||
formattedAvailablePoints() {
|
||||
if (!this.assetsInfo) return '0';
|
||||
return Number(this.assetsInfo.available_points).toLocaleString();
|
||||
},
|
||||
formattedTotalPoints() {
|
||||
if (!this.assetsInfo) return '0';
|
||||
return Number(this.assetsInfo.total_points_earned).toLocaleString();
|
||||
}
|
||||
},
|
||||
|
||||
onLoad() {
|
||||
this.fetchAssetsOverview();
|
||||
},
|
||||
|
||||
onShow() {
|
||||
if (this.assetsInfo) {
|
||||
this.fetchAssetsOverview();
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
fetchAssetsOverview() {
|
||||
this.loading = true;
|
||||
getAssetsOverview()
|
||||
.then(res => {
|
||||
if (res && (res.status === 200 || res.data)) {
|
||||
this.assetsInfo = res.data || res;
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
uni.showToast({ title: '加载失败,请稍后重试', icon: 'none' });
|
||||
})
|
||||
.finally(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
|
||||
goPointsDetail() {
|
||||
uni.navigateTo({ url: '/pages/assets/points_detail' });
|
||||
},
|
||||
|
||||
goCashDetail() {
|
||||
uni.navigateTo({ url: '/pages/users/user_bill/index?type=2' });
|
||||
},
|
||||
|
||||
goWithdraw() {
|
||||
uni.navigateTo({ url: '/pages/users/user_cash/index' });
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.hjf-assets-page {
|
||||
min-height: 100vh;
|
||||
background-color: #f4f5f7;
|
||||
padding-bottom: 60rpx;
|
||||
}
|
||||
|
||||
.assets-wrapper {
|
||||
background: linear-gradient(180deg, var(--view-theme, #e93323) 0%, var(--view-gradient, #f76b1c) 50%, #f4f5f7 100%);
|
||||
padding-bottom: 4rpx;
|
||||
}
|
||||
|
||||
.assets-header {
|
||||
padding-top: 32rpx;
|
||||
}
|
||||
|
||||
.assets-header__top {
|
||||
padding: 0 30rpx 24rpx;
|
||||
}
|
||||
|
||||
.assets-header__title {
|
||||
font-size: 36rpx;
|
||||
font-weight: 700;
|
||||
color: #fff;
|
||||
letter-spacing: 2rpx;
|
||||
}
|
||||
|
||||
.skeleton-card {
|
||||
width: 710rpx;
|
||||
height: 280rpx;
|
||||
background: linear-gradient(90deg, rgba(255,255,255,0.15) 25%, rgba(255,255,255,0.25) 50%, rgba(255,255,255,0.15) 75%);
|
||||
background-size: 400% 100%;
|
||||
border-radius: 32rpx;
|
||||
margin: 0 auto 20rpx;
|
||||
animation: skeleton-shimmer 1.5s infinite;
|
||||
}
|
||||
|
||||
@keyframes skeleton-shimmer {
|
||||
0% { background-position: 100% 50%; }
|
||||
100% { background-position: 0% 50%; }
|
||||
}
|
||||
|
||||
.hero-card {
|
||||
width: 710rpx;
|
||||
margin: 0 auto;
|
||||
border-radius: 32rpx;
|
||||
background: linear-gradient(135deg, var(--view-theme, #e93323) 0%, var(--view-gradient, #f76b1c) 100%);
|
||||
box-sizing: border-box;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 8rpx 32rpx rgba(0, 0, 0, 0.12);
|
||||
}
|
||||
|
||||
.hero-card__bg-circle {
|
||||
position: absolute;
|
||||
border-radius: 50%;
|
||||
opacity: 0.08;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.hero-card__bg-circle--1 {
|
||||
width: 320rpx;
|
||||
height: 320rpx;
|
||||
top: -80rpx;
|
||||
right: -60rpx;
|
||||
}
|
||||
|
||||
.hero-card__bg-circle--2 {
|
||||
width: 200rpx;
|
||||
height: 200rpx;
|
||||
bottom: -40rpx;
|
||||
left: -30rpx;
|
||||
}
|
||||
|
||||
.hero-card__main {
|
||||
padding: 40rpx 36rpx 28rpx;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.hero-card__label {
|
||||
font-size: 24rpx;
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
margin-bottom: 12rpx;
|
||||
}
|
||||
|
||||
.hero-card__money {
|
||||
font-size: 64rpx;
|
||||
font-weight: 700;
|
||||
color: #fff;
|
||||
font-family: 'SemiBold', sans-serif;
|
||||
line-height: 1.1;
|
||||
}
|
||||
|
||||
.hero-card__yen {
|
||||
font-size: 36rpx;
|
||||
font-weight: 500;
|
||||
margin-right: 4rpx;
|
||||
}
|
||||
|
||||
.hero-card__row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background: rgba(255, 255, 255, 0.12);
|
||||
padding: 24rpx 0;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.hero-card__col {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.hero-card__col-val {
|
||||
font-size: 36rpx;
|
||||
font-weight: 600;
|
||||
color: #fff;
|
||||
font-family: 'SemiBold', sans-serif;
|
||||
margin-bottom: 6rpx;
|
||||
}
|
||||
|
||||
.hero-card__col-val--accent {
|
||||
color: #ffe58f;
|
||||
}
|
||||
|
||||
.hero-card__col-key {
|
||||
font-size: 22rpx;
|
||||
color: rgba(255, 255, 255, 0.75);
|
||||
}
|
||||
|
||||
.hero-card__sep {
|
||||
width: 1rpx;
|
||||
height: 48rpx;
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.quick-nav {
|
||||
display: flex;
|
||||
margin: 20rpx 20rpx 0;
|
||||
}
|
||||
|
||||
.quick-nav__item {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
background: #fff;
|
||||
border-radius: 20rpx;
|
||||
padding: 30rpx 12rpx 26rpx;
|
||||
box-sizing: border-box;
|
||||
margin: 0 8rpx;
|
||||
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.04);
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.quick-nav__item:first-child { margin-left: 0; }
|
||||
.quick-nav__item:last-child { margin-right: 0; }
|
||||
|
||||
.quick-nav__item--hover {
|
||||
opacity: 0.75;
|
||||
transform: scale(0.97);
|
||||
}
|
||||
|
||||
.quick-nav__icon {
|
||||
width: 80rpx;
|
||||
height: 80rpx;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.quick-nav__icon .iconfont {
|
||||
font-size: 38rpx;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.quick-nav__icon--points {
|
||||
background: linear-gradient(135deg, var(--view-theme, #e93323) 0%, var(--view-gradient, #f76b1c) 100%);
|
||||
box-shadow: 0 6rpx 16rpx rgba(233, 51, 35, 0.25);
|
||||
}
|
||||
|
||||
.quick-nav__icon--cash {
|
||||
background: linear-gradient(135deg, #52c41a 0%, #73d13d 100%);
|
||||
box-shadow: 0 6rpx 16rpx rgba(82, 196, 26, 0.25);
|
||||
}
|
||||
|
||||
.quick-nav__icon--withdraw {
|
||||
background: linear-gradient(135deg, #fa8c16 0%, #ffc53d 100%);
|
||||
box-shadow: 0 6rpx 16rpx rgba(250, 140, 22, 0.25);
|
||||
}
|
||||
|
||||
.quick-nav__label {
|
||||
font-size: 26rpx;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
margin-bottom: 6rpx;
|
||||
}
|
||||
|
||||
.quick-nav__desc {
|
||||
font-size: 20rpx;
|
||||
color: #999;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.release-card {
|
||||
width: 710rpx;
|
||||
margin: 24rpx auto;
|
||||
background: #fff;
|
||||
border-radius: 24rpx;
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
|
||||
.release-card__header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 28rpx 32rpx 0;
|
||||
}
|
||||
|
||||
.release-card__dot {
|
||||
width: 8rpx;
|
||||
height: 30rpx;
|
||||
border-radius: 4rpx;
|
||||
background: var(--view-theme, #e93323);
|
||||
margin-right: 14rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.release-card__title {
|
||||
font-size: 30rpx;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.release-card__date {
|
||||
font-size: 22rpx;
|
||||
color: #bbb;
|
||||
}
|
||||
|
||||
.release-card__body {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 28rpx 20rpx 24rpx;
|
||||
}
|
||||
|
||||
.release-card__item {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.release-card__value {
|
||||
font-size: 38rpx;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
margin-bottom: 8rpx;
|
||||
font-family: 'SemiBold', sans-serif;
|
||||
}
|
||||
|
||||
.release-card__value--highlight {
|
||||
color: var(--view-theme, #e93323);
|
||||
}
|
||||
|
||||
.release-card__key {
|
||||
font-size: 22rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.release-card__divider {
|
||||
width: 1rpx;
|
||||
height: 60rpx;
|
||||
background-color: #eee;
|
||||
align-self: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.release-card__tips {
|
||||
font-size: 22rpx;
|
||||
color: #bbb;
|
||||
padding: 0 32rpx 24rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6rpx;
|
||||
background: #fafafa;
|
||||
padding-top: 18rpx;
|
||||
}
|
||||
|
||||
.release-card__tips .iconfont {
|
||||
font-size: 24rpx;
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
.stats-row {
|
||||
width: 710rpx;
|
||||
margin: 0 auto;
|
||||
background: #fff;
|
||||
border-radius: 24rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 32rpx 24rpx;
|
||||
box-sizing: border-box;
|
||||
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
|
||||
.stats-item {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.stats-icon {
|
||||
width: 64rpx;
|
||||
height: 64rpx;
|
||||
border-radius: 16rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.stats-icon .iconfont {
|
||||
font-size: 32rpx;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.stats-icon--refund {
|
||||
background: linear-gradient(135deg, #ff7875 0%, #ff4d4f 100%);
|
||||
}
|
||||
|
||||
.stats-icon--points {
|
||||
background: linear-gradient(135deg, #ffa940 0%, #fa8c16 100%);
|
||||
}
|
||||
|
||||
.stats-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.stats-value {
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
font-family: 'SemiBold', sans-serif;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.stats-label {
|
||||
font-size: 22rpx;
|
||||
color: #999;
|
||||
margin-top: 4rpx;
|
||||
}
|
||||
|
||||
.stats-divider {
|
||||
width: 1rpx;
|
||||
height: 60rpx;
|
||||
background-color: #eee;
|
||||
flex-shrink: 0;
|
||||
margin: 0 10rpx;
|
||||
}
|
||||
</style>
|
||||
376
pro_v3.5.1/view/uniapp_v2/pages/assets/points_detail.vue
Normal file
376
pro_v3.5.1/view/uniapp_v2/pages/assets/points_detail.vue
Normal file
@@ -0,0 +1,376 @@
|
||||
<template>
|
||||
<!-- P17 积分明细页 -->
|
||||
<view class="hjf-points-detail-page">
|
||||
|
||||
<!-- Tab 筛选栏 -->
|
||||
<view class="tab-nav acea-row">
|
||||
<view
|
||||
v-for="(tab, idx) in tabs"
|
||||
:key="idx"
|
||||
class="tab-nav__item"
|
||||
:class="{ on: activeTab === idx }"
|
||||
@tap="changeTab(idx)"
|
||||
>{{ tab.label }}</view>
|
||||
</view>
|
||||
|
||||
<!-- 积分流水列表(按日期分组) -->
|
||||
<view class="points-list">
|
||||
<view
|
||||
v-for="(group, gIdx) in list"
|
||||
:key="gIdx"
|
||||
class="points-list__group"
|
||||
>
|
||||
<!-- 日期分组标题 -->
|
||||
<view class="points-list__date">{{ group.date }}</view>
|
||||
|
||||
<!-- 分组内条目 -->
|
||||
<view class="points-list__card">
|
||||
<view
|
||||
v-for="(item, iIdx) in group.children"
|
||||
:key="iIdx"
|
||||
class="points-list__item acea-row row-between-wrapper"
|
||||
>
|
||||
<!-- 左侧:标题 + 时间 -->
|
||||
<view class="points-list__info">
|
||||
<view class="points-list__title line1">{{ item.title }}</view>
|
||||
<view class="points-list__meta acea-row">
|
||||
<view class="points-list__time">{{ item.add_time }}</view>
|
||||
<view
|
||||
class="points-list__tag"
|
||||
:class="item.status === 'frozen' ? 'tag--frozen' : 'tag--released'"
|
||||
>{{ item.status === 'frozen' ? '待释放' : '已释放' }}</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 右侧:积分增减 -->
|
||||
<view
|
||||
class="points-list__points"
|
||||
:class="item.pm === 1 ? 'points--add' : 'points--sub'"
|
||||
>
|
||||
{{ item.pm === 1 ? '+' : '-' }}{{ item.points }}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 加载更多提示 -->
|
||||
<view v-if="list.length > 0" class="loadingicon acea-row row-center-wrapper">
|
||||
<text
|
||||
v-if="loading"
|
||||
class="loading iconfont icon-jiazai"
|
||||
></text>
|
||||
<text class="load-title">{{ loadTitle }}</text>
|
||||
</view>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<view v-if="!loading && list.length === 0" class="empty-wrap">
|
||||
<emptyPage title="暂无积分记录~" src="/statics/images/noOrder.gif"></emptyPage>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getPointsDetail } from '@/api/hjfAssets.js';
|
||||
import emptyPage from '@/components/emptyPage.vue';
|
||||
import colors from '@/mixins/color';
|
||||
|
||||
/**
|
||||
* P17 积分明细页
|
||||
*
|
||||
* 展示当前用户的积分流水,支持按类型 Tab 筛选(全部/待释放/已释放),
|
||||
* 并以日期分组方式分页渲染列表,上拉触底自动加载下一页。
|
||||
*
|
||||
* @module pages/assets/points_detail
|
||||
*/
|
||||
export default {
|
||||
name: 'PointsDetail',
|
||||
|
||||
components: { emptyPage },
|
||||
|
||||
mixins: [colors],
|
||||
|
||||
data() {
|
||||
return {
|
||||
/**
|
||||
* Tab 配置:label 展示文案,type 对应 API 参数('' 表示全部)
|
||||
* @type {Array<{ label: string, type: string }>}
|
||||
*/
|
||||
tabs: [
|
||||
{ label: '全部', type: '' },
|
||||
{ label: '待释放', type: 'frozen' },
|
||||
{ label: '已释放', type: 'released' },
|
||||
],
|
||||
|
||||
/** 当前选中 Tab 索引,0=全部 1=待释放 2=已释放 @type {number} */
|
||||
activeTab: 0,
|
||||
|
||||
/**
|
||||
* 按日期分组后的列表,每项形如 { date: string, children: Array }
|
||||
* @type {Array<{ date: string, children: Array<Object> }>}
|
||||
*/
|
||||
list: [],
|
||||
|
||||
/** 已记录的日期键,用于去重分组 @type {string[]} */
|
||||
dateKeys: [],
|
||||
|
||||
/** 当前请求页码 @type {number} */
|
||||
page: 1,
|
||||
|
||||
/** 每页条数 @type {number} */
|
||||
limit: 15,
|
||||
|
||||
/** 是否正在加载(防重入锁) @type {boolean} */
|
||||
loading: false,
|
||||
|
||||
/** 是否已加载全部数据 @type {boolean} */
|
||||
finished: false,
|
||||
|
||||
/** 底部加载提示文案 @type {string} */
|
||||
loadTitle: '加载更多',
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* 页面加载:支持从资产总览通过 type 参数直接定位 Tab。
|
||||
* @param {Object} options - 页面跳转参数
|
||||
* @param {string} [options.type] - 可选,'frozen' | 'released'
|
||||
*/
|
||||
onLoad(options) {
|
||||
if (options && options.type) {
|
||||
const idx = this.tabs.findIndex(t => t.type === options.type);
|
||||
if (idx > -1) this.activeTab = idx;
|
||||
}
|
||||
this.loadList();
|
||||
},
|
||||
|
||||
/**
|
||||
* 上拉触底:加载下一页数据。
|
||||
*/
|
||||
onReachBottom() {
|
||||
this.loadList();
|
||||
},
|
||||
|
||||
methods: {
|
||||
/**
|
||||
* 切换 Tab 筛选,重置分页后重新加载列表。
|
||||
* @param {number} idx - 点击的 Tab 索引
|
||||
*/
|
||||
changeTab(idx) {
|
||||
if (idx === this.activeTab) return;
|
||||
this.activeTab = idx;
|
||||
this.resetAndLoad();
|
||||
},
|
||||
|
||||
/**
|
||||
* 重置所有分页/列表状态,然后发起首页请求。
|
||||
*/
|
||||
resetAndLoad() {
|
||||
this.page = 1;
|
||||
this.finished = false;
|
||||
this.loading = false;
|
||||
this.loadTitle = '加载更多';
|
||||
this.dateKeys = [];
|
||||
this.$set(this, 'list', []);
|
||||
this.loadList();
|
||||
},
|
||||
|
||||
/**
|
||||
* 加载积分明细数据(分页追加),通过 `getPointsDetail` 获取。
|
||||
* 按 `add_time` 日期前缀分组追加到 `list`。
|
||||
* 防重入:loading 为 true 或 finished 时直接返回。
|
||||
*/
|
||||
loadList() {
|
||||
if (this.loading || this.finished) return;
|
||||
this.loading = true;
|
||||
this.loadTitle = '';
|
||||
|
||||
const currentType = this.tabs[this.activeTab].type;
|
||||
|
||||
const params = {
|
||||
page: this.page,
|
||||
limit: this.limit,
|
||||
};
|
||||
if (currentType) params.type = currentType;
|
||||
|
||||
getPointsDetail(params).then(res => {
|
||||
const items = (res.data && res.data.list) ? res.data.list : [];
|
||||
|
||||
items.forEach(item => {
|
||||
// 取 add_time 的日期部分作为分组 key(格式 "YYYY-MM-DD")
|
||||
const dateKey = (item.add_time || '').split(' ')[0] || '未知日期';
|
||||
if (!this.dateKeys.includes(dateKey)) {
|
||||
this.dateKeys.push(dateKey);
|
||||
this.list.push({ date: dateKey, children: [] });
|
||||
}
|
||||
const group = this.list.find(g => g.date === dateKey);
|
||||
if (group) group.children.push(item);
|
||||
});
|
||||
|
||||
const loadend = items.length < this.limit;
|
||||
this.finished = loadend;
|
||||
this.loadTitle = loadend ? '没有更多内容啦~' : '加载更多';
|
||||
if (!loadend) this.page += 1;
|
||||
this.loading = false;
|
||||
}).catch(() => {
|
||||
this.loading = false;
|
||||
this.loadTitle = '加载更多';
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.hjf-points-detail-page {
|
||||
min-height: 100vh;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
/* -------- Tab 导航 -------- */
|
||||
.tab-nav {
|
||||
background-color: #fff;
|
||||
height: 88rpx;
|
||||
line-height: 88rpx;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 10;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
|
||||
&__item {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
position: relative;
|
||||
transition: color 0.2s;
|
||||
|
||||
&.on {
|
||||
color: var(--view-theme);
|
||||
font-size: 30rpx;
|
||||
font-weight: 500;
|
||||
|
||||
&::after {
|
||||
position: absolute;
|
||||
content: '';
|
||||
width: 48rpx;
|
||||
height: 6rpx;
|
||||
border-radius: 10rpx;
|
||||
background: var(--view-theme);
|
||||
bottom: 0;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* -------- 积分列表 -------- */
|
||||
.points-list {
|
||||
padding: 20rpx 24rpx;
|
||||
|
||||
&__group {
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
&__date {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
padding: 12rpx 0 10rpx;
|
||||
}
|
||||
|
||||
&__card {
|
||||
background: #fff;
|
||||
border-radius: 16rpx;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
|
||||
&__item {
|
||||
padding: 28rpx 28rpx;
|
||||
border-bottom: 1rpx solid #f5f5f5;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
|
||||
&__info {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
padding-right: 20rpx;
|
||||
}
|
||||
|
||||
&__title {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
margin-bottom: 10rpx;
|
||||
}
|
||||
|
||||
&__meta {
|
||||
align-items: center;
|
||||
gap: 12rpx;
|
||||
}
|
||||
|
||||
&__time {
|
||||
font-size: 22rpx;
|
||||
color: #aaa;
|
||||
}
|
||||
|
||||
&__tag {
|
||||
font-size: 20rpx;
|
||||
padding: 2rpx 10rpx;
|
||||
border-radius: 20rpx;
|
||||
|
||||
&.tag--frozen {
|
||||
color: #ff8c00;
|
||||
background: rgba(255, 140, 0, 0.1);
|
||||
}
|
||||
|
||||
&.tag--released {
|
||||
color: #52c41a;
|
||||
background: rgba(82, 196, 26, 0.1);
|
||||
}
|
||||
}
|
||||
|
||||
&__points {
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
white-space: nowrap;
|
||||
|
||||
&.points--add {
|
||||
color: var(--view-theme);
|
||||
}
|
||||
|
||||
&.points--sub {
|
||||
color: #333;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* -------- 加载更多 & 空状态 -------- */
|
||||
.loadingicon {
|
||||
padding: 24rpx 0;
|
||||
font-size: 24rpx;
|
||||
color: #aaa;
|
||||
|
||||
.loading {
|
||||
margin-right: 8rpx;
|
||||
animation: rotating 1.5s linear infinite;
|
||||
}
|
||||
|
||||
.load-title {
|
||||
font-size: 24rpx;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes rotating {
|
||||
from { transform: rotate(0deg); }
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
.empty-wrap {
|
||||
padding: 60rpx 0;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user