bm-bmt/pages/assets/points-convert-list.vue

880 lines
20 KiB
Vue
Raw 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 asset-theme points-list-page">
<asset-page-shell title="积分转换" />
<view class="asset-scroll points-list-scroll">
<view class="summary-card">
<text class="summary-card__text">
已选择订单
<text class="summary-card__accent asset-number-font">{{
selectedCount
}}</text>
<text class="summary-card__accent asset-number-font">{{
displaySelectedTotal
}}</text>
积分
</text>
<text
v-if="showTransferPointEstimate"
class="summary-card__extra"
>
可用积分预估值:
<text class="summary-card__accent asset-number-font">{{
displayTransferPointEstimate
}}</text>
</text>
</view>
<view class="selection-card">
<text class="selection-card__title">按订单积分区间筛选</text>
<view class="range-tabs">
<view
v-for="tab in rangeTabs"
:key="tab.key"
class="range-tabs__item"
:class="{ 'range-tabs__item--active': activeRange === tab.key }"
@click="switchRange(tab.key)"
>
{{ tab.label }}
</view>
</view>
<view v-if="filteredItems.length" class="selection-list">
<view
v-for="item in filteredItems"
:key="item.id"
class="selection-item"
:class="{ 'selection-item--disabled': item.disabled }"
@click="toggleItem(item)"
>
<view
class="selection-item__checkbox"
:class="{
'selection-item__checkbox--checked': isChecked(item),
'selection-item__checkbox--disabled': item.disabled,
}"
>
<text
v-if="isChecked(item)"
class="selection-item__checkbox-mark"
>✓</text
>
</view>
<view class="selection-item__main">
<view class="selection-item__title-row">
<text class="selection-item__title">{{ item.title }}</text>
<view
v-if="item.statusText"
class="selection-item__status"
:class="{
'selection-item__status--warning': item.disabled,
}"
>
{{ item.statusText }}
</view>
</view>
<text class="selection-item__time asset-number-font">{{
item.time
}}</text>
</view>
<view class="selection-item__side" style="display: flex; align-items: center;">
<text class="selection-item__label" style="margin-top: 6rpx">积分</text>
<text class="selection-item__amount asset-number-font">{{
item.amount
}}</text>
</view>
</view>
</view>
<view v-else class="selection-empty">
<text class="selection-empty__title">暂无可转换订单</text>
<text class="selection-empty__desc">
当前筛选条件下没有符合条件的积分订单。
</text>
</view>
<view v-if="showLoadMoreState" class="list-load-more">
<text class="list-load-more__text asset-number-font">{{ loadMoreText }}</text>
</view>
</view>
</view>
<view class="bottom-bar">
<view class="bottom-bar__check-all" @click="toggleAll">
<view
class="bottom-bar__checkbox"
:class="{ 'bottom-bar__checkbox--checked': allChecked }"
>
<text v-if="allChecked" class="bottom-bar__checkbox-mark">✓</text>
</view>
<text class="bottom-bar__check-all-text">全选</text>
</view>
<view
class="bottom-bar__button"
:class="{ 'bottom-bar__button--disabled': !canSubmit }"
@click="openConfirm"
>
{{ submitting ? "提交中..." : "确认转换" }}
</view>
</view>
<asset-confirm-popup
class="points-confirm-popup"
:visible="confirmVisible"
title="兑换可用积分"
:confirm-text="submitting ? '提交中...' : '确认'"
cancel-text="取消"
:show-close="false"
:close-on-mask="false"
@cancel="handleConfirmCancel"
@confirm="submit"
>
<view class="points-confirm">
<text class="points-confirm__line">
确认使用
<text class="points-confirm__number asset-number-font">{{
displaySelectedTotal
}}</text>
积分 兑换
<text class="points-confirm__number asset-number-font">{{
displayTransferPointEstimate
}}</text>
可用积分吗?
</text>
<text class="points-confirm__note">
积分成功兑换可用积分不能再换回积分
</text>
</view>
</asset-confirm-popup>
</view>
</template>
<script>
import AssetConfirmPopup from "../../components/asset-confirm-popup.vue";
import AssetPageShell from "../../components/asset-page-shell.vue";
import {
fetchPointsConvertSelection,
submitAssetPointsConvert,
} from "../../api/assets";
export default {
components: {
AssetConfirmPopup,
AssetPageShell,
},
data() {
return {
hasShown: false,
activeRange: "below-20",
submitting: false,
confirmVisible: false,
selectedIds: [],
detail: {
items: [],
},
page: 1,
pageSize: 10,
hasMore: false,
loadingMore: false,
};
},
computed: {
rangeTabs() {
return [
{
key: "below-20",
label: "20以下",
interval: "0,20",
},
{
key: "20-100",
label: "20-100",
interval: "20,100",
},
{
key: "100-200",
label: "100-200",
interval: "100,200",
},
{
key: "200-500",
label: "200-500",
interval: "200,500",
},
{
key: "500-plus",
label: "500以上",
interval: "500,10000",
},
];
},
currentInterval() {
const currentTab = this.rangeTabs.find(
(item) => item.key === this.activeRange,
);
return (currentTab && currentTab.interval) || "0,20";
},
items() {
return Array.isArray(this.detail.items) ? this.detail.items : [];
},
filteredItems() {
return this.items;
},
selectedItems() {
return this.items.filter(
(item) => !item.disabled && this.selectedIds.indexOf(item.id) > -1,
);
},
selectedCount() {
return this.selectedItems.length;
},
selectedTotal() {
return this.selectedItems.reduce(
(total, item) => total + Number(item.amountValue || 0),
0,
);
},
displaySelectedTotal() {
return this.formatAmount(this.selectedTotal);
},
transferPointEstimate() {
return this.selectedItems.reduce(
(total, item) => total + Number(item.transferPointValue || 0),
0,
);
},
displayTransferPointEstimate() {
return this.formatAmount(this.transferPointEstimate);
},
showTransferPointEstimate() {
return this.transferPointEstimate > 0;
},
selectableFilteredItems() {
return this.filteredItems.filter((item) => !item.disabled);
},
allChecked() {
if (!this.selectableFilteredItems.length) {
return false;
}
return this.selectableFilteredItems.every(
(item) => this.selectedIds.indexOf(item.id) > -1,
);
},
canSubmit() {
return this.selectedItems.length > 0 && !this.submitting;
},
showLoadMoreState() {
return this.items.length > 0;
},
loadMoreText() {
if (this.loadingMore) {
return "加载中...";
}
if (this.hasMore) {
return "上拉加载更多";
}
return "没有更多了";
},
},
onLoad() {
this.resetPagingState();
this.loadPage(true, 1);
},
onShow() {
if (this.hasShown) {
this.resetPagingState();
this.loadPage(false, 1);
return;
}
this.hasShown = true;
},
onReachBottom() {
this.loadMore();
},
methods: {
resetPagingState() {
this.page = 1;
this.hasMore = false;
this.loadingMore = false;
},
updatePaging(result, requestedPage, receivedLength) {
const pagination =
result && result.pagination && typeof result.pagination === "object"
? result.pagination
: null;
this.page =
Number(pagination && pagination.page) || Number(requestedPage) || this.page || 1;
if (pagination && typeof pagination.hasMore === "boolean") {
this.hasMore = pagination.hasMore;
return;
}
this.hasMore = receivedLength >= this.pageSize;
},
mergeItems(currentItems, nextItems) {
const mergedMap = {};
const mergedList = [];
const appendItem = (item) => {
const key =
String(item && item.id ? item.id : "").trim() ||
[
item && item.orderSn,
item && item.time,
item && item.title,
item && item.amount,
]
.map((value) => String(value || ""))
.join("-");
if (!key || mergedMap[key]) {
return;
}
mergedMap[key] = true;
mergedList.push(item);
};
(Array.isArray(currentItems) ? currentItems : []).forEach(appendItem);
(Array.isArray(nextItems) ? nextItems : []).forEach(appendItem);
return mergedList;
},
formatAmount(value) {
const number = Number(value || 0);
if (!Number.isFinite(number)) {
return "0";
}
return Math.round(number)
.toString()
.replace(/\B(?=(\d{3})+(?!\d))/g, ",");
},
switchRange(nextRangeKey) {
const targetRangeKey = String(nextRangeKey || "");
if (!targetRangeKey || targetRangeKey === this.activeRange) {
return;
}
this.activeRange = targetRangeKey;
this.selectedIds = [];
this.resetPagingState();
this.loadPage(true, 1);
},
isChecked(item) {
return this.selectedIds.indexOf(item.id) > -1;
},
toggleItem(item) {
if (!item) {
return;
}
const transferPointNum = Number(
item.transfer_point_num !== undefined
? item.transfer_point_num
: item.transferPointValue || 0,
);
if (transferPointNum === 0) {
uni.showToast({
title: "当前订单无可用积分",
icon: "none",
});
return;
}
if (item.disabled) {
return;
}
if (this.isChecked(item)) {
this.selectedIds = this.selectedIds.filter((id) => id !== item.id);
return;
}
this.selectedIds = this.selectedIds.concat(item.id);
},
toggleAll() {
const currentIds = this.selectableFilteredItems.map((item) => item.id);
if (!currentIds.length) {
return;
}
if (this.allChecked) {
this.selectedIds = this.selectedIds.filter(
(id) => currentIds.indexOf(id) === -1,
);
return;
}
this.selectedIds = Array.from(new Set(this.selectedIds.concat(currentIds)));
},
async loadPage(showLoading, targetPage) {
const page = Number(targetPage || 1);
const isLoadMore = page > 1;
if (isLoadMore && this.loadingMore) {
return;
}
if (isLoadMore) {
this.loadingMore = true;
}
try {
const requestOptions =
!isLoadMore && showLoading
? {
showLoading: true,
loadingText: "加载中",
}
: null;
const result = await fetchPointsConvertSelection(
{
page: page,
pageSize: this.pageSize,
interval: this.currentInterval,
},
requestOptions,
);
const incomingItems = Array.isArray(result.items) ? result.items : [];
const previousCount = this.items.length;
const mergedItems = isLoadMore
? this.mergeItems(this.items, incomingItems)
: incomingItems;
this.detail = Object.assign({}, this.detail, result, {
items: mergedItems,
});
const validIds = this.items
.filter((item) => !item.disabled)
.map((item) => item.id);
this.selectedIds = this.selectedIds.filter(
(id) => validIds.indexOf(id) > -1,
);
this.updatePaging(result, page, incomingItems.length);
if (isLoadMore && mergedItems.length <= previousCount) {
this.hasMore = false;
}
} 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);
},
openConfirm() {
if (!this.canSubmit) {
uni.showToast({
title: "请选择可转换订单",
icon: "none",
});
return;
}
this.confirmVisible = true;
},
handleConfirmCancel() {
if (this.submitting) {
return;
}
this.confirmVisible = false;
},
async submit() {
if (this.submitting) {
return;
}
if (!this.canSubmit) {
uni.showToast({
title: "请选择可转换订单",
icon: "none",
});
return;
}
this.submitting = true;
try {
await submitAssetPointsConvert(
{
ids: this.selectedItems.map((item) => item.sourceId || item.id),
},
{
showLoading: true,
loadingText: "转换中",
},
);
this.confirmVisible = false;
uni.showToast({
title: "转换成功",
icon: "none",
});
this.selectedIds = [];
this.resetPagingState();
this.loadPage(false, 1);
} catch (error) {
uni.showToast({
title: error.message || "转换失败",
icon: "none",
});
} finally {
this.submitting = false;
}
},
},
};
</script>
<style lang="scss" scoped>
@import "../../styles/tokens.scss";
@import "../../styles/common.scss";
.points-list-page {
min-height: 100vh;
background: #191e32;
}
.points-list-scroll {
min-height: calc(100vh - env(safe-area-inset-top) - 104rpx);
padding: 12rpx 14rpx calc(env(safe-area-inset-bottom) + 150rpx);
}
.summary-card,
.selection-card,
.bottom-bar {
background: #242944;
}
.summary-card,
.selection-card,
.selection-empty {
border-radius: 10rpx;
box-shadow: 0 12rpx 24rpx rgba(8, 13, 30, 0.14);
}
.summary-card {
position: relative;
overflow: hidden;
padding: 28rpx 26rpx;
}
.summary-card::after {
content: "";
position: absolute;
top: -18rpx;
left: -260rpx;
width: 600rpx;
height: 180rpx;
background: rgba(123, 137, 190, 0.18);
transform: rotate(130deg);
}
.summary-card__text {
position: relative;
z-index: 1;
font-size: 28rpx;
font-weight: 500;
color: #ffffff;
}
.summary-card__accent {
color: #ff8d62;
}
.summary-card__extra {
position: relative;
z-index: 1;
display: block;
margin-top: 12rpx;
font-size: 26rpx;
font-weight: 500;
color: rgba(236, 242, 255, 0.92);
}
.selection-card {
margin-top: 18rpx;
padding: 28rpx 0 0;
overflow: hidden;
}
.selection-card__title {
display: block;
padding: 0 20rpx;
font-size: 28rpx;
font-weight: 500;
color: #ffffff;
}
.range-tabs {
display: flex;
align-items: center;
margin-top: 26rpx;
padding: 0 18rpx;
border-bottom: 1px solid rgba(129, 141, 183, 0.18);
}
.range-tabs__item {
position: relative;
flex: 1;
padding: 0 4rpx 18rpx;
text-align: center;
font-size: 24rpx;
color: rgba(173, 182, 211, 0.92);
}
.range-tabs__item--active {
color: #ff8d62;
font-weight: 600;
}
.range-tabs__item--active::after {
content: "";
position: absolute;
right: 18rpx;
bottom: 0;
left: 18rpx;
height: 4rpx;
border-radius: 999rpx;
background: #ff8d62;
}
.selection-list {
padding: 0 20rpx;
}
.selection-item {
display: flex;
align-items: flex-start;
padding: 28rpx 0;
border-top: 1px solid rgba(129, 141, 183, 0.18);
}
.selection-item:first-child {
border-top: 0;
}
.selection-item--disabled {
opacity: 0.6;
}
.selection-item__checkbox,
.bottom-bar__checkbox {
display: flex;
align-items: center;
justify-content: center;
width: 32rpx;
height: 32rpx;
border-radius: 6rpx;
border: 2rpx solid rgba(255, 255, 255, 0.9);
background: #ffffff;
flex-shrink: 0;
}
.selection-item__checkbox {
margin-top: 6rpx;
}
.selection-item__checkbox--checked,
.bottom-bar__checkbox--checked {
border-color: #29d39c;
background: #29d39c;
}
.selection-item__checkbox--disabled {
border-color: rgba(205, 212, 235, 0.5);
background: rgba(255, 255, 255, 0.92);
}
.selection-item__checkbox-mark,
.bottom-bar__checkbox-mark {
font-size: 22rpx;
line-height: 1;
color: #ffffff;
}
.selection-item__main {
flex: 1;
min-width: 0;
margin-left: 16rpx;
}
.selection-item__title-row {
display: flex;
align-items: center;
flex-wrap: wrap;
}
.selection-item__title {
font-size: 28rpx;
font-weight: 500;
color: #ffffff;
}
.selection-item__status {
margin-left: 14rpx;
padding: 4rpx 14rpx;
border-radius: 999rpx;
background: rgba(255, 141, 98, 0.14);
font-size: 22rpx;
color: #ff8d62;
}
.selection-item__status--warning {
background: rgba(255, 141, 98, 0.18);
}
.selection-item__time {
display: block;
margin-top: 14rpx;
font-size: 22rpx;
color: rgba(158, 170, 204, 0.9);
}
.selection-item__side {
margin-left: 18rpx;
padding-top: 2rpx;
text-align: right;
}
.selection-item__label {
display: block;
font-size: 26rpx;
color: rgba(240, 243, 255, 0.92);
}
.selection-item__amount {
display: block;
margin-top: 10rpx;
font-size: 28rpx;
font-weight: 700;
color: #ff8d62;
}
.selection-empty {
margin: 22rpx 20rpx 30rpx;
padding: 70rpx 24rpx;
text-align: center;
background: rgba(38, 45, 73, 0.72);
}
.selection-empty__title {
display: block;
font-size: 28rpx;
color: #ffffff;
}
.selection-empty__desc {
display: block;
margin-top: 14rpx;
font-size: 22rpx;
color: rgba(158, 170, 204, 0.86);
}
.list-load-more {
display: flex;
justify-content: center;
padding: 12rpx 0 22rpx;
}
.list-load-more__text {
font-size: 22rpx;
color: rgba(173, 182, 211, 0.9);
}
.bottom-bar {
position: fixed;
right: 0;
bottom: 0;
left: 0;
z-index: 10;
display: flex;
align-items: center;
justify-content: space-between;
padding: 18rpx 20rpx calc(env(safe-area-inset-bottom) + 18rpx);
border-top: 1px solid rgba(129, 141, 183, 0.16);
}
.bottom-bar__check-all {
display: flex;
align-items: center;
}
.bottom-bar__check-all-text {
margin-left: 14rpx;
font-size: 28rpx;
color: #ffffff;
}
.bottom-bar__button {
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 170rpx;
height: 78rpx;
padding: 0 34rpx;
border-radius: 999rpx;
background: linear-gradient(135deg, #1ec5ff 0%, #179fe8 100%);
box-shadow: 0 14rpx 24rpx rgba(27, 179, 242, 0.22);
font-size: 32rpx;
font-weight: 600;
color: #ffffff;
}
.bottom-bar__button--disabled {
opacity: 0.5;
}
.points-confirm {
padding: 6rpx 2rpx 4rpx;
}
.points-confirm__line {
display: block;
font-size: 30rpx;
line-height: 1.9;
text-align: center;
color: #edf2ff;
}
.points-confirm__number {
margin: 0 6rpx;
color: #ff8d62;
font-weight: 700;
}
.points-confirm__note {
display: block;
margin-top: 18rpx;
font-size: 24rpx;
line-height: 1.7;
color: rgba(186, 194, 218, 0.9);
}
</style>