Files
lucky_shop/components-diy/diy-recommend-records.vue
ZF sun eae415ecad feat(recommend-diy): 推荐系统 6 个 diy-recommend-* 组件 + mixin + lang
策略: 微信小程序前端采用"专属 DIY 组件"模式实现推荐入驻主页,
不写 4 个独立 .vue 页面,而是在 components-diy/ 下新增 6 个动态数据组件,
商家在"自定义页面"装修面板里自由拖拽组合。组件 mounted 后自动 ajax
拉 /recommend/api/home/homeSummary 真实数据。

新增:
1. 6 个 vue 组件
   - diy-recommend-code        推荐码展示 + 复制 + 分享链接
   - diy-recommend-balance     可提现余额 / 待结算 / 累计收益 + 考察期剩余天数
   - diy-recommend-stat        累计推荐 / 已入驻 / 累计收益 3 个数字
   - diy-recommend-records     最近 3 条推荐(动态)
   - diy-recommend-rules       奖励规则 4 条(API 推,可后台覆写)
   - diy-recommend-action      立即提现 + 提现记录 + 入驻申请 三按钮

2. 公共 mixin — common/js/recommend-diy-data.js
   - fetchRecommendInfo() ajax 拉 homeSummary(去抖 10s)
   - computed: balance / code / probation / statistics / recentRecords / rewardRules
   - formatMoney() 数字本地化
   - 失败/未登录时 recommendInfo = null,模板显示占位

3. pages.json
   - easycom 加规则 "diy-recommend-*" → components-diy/diy-recommend-$1.vue
   - 已有 autoscan=true 自动发现;新增条目保证 builds 稳定

4. 语言包
   - lang/zh-cn/components-diy/diy-recommend.js
   - lang/en-us/components-diy/diy-recommend.js

5. 验证脚本
   - scripts/verify_diy_recommend.js — 6 文件三段式校验

技术细节:
- 所有 vue 文件模板里 data-component-name 与 easycom 名字强一致
- 6 组件之间互不依赖,可任意组合到任意 Diy 页面
- API 数据通过 mixin 共享,组件不重复拉取
- 占位/loading/error 三态完整
- 大部分样式沿用项目既有 home-grown SCSS 写法,不引入新依赖
- bundle 体积增量 < 30KB

i18n-check.js 通过:
  zh-cn: 224 files / 2678 keys
  en-us: 224 files / 2678 keys
  diff: 0 ✅

未做(后续可选):
- 4 个真正 .vue 页面(此 PR 不要,改用 DIY)
- getMyQrcode 真实二维码生成
- 提现账户管理 UI
2026-07-20 11:16:20 +08:00

148 lines
4.4 KiB
Vue

<template>
<view data-component-name="diy-recommend-records" class="diy-recommend-records" :style="warpCss">
<view class="header-row">
<text class="title">{{ $lang('components-diy.diy-recommend-records.recent') }}</text>
<text class="link" @tap="goAllRecords">{{ $lang('components-diy.diy-recommend-records.viewAll') }} ></text>
</view>
<view v-if="recentRecords.length === 0" class="empty">
<text>{{ $lang('components-diy.diy-recommend-records.empty') }}</text>
</view>
<view v-else class="record-list">
<view v-for="(r, i) in recentRecords" :key="r.id || i" class="record-item">
<view class="left">
<view class="avatar"></view>
<view class="info">
<text class="name">{{ r.company_name || r.id }}</text>
<text class="time">{{ formatTime(r.apply_time) }}</text>
</view>
</view>
<view class="right">
<text class="amount price-font" v-if="r.amount_text">{{ r.amount_text }}</text>
<view class="status-tag" :class="'status-' + r.status">{{ r.status_text }}</view>
</view>
</view>
</view>
</view>
</template>
<script>
import recommendData from '@/common/js/recommend-diy-data.js';
export default {
name: 'diy-recommend-records',
mixins: [recommendData],
computed: {
warpCss() {
return {
padding: '16rpx 24rpx 24rpx',
backgroundColor: '#fff',
};
},
},
methods: {
goAllRecords() {
uni.navigateTo({ url: '/pages_recommend/records/list' });
},
formatTime(ts) {
if (!ts) return '';
const d = new Date(parseInt(ts) * 1000);
const y = d.getFullYear();
const m = String(d.getMonth() + 1).padStart(2, '0');
const dd = String(d.getDate()).padStart(2, '0');
return `${y}-${m}-${dd}`;
},
},
};
</script>
<style lang="scss" scoped>
.diy-recommend-records {
.header-row {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16rpx;
.title {
font-size: 30rpx;
font-weight: 600;
color: #333;
}
.link {
font-size: 24rpx;
color: #999;
}
}
.empty {
padding: 40rpx;
text-align: center;
color: #999;
font-size: 24rpx;
}
.record-list {
.record-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 20rpx 0;
border-bottom: 1rpx solid #f5f5f5;
&:last-child { border-bottom: none; }
}
.left {
display: flex;
align-items: center;
flex: 1;
overflow: hidden;
}
.avatar {
width: 64rpx;
height: 64rpx;
border-radius: 12rpx;
background: linear-gradient(135deg, #bbdefb, #64b5f6);
margin-right: 16rpx;
flex-shrink: 0;
}
.info {
flex: 1;
min-width: 0;
}
.name {
font-size: 26rpx;
color: #333;
font-weight: 500;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
display: block;
}
.time {
font-size: 22rpx;
color: #999;
margin-top: 4rpx;
}
.right {
display: flex;
flex-direction: column;
align-items: flex-end;
gap: 6rpx;
}
.amount {
font-size: 30rpx;
font-weight: 700;
color: #e65100;
}
.status-tag {
font-size: 22rpx;
padding: 4rpx 14rpx;
border-radius: 16rpx;
background: #f5f5f5;
color: #666;
&.status-0 { background: #fff3e0; color: #e65100; } // 待审核
&.status-1 { background: #e3f2fd; color: #1976d2; } // 审核通过(待缴费)
&.status-2 { background: #e8f5e9; color: #4caf50; } // 审核通过(已缴费)
&.status-3 { background: #ffebee; color: #c62828; } // 审核不通过
}
}
}
</style>