first commit

This commit is contained in:
2026-09-16 15:04:08 +08:00
commit 43d095ea0f
117 changed files with 31982 additions and 0 deletions
+222
View File
@@ -0,0 +1,222 @@
<!--
* 我的加成券 对齐 C端.html #sub-my-coupons (~line 3332)
* 汇总Hero + 3Tab(未使用/使用中/已过期) + 券卡片列表
-->
<template>
<view class="sub-page">
<nav-bar title="我的加成券" :show-back="true" @back="closeSub()" />
<scroll-view scroll-y class="page-content">
<!-- 汇总Hero -->
<view class="mc-summary">
<text class="mcs-label">可用加成券</text>
<text class="mcs-count">{{ summary.unused }}<text class="mcs-unit"> </text></text>
<view class="mcs-stats">
<view class="mcs-stat"><text class="mcs-stat-num">{{ summary.unused }}</text><text class="mcs-stat-label">未使用</text></view>
<view class="mcs-stat"><text class="mcs-stat-num">{{ summary.using }}</text><text class="mcs-stat-label">使用中</text></view>
<view class="mcs-stat"><text class="mcs-stat-num">{{ summary.expired }}</text><text class="mcs-stat-label">已过期</text></view>
</view>
</view>
<!-- Tabs -->
<view class="mc-tabs">
<view v-for="t in tabs" :key="t.key" class="mc-tab" :class="{ active: activeTab === t.key }" @click="activeTab = t.key">
{{ t.label }}<text class="mc-tab-badge" v-if="t.count">{{ t.count }}</text>
</view>
</view>
<!-- 未使用 -->
<view v-if="activeTab === 'unused'">
<view class="mc-card" v-for="c in unusedCoupons" :key="c.id">
<view class="mcc-top">
<view class="mcc-icon" :style="{ background: c.iconBg }">{{ c.multiplier }}x</view>
<view class="mcc-info">
<text class="mcc-name">收益加成券 {{ c.multiplier }}x</text>
<text class="mcc-meta">{{ c.desc }}</text>
</view>
<text class="mcc-status usable">未使用</text>
</view>
<view class="mcc-body">
<view class="mcc-params">
<view class="mcc-param"><text class="mcp-val">{{ c.multiplier }}x</text><text class="mcp-label">加成倍数</text></view>
<view class="mcc-param"><text class="mcp-val">{{ c.days }}</text><text class="mcp-label">有效期</text></view>
<view class="mcc-param"><text class="mcp-val">{{ c.scope }}</text><text class="mcp-label">适用范围</text></view>
</view>
<text class="mcc-buy-info">{{ c.boughtAt }} · 剩余 {{ c.remainTime }}</text>
<view class="mcc-actions">
<view class="mcc-btn mcc-btn-secondary">使用规则</view>
<view class="mcc-btn mcc-btn-primary" @click="openSub('coupon-detail')">去使用</view>
</view>
</view>
</view>
</view>
<!-- 使用中 -->
<view v-if="activeTab === 'using'">
<view class="mc-card" v-for="c in usingCoupons" :key="c.id">
<view class="mcc-top">
<view class="mcc-icon" :style="{ background: c.iconBg }">{{ c.multiplier }}x</view>
<view class="mcc-info">
<text class="mcc-name">收益加成券 {{ c.multiplier }}x</text>
<text class="mcc-meta">绑定: {{ c.deviceName }}</text>
</view>
<text class="mcc-status active">使用中</text>
</view>
<view class="mcc-body">
<view class="mcc-device"><text>🖥 {{ c.deviceName }} · {{ c.deviceModel }}</text></view>
<text class="mcc-earn">日产出 ¥{{ c.dailyEarn }}</text>
<view class="mcc-progress"><view class="mcc-progress-fill" :style="{ width: c.progress + '%', background: c.progressColor }"></view></view>
<view class="mcc-progress-info"><text>已使用 {{ c.usedDays }}</text><text>剩余 {{ c.remainDays }}</text></view>
<view class="mcc-actions">
<view class="mcc-btn mcc-btn-secondary">积分明细</view>
<view class="mcc-btn mcc-btn-secondary" style="color:#FF4D4F">解绑设备</view>
</view>
</view>
</view>
</view>
<!-- 已过期 -->
<view v-if="activeTab === 'expired'">
<view class="mc-card expired" v-for="c in expiredCoupons" :key="c.id">
<view class="mcc-top">
<view class="mcc-icon" :style="{ background: c.iconBg, filter: 'grayscale(1)' }">{{ c.multiplier }}x</view>
<view class="mcc-info">
<text class="mcc-name">收益加成券 {{ c.multiplier }}x</text>
<text class="mcc-meta">已过期 {{ c.expiredAt }}</text>
</view>
<text class="mcc-status expired">已过期</text>
</view>
<view class="mcc-body" v-if="c.boundDevice">
<text class="mcc-bind-note">曾绑定: {{ c.boundDevice }} · 产生收益 ¥{{ formatThousands(c.generatedRev) }}</text>
</view>
</view>
<text class="expired-more">仅显示最近3条过期记录 · 查看全部</text>
</view>
</scroll-view>
</view>
</template>
<script setup>
import NavBar from '@/components/NavBar.vue'
import request from '@/utils/request'
import { ref } from 'vue'
import { onLoad } from '@dcloudio/uni-app'
import { openSub, closeSub, parseSubParams } from '@/composables/useNavigation'
import { formatThousands } from '@/utils/util'
const MULT_BG = {
'2': 'linear-gradient(135deg, #FF4D4F, #FF6B35)', '1.5': 'linear-gradient(135deg, #FF8800, #FFB84D)',
'1.2': 'linear-gradient(135deg, #1677FF, #4096FF)', '1.1': 'linear-gradient(135deg, #00C4A7, #5FE6CC)',
}
const activeTab = ref('unused')
const tabs = [
{ key: 'unused', label: '未使用', count: 0 },
{ key: 'using', label: '使用中', count: 0 },
{ key: 'expired', label: '已过期', count: 0 }
]
const summary = ref({ unused: 0, using: 0, expired: 0 })
const unusedCoupons = ref([])
const usingCoupons = ref([])
const expiredCoupons = ref([])
const mapCoupon = (c) => {
const mult = String(c.multiplier || '1')
return {
...c,
days: c.duration_days,
scope: '全部型号',
iconBg: MULT_BG[mult] || MULT_BG['1.5'],
desc: `有效期${c.duration_days}天 · 购买于 ${c.created_at}`,
boughtAt: '购买于 ' + c.created_at,
remainTime: '--',
deviceName: c.device_name || '--',
deviceModel: '--',
dailyEarn: '--',
usedDays: c.activated_at ? Math.ceil((Date.now() - new Date(c.activated_at).getTime()) / 86400000) : 0,
remainDays: c.expires_at ? Math.max(0, Math.ceil((new Date(c.expires_at).getTime() - Date.now()) / 86400000)) : 0,
progress: c.activated_at && c.expires_at ? Math.min(100, Math.round((Date.now() - new Date(c.activated_at).getTime()) / (new Date(c.expires_at).getTime() - new Date(c.activated_at).getTime()) * 100)) : 0,
progressColor: 'linear-gradient(90deg, #1677FF, #4096FF)',
expiredAt: c.expires_at || '--',
boundDevice: c.device_name || '',
generatedRev: '--'
}
}
const fetchCoupons = async () => {
try {
const res = await request.get('/c/my-coupons', { showLoading: false })
const d = res.data
summary.value = d.summary || { unused: 0, using: 0, expired: 0 }
tabs[0].count = summary.value.unused
tabs[1].count = summary.value.using
tabs[2].count = summary.value.expired
unusedCoupons.value = (d.unused || []).map(mapCoupon)
usingCoupons.value = (d.using || []).map(mapCoupon)
expiredCoupons.value = (d.expired || []).map(mapCoupon)
} catch (e) { /* 忽略 */ }
}
onLoad((options) => { const params = parseSubParams(options); fetchCoupons() })
</script>
<style lang="scss" scoped>
.sub-page { min-height: 100vh; background: #F2F4F8; }
.page-content { padding: 24rpx; }
.mc-summary { background: linear-gradient(135deg, #0A1733 0%, #0D2B6E 50%, #5B2C8E 100%); border-radius: 14rpx; padding: 28rpx 24rpx; color: #fff; margin-bottom: 24rpx; position: relative; overflow: hidden; box-shadow: 0 16rpx 48rpx rgba(13,43,110,.25);
&::after { content: ''; position: absolute; top: -60rpx; right: -40rpx; width: 240rpx; height: 240rpx; border-radius: 50%; background: radial-gradient(circle, rgba(255,136,0,.18), transparent 65%); }
}
.mcs-label { font-size: 24rpx; opacity: .7; position: relative; z-index: 1; }
.mcs-count { font-size: 60rpx; font-weight: 800; font-family: 'DIN Alternate', -apple-system, sans-serif; display: block; position: relative; z-index: 1; }
.mcs-unit { font-size: 26rpx; opacity: .7; }
.mcs-stats { display: flex; gap: 0; margin-top: 28rpx; position: relative; z-index: 1; }
.mcs-stat { flex: 1; text-align: center; position: relative;
&:not(:last-child)::after { content: ''; position: absolute; right: 0; top: 50%; transform: translateY(-50%); width: 1rpx; height: 48rpx; background: rgba(255,255,255,.15); }
}
.mcs-stat-num { font-size: 36rpx; font-weight: 700; font-family: 'DIN Alternate', -apple-system, sans-serif; display: block; }
.mcs-stat-label { font-size: 20rpx; opacity: .7; }
.mc-tabs { display: flex; background: #fff; border-radius: 10rpx; padding: 6rpx; margin-bottom: 24rpx; box-shadow: 0 1px 3px rgba(15,18,38,.04); }
.mc-tab { flex: 1; text-align: center; padding: 14rpx 0; font-size: 24rpx; font-weight: 600; color: #8B91A1; border-radius: 8rpx; position: relative;
&.active { background: #FF8800; color: #fff; }
}
.mc-tab-badge { font-size: 20rpx; margin-left: 4rpx; }
.mc-tab.active .mc-tab-badge { background: rgba(255,255,255,.25); color: #fff; }
.mc-tab:not(.active) .mc-tab-badge { background: #F3F5F9; color: #8B91A1; }
.mc-card { background: #fff; border-radius: 14rpx; margin-bottom: 20rpx; box-shadow: 0 1px 3px rgba(15,18,38,.04); overflow: hidden;
&.expired { opacity: .55; }
}
.mcc-top { display: flex; align-items: center; padding: 28rpx; gap: 20rpx; }
.mcc-icon { width: 96rpx; height: 96rpx; border-radius: 24rpx; display: flex; align-items: center; justify-content: center; flex-shrink: 0; font-size: 30rpx; font-weight: 800; color: #fff; font-family: 'DIN Alternate', -apple-system, sans-serif; }
.mcc-info { flex: 1; min-width: 0; }
.mcc-name { font-size: 28rpx; font-weight: 700; color: #0F1226; display: block; }
.mcc-meta { font-size: 22rpx; color: #8B91A1; margin-top: 4rpx; display: block; }
.mcc-status { font-size: 20rpx; font-weight: 600; padding: 6rpx 16rpx; border-radius: 20rpx; flex-shrink: 0;
&.usable { background: #E6FAF5; color: #00C4A7; }
&.active { background: #FFF7E6; color: #FF8800; }
&.expired { background: #F3F5F9; color: #BCC2CE; }
}
.mcc-body { padding: 0 28rpx 28rpx; }
.mcc-params { display: grid; grid-template-columns: repeat(3, 1fr); gap: 12rpx; margin-bottom: 20rpx; }
.mcc-param { background: #F7F9FC; border-radius: 16rpx; padding: 14rpx 12rpx; text-align: center; }
.mcp-val { font-size: 24rpx; font-weight: 700; font-family: 'DIN Alternate', -apple-system, sans-serif; color: #0F1226; display: block; }
.mcp-label { font-size: 18rpx; color: #8B91A1; }
.mcc-buy-info { font-size: 20rpx; color: #BCC2CE; display: block; margin-bottom: 16rpx; }
.mcc-actions { display: flex; gap: 16rpx; }
.mcc-btn { flex: 1; padding: 16rpx 0; text-align: center; font-size: 24rpx; font-weight: 600; border-radius: 16rpx; }
.mcc-btn-primary { background: #FF8800; color: #fff; }
.mcc-btn-secondary { background: #F3F5F9; color: #5A6072; }
.mcc-device { background: #FFF7E6; border-radius: 8rpx; padding: 12rpx 16rpx; font-size: 22rpx; color: #FF8800; margin-bottom: 12rpx; }
.mcc-earn { font-size: 30rpx; font-weight: 700; color: #FF8800; font-family: 'DIN Alternate', -apple-system, sans-serif; display: block; margin-bottom: 12rpx; }
.mcc-progress { height: 8rpx; background: #F3F5F9; border-radius: 4rpx; overflow: hidden; margin-bottom: 8rpx; }
.mcc-progress-fill { height: 100%; border-radius: 4rpx; }
.mcc-progress-info { display: flex; justify-content: space-between; font-size: 20rpx; color: #8B91A1; margin-bottom: 16rpx; }
.mcc-bind-note { font-size: 22rpx; color: #8B91A1; display: block; }
.expired-more { text-align: center; font-size: 22rpx; color: #BCC2CE; display: block; padding: 20rpx; }
</style>