<template>
|
<cl-page background-color="#f7f7f7">
|
<view class="transfer-page">
|
<cl-sticky>
|
<view class="tab-bar">
|
<view class="tab-left">
|
<view
|
v-for="tab in tabs"
|
:key="tab.value"
|
class="tab-item"
|
:class="{ 'is-active': activeTab === tab.value }"
|
@tap="switchTab(tab.value)"
|
>
|
<text>{{ tab.label }}</text>
|
<view v-if="activeTab === tab.value" class="tab-line"></view>
|
</view>
|
</view>
|
<view class="tab-right">
|
<text class="balance-chip">余额 {{ carbonPoints }}</text>
|
<view class="add-btn" @tap="startTransfer">转赠他人</view>
|
</view>
|
</view>
|
</cl-sticky>
|
|
<scroll-view
|
class="record-scroll"
|
scroll-y
|
:lower-threshold="80"
|
@scrolltolower="loadMore"
|
:refresher-enabled="true"
|
:refresher-triggered="refreshing"
|
@refresherrefresh="onRefresh"
|
>
|
<view class="record-list">
|
<view v-for="item in list" :key="item.id" class="record-card">
|
<view class="card-header">
|
<text class="source">{{ item.sourceType || item.type }}</text>
|
<text class="amount" :class="item.type === '收入' ? 'is-in' : 'is-out'">
|
{{ item.type === '收入' ? '+' : '-' }}{{ item.amount ?? 0 }}
|
</text>
|
</view>
|
<view class="card-body">
|
<text class="remark">{{ item.remark || "-" }}</text>
|
<text class="time">{{ item.createTime || "-" }}</text>
|
</view>
|
</view>
|
</view>
|
|
<cl-loadmore
|
v-if="list.length > 0"
|
:loading="loading"
|
:finish="finished"
|
:divider="false"
|
/>
|
|
<cl-empty
|
v-if="!loading && list.length === 0"
|
:fixed="false"
|
text="暂无记录"
|
/>
|
</scroll-view>
|
</view>
|
|
<cl-popup
|
v-model="showAmount"
|
direction="center"
|
:close-on-click-overlay="false"
|
background-color="transparent"
|
>
|
<view class="amount-popup" @tap.stop>
|
<text class="amount-title">确认转赠</text>
|
<text class="amount-desc">转给:{{ targetName }}</text>
|
<text class="amount-desc">当前积分:{{ carbonPoints }}</text>
|
<input
|
class="amount-input"
|
type="digit"
|
v-model="transferAmount"
|
placeholder="请输入转赠积分"
|
placeholder-class="placeholder"
|
/>
|
<view class="amount-btns">
|
<button class="btn-cancel" @tap="showAmount = false">取消</button>
|
<button class="btn-ok" :disabled="submitting" @tap="submitTransfer">
|
{{ submitting ? "提交中..." : "确认" }}
|
</button>
|
</view>
|
</view>
|
</cl-popup>
|
</cl-page>
|
</template>
|
|
<script lang="ts" setup>
|
import { computed, ref } from "vue";
|
import { onReady, onPullDownRefresh } from "@dcloudio/uni-app";
|
import { useCool, useStore } from "/@/cool";
|
|
const { service } = useCool();
|
const { user } = useStore();
|
|
const tabs = [
|
{ label: "转赠", value: "gift" },
|
{ label: "收支", value: "io" },
|
] as const;
|
|
type TabValue = (typeof tabs)[number]["value"];
|
|
const activeTab = ref<TabValue>("gift");
|
const list = ref<Eps.RecycleTransactionEntity[]>([]);
|
const loading = ref(false);
|
const refreshing = ref(false);
|
const finished = ref(false);
|
const page = ref(1);
|
const size = 10;
|
|
const carbonPoints = ref(0);
|
const showAmount = ref(false);
|
const submitting = ref(false);
|
const transferAmount = ref("");
|
const targetUnionid = ref("");
|
const targetUser = ref<{ unionid?: string; nickName?: string; phone?: string }>({});
|
|
const targetName = computed(() => {
|
return targetUser.value.nickName || targetUser.value.phone || targetUnionid.value || "-";
|
});
|
|
let reqSeq = 0;
|
|
function getUnionid() {
|
return user.info?.phone || "18918594752";
|
}
|
|
async function loadBalance() {
|
try {
|
const unionid = getUnionid();
|
const res = await service.user.info.dashboard({ unionid });
|
carbonPoints.value = res.carbonPoints ?? 0;
|
} catch (e) {
|
console.error("【碳积分加载失败】", e);
|
}
|
}
|
|
function buildQuery() {
|
return {
|
page: page.value,
|
size,
|
userId: getUnionid(),
|
tab: activeTab.value,
|
};
|
}
|
|
async function loadList(reset = false) {
|
if (loading.value && !reset) return;
|
if (!reset && finished.value) return;
|
|
if (reset) {
|
page.value = 1;
|
finished.value = false;
|
list.value = [];
|
}
|
|
const current = ++reqSeq;
|
loading.value = true;
|
|
try {
|
const res = await service.shop.transaction.page(buildQuery());
|
if (current !== reqSeq) return;
|
|
console.log("【赠送记录返回】", res);
|
|
const newList = res?.list || [];
|
list.value = page.value === 1 ? newList : [...list.value, ...newList];
|
|
const total = res?.pagination?.total;
|
finished.value =
|
newList.length < size || (typeof total === "number" && list.value.length >= total);
|
page.value++;
|
} catch (e) {
|
if (current !== reqSeq) return;
|
console.error("【赠送记录加载失败】", e);
|
} finally {
|
if (current === reqSeq) {
|
loading.value = false;
|
refreshing.value = false;
|
}
|
}
|
}
|
|
function switchTab(val: TabValue) {
|
if (activeTab.value === val) return;
|
activeTab.value = val;
|
loadList(true);
|
}
|
|
function loadMore() {
|
if (!loading.value && !finished.value) {
|
loadList(false);
|
}
|
}
|
|
function onRefresh() {
|
refreshing.value = true;
|
loadBalance();
|
loadList(true);
|
}
|
|
function handleScanResult(code: string) {
|
const toUnionid = String(code || "").trim();
|
if (!toUnionid) {
|
uni.showToast({ title: "未识别到回收码", icon: "none" });
|
return;
|
}
|
if (toUnionid === getUnionid()) {
|
uni.showToast({ title: "不能转赠给自己", icon: "none" });
|
return;
|
}
|
|
service.shop.transaction
|
.checkUser({ unionid: toUnionid })
|
.then((res) => {
|
targetUnionid.value = toUnionid;
|
targetUser.value = res || { unionid: toUnionid };
|
transferAmount.value = "";
|
showAmount.value = true;
|
})
|
.catch((e) => {
|
uni.showToast({
|
title: e?.message || "请选择系统用户作为接受转赠对象",
|
icon: "none",
|
});
|
});
|
}
|
|
function startTransfer() {
|
loadBalance();
|
|
// #ifdef APP-PLUS || MP-WEIXIN
|
uni.scanCode({
|
scanType: ["qrCode"],
|
success(res) {
|
console.log("【扫码结果】", res.result);
|
handleScanResult(res.result);
|
},
|
fail() {
|
uni.showToast({ title: "扫码取消", icon: "none" });
|
},
|
});
|
// #endif
|
|
// #ifdef H5
|
uni.showModal({
|
title: "模拟扫码",
|
editable: true,
|
placeholderText: "请输入对方回收码",
|
success(res) {
|
if (res.confirm && res.content) {
|
handleScanResult(res.content);
|
}
|
},
|
});
|
// #endif
|
}
|
|
async function submitTransfer() {
|
const amount = Number(transferAmount.value);
|
if (!amount || amount <= 0) {
|
uni.showToast({ title: "请输入转赠积分", icon: "none" });
|
return;
|
}
|
if (amount > carbonPoints.value) {
|
uni.showToast({ title: "碳积分不足", icon: "none" });
|
return;
|
}
|
|
submitting.value = true;
|
try {
|
await service.shop.transaction.transfer({
|
fromUnionid: getUnionid(),
|
toUnionid: targetUnionid.value,
|
amount,
|
});
|
uni.showToast({ title: "转赠成功", icon: "success" });
|
showAmount.value = false;
|
transferAmount.value = "";
|
activeTab.value = "gift";
|
await loadBalance();
|
loadList(true);
|
} catch (e: any) {
|
console.error("【转赠失败】", e);
|
uni.showToast({ title: e?.message || "转赠失败,请重试", icon: "none" });
|
} finally {
|
submitting.value = false;
|
}
|
}
|
|
onReady(() => {
|
loadBalance();
|
loadList(true);
|
});
|
|
onPullDownRefresh(() => {
|
loadBalance();
|
loadList(true).finally(() => {
|
uni.stopPullDownRefresh();
|
});
|
});
|
</script>
|
|
<style lang="scss" scoped>
|
.transfer-page {
|
height: 100vh;
|
display: flex;
|
flex-direction: column;
|
|
.tab-bar {
|
display: flex;
|
align-items: center;
|
justify-content: space-between;
|
background: #fff;
|
padding: 0 24rpx 0 16rpx;
|
border-bottom: 1rpx solid #f0f0f0;
|
}
|
|
.tab-left {
|
display: flex;
|
align-items: center;
|
}
|
|
.tab-item {
|
position: relative;
|
padding: 28rpx 24rpx;
|
font-size: 28rpx;
|
color: #666;
|
|
&.is-active {
|
color: #2ecc71;
|
font-weight: 600;
|
}
|
|
.tab-line {
|
position: absolute;
|
bottom: 0;
|
left: 50%;
|
transform: translateX(-50%);
|
width: 40rpx;
|
height: 4rpx;
|
background: #2ecc71;
|
border-radius: 2rpx;
|
}
|
}
|
|
.tab-right {
|
display: flex;
|
align-items: center;
|
gap: 12rpx;
|
}
|
|
.balance-chip {
|
font-size: 22rpx;
|
color: #27ae60;
|
background: #e8f8f0;
|
padding: 6rpx 14rpx;
|
border-radius: 20rpx;
|
}
|
|
.add-btn {
|
padding: 10rpx 22rpx;
|
border-radius: 28rpx;
|
background: #2ecc71;
|
color: #fff;
|
font-size: 24rpx;
|
font-weight: 500;
|
}
|
|
.record-scroll {
|
flex: 1;
|
height: 0;
|
padding: 24rpx;
|
box-sizing: border-box;
|
}
|
|
.record-list {
|
display: flex;
|
flex-direction: column;
|
gap: 20rpx;
|
}
|
|
.record-card {
|
background: #fff;
|
border-radius: 20rpx;
|
padding: 28rpx;
|
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.04);
|
}
|
|
.card-header {
|
display: flex;
|
justify-content: space-between;
|
align-items: center;
|
padding-bottom: 16rpx;
|
border-bottom: 1rpx solid #f5f5f5;
|
}
|
|
.source {
|
font-size: 26rpx;
|
color: #333;
|
font-weight: 500;
|
}
|
|
.amount {
|
font-size: 32rpx;
|
font-weight: 700;
|
|
&.is-in {
|
color: #27ae60;
|
}
|
|
&.is-out {
|
color: #e74c3c;
|
}
|
}
|
|
.card-body {
|
padding-top: 16rpx;
|
display: flex;
|
flex-direction: column;
|
gap: 8rpx;
|
}
|
|
.remark {
|
font-size: 26rpx;
|
color: #555;
|
line-height: 1.4;
|
}
|
|
.time {
|
font-size: 22rpx;
|
color: #999;
|
}
|
}
|
|
.amount-popup {
|
width: 560rpx;
|
background: #fff;
|
border-radius: 24rpx;
|
padding: 40rpx 36rpx;
|
display: flex;
|
flex-direction: column;
|
}
|
|
.amount-title {
|
font-size: 32rpx;
|
font-weight: 700;
|
color: #333;
|
text-align: center;
|
margin-bottom: 24rpx;
|
}
|
|
.amount-desc {
|
font-size: 26rpx;
|
color: #666;
|
margin-bottom: 12rpx;
|
}
|
|
.amount-input {
|
height: 80rpx;
|
margin: 16rpx 0 32rpx;
|
padding: 0 24rpx;
|
background: #f7f7f7;
|
border-radius: 12rpx;
|
font-size: 30rpx;
|
color: #333;
|
}
|
|
.placeholder {
|
color: #ccc;
|
}
|
|
.amount-btns {
|
display: flex;
|
gap: 20rpx;
|
|
button {
|
flex: 1;
|
height: 70rpx;
|
line-height: 70rpx;
|
border-radius: 35rpx;
|
font-size: 28rpx;
|
border: none;
|
}
|
|
.btn-cancel {
|
background: #f5f5f5;
|
color: #666;
|
}
|
|
.btn-ok {
|
background: linear-gradient(135deg, #2ecc71, #27ae60);
|
color: #fff;
|
font-weight: 600;
|
|
&[disabled] {
|
opacity: 0.6;
|
}
|
}
|
}
|
</style>
|