bm-bmt/pages/assets/voucher-center.vue

726 lines
17 KiB
Vue
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<view class="asset-page voucher-page" @click="closeTips">
<asset-page-shell title="抵用券" />
<view class="asset-scroll voucher-scroll">
<view class="voucher-header">
<view class="voucher-header__bg">
<image
class="voucher-header__bg-image"
src="https://imgs.agrimedia.cn/shop/d-bg.png"
mode="aspectFill"
></image>
<view class="voucher-header__content">
<view class="voucher-header__current">
<text class="voucher-header__current-label">当前抵用券</text>
<text class="voucher-header__current-value asset-number-font">{{
summary.balance
}}</text>
</view>
<view class="voucher-header__totals">
<view class="voucher-header__total-item">
<text class="voucher-header__total-value asset-number-font">{{
summary.totalIn
}}</text>
<view
class="voucher-header__total-text"
@click.stop="toggleTip('showTip1')"
>
<text>累计收入</text>
<image
class="voucher-header__help-icon"
src="https://imgs.agrimedia.cn/shop/%E6%84%9F%E5%8F%B9%E5%8F%B7.svg"
mode="widthFix"
></image>
</view>
<view
v-if="showTip1"
class="voucher-tooltip voucher-tooltip--left"
@click.stop
>
<view class="voucher-tooltip__row">
<text class="voucher-tooltip__text">
收入不包含消费、退款扣回等扣除抵用券的场景
</text>
<view
class="voucher-tooltip__close"
@click.stop="showTip1 = false"
>
<image
src="https://imgs.agrimedia.cn/shop/line-close.png"
mode="aspectFit"
></image>
</view>
</view>
<view class="voucher-tooltip__arrow voucher-tooltip__arrow--left"></view>
</view>
</view>
<view class="voucher-header__total-item">
<text class="voucher-header__total-value asset-number-font">{{
summary.totalOut
}}</text>
<view
class="voucher-header__total-text"
@click.stop="toggleTip('showTip2')"
>
<text>累计支出</text>
<image
class="voucher-header__help-icon"
src="https://imgs.agrimedia.cn/shop/%E6%84%9F%E5%8F%B9%E5%8F%B7.svg"
mode="widthFix"
></image>
</view>
<view
v-if="showTip2"
class="voucher-tooltip voucher-tooltip--right"
@click.stop
>
<view class="voucher-tooltip__row">
<text class="voucher-tooltip__text">
支出包含消费、退款扣回等扣除抵用券的场景
</text>
<view
class="voucher-tooltip__close"
@click.stop="showTip2 = false"
>
<image
src="https://imgs.agrimedia.cn/shop/line-close.png"
mode="aspectFit"
></image>
</view>
</view>
<view class="voucher-tooltip__arrow"></view>
</view>
</view>
</view>
</view>
</view>
</view>
<view class="voucher-wrapper">
<view class="voucher-nav">
<view class="voucher-tabs">
<view
v-for="item in tabOptions"
:key="item.key"
class="voucher-tabs__item"
:class="{ 'is-active': activeTab === item.key }"
@click.stop="changeTab(item.key)"
>
<text class="voucher-tabs__text">{{ item.label }}</text>
<image
v-if="activeTab === item.key"
class="voucher-tabs__line"
src="https://imgs.agrimedia.cn/shop/active.png"
mode="widthFix"
></image>
</view>
</view>
</view>
<view class="voucher-list" :class="{ 'voucher-list--filled': sections.length }">
<view
v-for="section in sections"
:key="section.key"
class="voucher-card"
>
<view class="voucher-card__head">
<view class="voucher-card__month">
<text class="voucher-card__month-text asset-number-font">{{
section.label
}}</text>
</view>
<view class="voucher-card__totals">
<text class="voucher-card__total-text">
收入:
<text class="voucher-card__total-income asset-number-font">{{
section.income
}}</text>
</text>
<text class="voucher-card__total-text">
支出:
<text class="voucher-card__total-expense asset-number-font">
-{{ section.expense }}
</text>
</text>
</view>
</view>
<view
v-for="item in section.items"
:key="item.id"
class="voucher-row"
>
<view class="voucher-row__left">
<view class="voucher-row__icon">
<image
src="https://imgs.agrimedia.cn/shop/sifang-icon.png"
mode="aspectFit"
></image>
</view>
<view class="voucher-row__body">
<text class="voucher-row__title">{{ item.title }}</text>
<text class="voucher-row__time asset-number-font">{{
item.time
}}</text>
</view>
</view>
<text
class="voucher-row__amount asset-number-font"
:class="
item.amountTone === 'income'
? 'voucher-row__amount--income'
: 'voucher-row__amount--expense'
"
>
{{ item.amount }}
</text>
</view>
</view>
<view v-if="sections.length" class="voucher-loading">
<text class="voucher-loading__text asset-number-font">{{
loadMoreText
}}</text>
</view>
<view v-else class="voucher-empty">
<text class="voucher-empty__title">暂无记录哦~</text>
<text class="voucher-empty__desc">
当前分类下还没有抵用券流水数据
</text>
</view>
</view>
</view>
</view>
</view>
</template>
<script>
import AssetPageShell from "../../components/asset-page-shell.vue";
import { fetchVoucherCenterPage } from "../../api/assets";
export default {
components: {
AssetPageShell,
},
data() {
return {
summary: {
balance: "0",
totalIn: "0",
totalOut: "0",
},
tabOptions: [
{ key: "all", label: "全部" },
{ key: "normal", label: "常规补贴" },
{ key: "order", label: "单单补贴" },
],
activeTab: "all",
sections: [],
page: 1,
pageSize: 10,
hasMore: false,
loadingMore: false,
showTip1: false,
showTip2: false,
};
},
computed: {
loadMoreText() {
if (this.loadingMore) {
return "加载中...";
}
if (this.hasMore) {
return "加载更多";
}
return "暂无更多";
},
},
onLoad() {
this.loadPage(true, 1);
},
onReachBottom() {
this.loadMore();
},
methods: {
closeTips() {
this.showTip1 = false;
this.showTip2 = false;
},
toggleTip(key) {
if (this[key]) {
this[key] = false;
return;
}
this.showTip1 = false;
this.showTip2 = false;
this[key] = true;
},
mergeSections(currentSections, nextSections) {
const sectionMap = {};
const mergedSections = [];
const appendSection = (section) => {
if (!section || !section.key) {
return;
}
if (!sectionMap[section.key]) {
sectionMap[section.key] = {
key: section.key,
label: section.label,
income: section.income,
expense: section.expense,
items: [],
};
mergedSections.push(sectionMap[section.key]);
}
const targetSection = sectionMap[section.key];
targetSection.income = section.income || targetSection.income;
targetSection.expense = section.expense || targetSection.expense;
const itemMap = {};
targetSection.items.forEach((item) => {
itemMap[item.id] = true;
});
(section.items || []).forEach((item) => {
if (!item || itemMap[item.id]) {
return;
}
itemMap[item.id] = true;
targetSection.items.push(item);
});
};
(currentSections || []).forEach(appendSection);
(nextSections || []).forEach(appendSection);
return mergedSections;
},
changeTab(key) {
if (this.activeTab === key) {
return;
}
this.activeTab = key;
this.closeTips();
this.loadPage(true, 1);
},
async loadPage(showLoading, targetPage) {
const page = Number(targetPage || 1);
const isLoadMore = page > 1;
if (isLoadMore && (this.loadingMore || !this.hasMore)) {
return;
}
if (isLoadMore) {
this.loadingMore = true;
}
try {
const requestOptions =
!isLoadMore && showLoading
? {
showLoading: true,
loadingText: "加载中",
}
: null;
const result = await fetchVoucherCenterPage(
this.activeTab,
{
page: page,
pageSize: this.pageSize,
},
requestOptions,
);
const nextSections = Array.isArray(result.sections) ? result.sections : [];
this.summary = Object.assign({}, this.summary, result.summary || {});
this.sections = isLoadMore
? this.mergeSections(this.sections, nextSections)
: nextSections;
const pagination = result.pagination || {};
this.page = Number(pagination.page || page || 1);
this.hasMore = Boolean(pagination.hasMore);
} catch (error) {
uni.showToast({
title: error.message || (isLoadMore ? "加载更多失败" : "页面加载失败"),
icon: "none",
});
} finally {
if (isLoadMore) {
this.loadingMore = false;
}
}
},
loadMore() {
if (this.loadingMore || !this.hasMore) {
return;
}
this.loadPage(false, this.page + 1);
},
},
};
</script>
<style lang="scss" scoped>
@import "../../styles/tokens.scss";
@import "../../styles/common.scss";
.voucher-page {
min-height: 100vh;
background: #f5f5f5;
}
.voucher-scroll {
padding: 16rpx 12rpx calc(env(safe-area-inset-bottom) + 36rpx);
}
.voucher-header {
margin-top: 6rpx;
}
.voucher-header__bg {
position: relative;
width: 100%;
height: 206rpx;
overflow: visible;
}
.voucher-header__bg-image {
width: 100%;
height: 206rpx;
border-radius: 24rpx;
}
.voucher-header__content {
position: absolute;
inset: 0;
display: flex;
flex-direction: column;
align-items: center;
}
.voucher-header__current {
position: absolute;
bottom: 130rpx;
display: flex;
align-items: baseline;
justify-content: center;
width: 618rpx;
color: #ffffff;
}
.voucher-header__current-label {
font-size: 36rpx;
font-weight: 500;
}
.voucher-header__current-value {
margin-left: 8rpx;
font-size: 48rpx;
font-weight: 700;
}
.voucher-header__totals {
position: absolute;
top: 100rpx;
left: 0;
right: 0;
display: flex;
justify-content: space-around;
color: #ffffff;
}
.voucher-header__total-item {
position: relative;
text-align: center;
}
.voucher-header__total-value {
display: block;
font-size: 42rpx;
font-weight: 600;
line-height: 1.2;
}
.voucher-header__total-text {
display: flex;
align-items: center;
justify-content: center;
margin-top: 6rpx;
font-size: 28rpx;
}
.voucher-header__help-icon {
width: 30rpx;
margin-left: 10rpx;
}
.voucher-tooltip {
position: absolute;
top: calc(100% + 14rpx);
width: 360rpx;
max-width: calc(100vw - 48rpx);
padding: 16rpx 18rpx;
border-radius: 12rpx;
background: #333333;
color: #ffffff;
z-index: 30;
text-align: left;
box-shadow: 0 12rpx 24rpx rgba(0, 0, 0, 0.18);
}
.voucher-tooltip--left {
left: 0;
right: auto;
}
.voucher-tooltip--right {
right: 0;
left: auto;
}
.voucher-tooltip__row {
display: flex;
align-items: flex-start;
}
.voucher-tooltip__text {
flex: 1;
min-width: 0;
font-size: 24rpx;
line-height: 1.6;
}
.voucher-tooltip__close {
width: 44rpx;
height: 44rpx;
margin-left: 14rpx;
flex-shrink: 0;
}
.voucher-tooltip__close image {
width: 44rpx;
height: 44rpx;
}
.voucher-tooltip__arrow {
position: absolute;
top: -14rpx;
right: 60rpx;
width: 0;
height: 0;
border-left: 12rpx solid transparent;
border-right: 12rpx solid transparent;
border-bottom: 16rpx solid #333333;
}
.voucher-tooltip__arrow--left {
left: 80rpx;
right: auto;
}
.voucher-wrapper {
margin-top: 20rpx;
}
.voucher-nav {
display: flex;
justify-content: center;
}
.voucher-tabs {
display: flex;
width: 100%;
padding: 0 8rpx;
}
.voucher-tabs__item {
position: relative;
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-width: 0;
padding: 8rpx 0 18rpx;
}
.voucher-tabs__text {
font-size: 30rpx;
color: #000;
}
.voucher-tabs__item.is-active .voucher-tabs__text {
color: #ec0208;
font-weight: 500;
}
.voucher-tabs__line {
position: absolute;
bottom: 0;
width: 60rpx;
height: 16rpx;
}
.voucher-list {
width: 100%;
margin-top: 20rpx;
border-radius: 20rpx;
}
.voucher-card {
width: 100%;
margin-bottom: 20rpx;
padding: 34rpx 28rpx 20rpx;
border-radius: 22rpx;
background: #ffffff;
overflow: hidden;
}
.voucher-card__head {
display: flex;
align-items: flex-start;
justify-content: space-between;
margin-bottom: 20rpx;
gap: 20rpx;
}
.voucher-card__month-text {
font-size: 32rpx;
line-height: 44rpx;
color: #000000;
}
.voucher-card__totals {
flex-shrink: 0;
font-size: 28rpx;
line-height: 40rpx;
color: #000000;
}
.voucher-card__total-text + .voucher-card__total-text {
margin-top: 6rpx;
}
.voucher-card__total-income {
margin-left: 8rpx;
color: #fc2838;
}
.voucher-card__total-expense {
margin-left: 8rpx;
color: #40ae36;
}
.voucher-row {
display: flex;
align-items: center;
justify-content: space-between;
min-height: 115rpx;
padding: 10rpx 0;
color: #999999;
}
.voucher-row + .voucher-row {
border-top: 1px solid rgba(0, 0, 0, 0.04);
}
.voucher-row__left {
flex: 1;
min-width: 0;
display: flex;
align-items: center;
margin-right: 20rpx;
}
.voucher-row__icon {
width: 48rpx;
height: 48rpx;
margin-right: 30rpx;
flex-shrink: 0;
}
.voucher-row__icon image {
width: 48rpx;
height: 48rpx;
}
.voucher-row__body {
flex: 1;
min-width: 0;
}
.voucher-row__title {
display: block;
font-size: 26rpx;
color: #000000;
line-height: 1.5;
}
.voucher-row__time {
display: block;
margin-top: 12rpx;
font-size: 24rpx;
color: #999999;
}
.voucher-row__amount {
font-size: 36rpx;
font-weight: 400;
line-height: 1;
}
.voucher-row__amount--income {
color: #e93323;
}
.voucher-row__amount--expense {
color: #16ac57;
}
.voucher-loading {
display: flex;
justify-content: center;
padding: 10rpx 0 0;
}
.voucher-loading__text {
font-size: 24rpx;
color: rgba(255, 255, 255, 0.72);
}
.voucher-empty {
padding: 72rpx 28rpx 120rpx;
text-align: center;
}
.voucher-empty__title {
display: block;
font-size: 30rpx;
font-weight: 700;
color: #ffffff;
}
.voucher-empty__desc {
display: block;
margin-top: 16rpx;
font-size: 24rpx;
line-height: 1.7;
color: rgba(255, 255, 255, 0.68);
}
</style>