权益增加总额统计

This commit is contained in:
zhanchao 2026-09-03 17:58:02 +08:00
parent 01ee09524b
commit 66ae382b55

View File

@ -43,7 +43,7 @@
</view> </view>
<view class="field-block"> <view class="field-block">
<view class="field-label">折扣</view> <view class="field-label">折扣</view>
<picker class="discount-picker" mode="multiSelector" :range="getDiscountPickerRange(entry)" :value="getDiscountPickerValue(entry)" :disabled="!entry.enableDiscount" @change="changeDiscount($event, entry)"> <picker class="discount-picker" :class="{ 'discount-disabled': !entry.enableDiscount }" mode="multiSelector" :range="getDiscountPickerRange(entry)" :value="getDiscountPickerValue(entry)" :disabled="!entry.enableDiscount" @change="changeDiscount($event, entry)">
<view class="number-input picker-value value-text">{{ Number(entry.discount).toFixed(1) }}</view> <view class="number-input picker-value value-text">{{ Number(entry.discount).toFixed(1) }}</view>
</picker> </picker>
</view> </view>
@ -143,7 +143,7 @@
<view class="field-block"> <view class="field-block">
<view class="field-label">折扣</view> <view class="field-label">折扣</view>
<view class="discount-input-wrap"> <view class="discount-input-wrap">
<picker class="discount-picker" mode="multiSelector" :range="getDiscountPickerRange(entry)" :value="getDiscountPickerValue(entry)" :disabled="entry.isFree || !entry.isDiscount" @change="changeDiscount($event, entry)"> <picker class="discount-picker" :class="{ 'discount-disabled': entry.isFree || !entry.isDiscount }" mode="multiSelector" :range="getDiscountPickerRange(entry)" :value="getDiscountPickerValue(entry)" :disabled="entry.isFree || !entry.isDiscount" @change="changeDiscount($event, entry)">
<view class="number-input picker-value value-text">{{ Number(entry.discount).toFixed(1) }}</view> <view class="number-input picker-value value-text">{{ Number(entry.discount).toFixed(1) }}</view>
</picker> </picker>
<checkbox-group v-if="entry.isGift" class="free-check" @change="handleFreeChange($event, entry)"> <checkbox-group v-if="entry.isGift" class="free-check" @change="handleFreeChange($event, entry)">
@ -319,8 +319,40 @@
<template #footer> <template #footer>
<view class="footer-bar"> <view class="footer-bar">
<button class="cancel-button" @click="goBack">取消</button> <view v-if="showBenefitAmount" class="footer-summary">
<button class="submit-button" :loading="saving" @click="submitEntries">生成权益</button> <view class="total-summary" @tap="toggleBenefitDetail">
<text class="summary-label">总金额</text>
<text class="summary-total-value">¥{{ benefitSummary.totalAmount.toFixed(2) }}</text>
<uni-icons :type="benefitDetailVisible ? 'top' : 'bottom'" size="16" color="#6b7280" />
</view>
<text class="detail-trigger" @tap="toggleBenefitDetail">明细</text>
</view>
<view
v-if="showBenefitAmount"
class="benefit-detail-panel"
:class="{ 'benefit-detail-panel-visible': benefitDetailVisible }"
>
<view class="detail-header">
<text class="detail-project-name">权益项目</text>
<text class="detail-quantity">数量</text>
<text class="detail-discount">折扣</text>
<text class="detail-amount">最终金额</text>
</view>
<view v-for="item in benefitDetailItems" :key="item.key" class="detail-row">
<text class="detail-project-name detail-project-name-value">{{ item.name }}</text>
<text class="detail-quantity">{{ item.quantity }}</text>
<text class="detail-discount">{{ item.isFree ? "赠送" : `${item.discount.toFixed(1)}` }}</text>
<text class="detail-amount">¥{{ item.totalAmount.toFixed(2) }}</text>
</view>
<view v-if="!benefitDetailItems.length" class="detail-empty">暂无已选择权益</view>
<view class="detail-row detail-row-total">
<text>合计</text>
<text class="detail-amount">¥{{ benefitSummary.totalAmount.toFixed(2) }}</text>
</view>
</view>
<view class="footer-actions">
<button class="submit-button" :loading="saving" @click="submitEntries">生成权益</button>
</view>
</view> </view>
</template> </template>
</full-page> </full-page>
@ -369,10 +401,67 @@ const todoExecutorPickerVisible = ref(false);
const todoExecutorTarget = ref(null); const todoExecutorTarget = ref(null);
const todoExecutorKeyword = ref(""); const todoExecutorKeyword = ref("");
const todoExecutorRefreshing = ref(false); const todoExecutorRefreshing = ref(false);
const benefitDetailVisible = ref(false);
const todoTypeOptions = Object.entries(TODO_EVENT_TYPE_LABELS).map(([value, label]) => ({ value, label })); const todoTypeOptions = Object.entries(TODO_EVENT_TYPE_LABELS).map(([value, label]) => ({ value, label }));
let searchTimer = null; let searchTimer = null;
let entryKey = 0; let entryKey = 0;
function toggleBenefitDetail() {
benefitDetailVisible.value = !benefitDetailVisible.value;
}
const benefitDetailItems = computed(() => entries.value.flatMap((entry) => {
if (entry.isPackage) {
return (entry.projects || [])
.filter((project) => project.selected)
.map((project) => ({
key: `${entry.key}_${project.key}`,
name: project.project?.projectName || "未命名权益",
quantity: Math.max(0, toNumber(project.usageCount, 1)),
discount: toNumber(project.discount),
totalAmount: Math.max(0, toNumber(project.totalPrice)),
isFree: false,
}));
}
if (!entry.project?._id) return [];
return [{
key: entry.key,
name: entry.project.projectName || "未命名权益",
quantity: Math.max(0, toNumber(entry.usageCount, 1)),
discount: toNumber(entry.discount),
totalAmount: Math.max(0, toNumber(entry.isFree ? 0 : entry.totalPrice)),
isFree: Boolean(entry.isFree),
}];
}));
const benefitSummary = computed(() => entries.value.reduce((summary, entry) => {
if (entry.isPackage) {
summary.selectedCount += 1;
const originalAmount = toNumber(entry.packagePrice || entry.price) * Math.max(0, toNumber(entry.usageCount, 1));
const totalAmount = entry.isFree ? 0 : Math.max(0, toNumber(entry.totalPrice));
summary.originalAmount += originalAmount;
summary.totalAmount += totalAmount;
summary.savedAmount += Math.max(0, originalAmount - totalAmount);
return summary;
}
if (!entry.project?._id) return summary;
summary.selectedCount += 1;
const usageCount = Math.max(0, toNumber(entry.usageCount, 1));
const originalAmount = toNumber(entry.price) * usageCount;
const totalAmount = entry.isFree ? 0 : Math.max(0, toNumber(entry.totalPrice));
summary.originalAmount += originalAmount;
summary.totalAmount += totalAmount;
summary.savedAmount += Math.max(0, originalAmount - totalAmount);
return summary;
}, {
selectedCount: 0,
originalAmount: 0,
savedAmount: 0,
totalAmount: 0,
}));
function formatDate(date) { function formatDate(date) {
const year = date.getFullYear(); const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, "0"); const month = String(date.getMonth() + 1).padStart(2, "0");
@ -1755,6 +1844,18 @@ async function submitEntries() {
width: 100%; width: 100%;
} }
.discount-picker.discount-disabled {
opacity: 0.62;
pointer-events: none;
}
.discount-picker.discount-disabled .number-input {
border-color: #d1d5db;
background: #f3f4f6;
color: #9ca3af;
-webkit-text-fill-color: #9ca3af;
}
.field-row { .field-row {
display: flex; display: flex;
gap: 20rpx; gap: 20rpx;
@ -1876,17 +1977,163 @@ async function submitEntries() {
} }
.footer-bar { .footer-bar {
position: relative;
display: flex; display: flex;
align-items: center;
gap: 20rpx; gap: 20rpx;
padding: 20rpx 24rpx; padding: 14rpx 24rpx 18rpx;
background: rgba(245, 246, 248, 0.96); background: #fff;
border-top: 1rpx solid #eef2f7; border-top: 1rpx solid #eef2f7;
backdrop-filter: blur(12px); box-shadow: 0 -4rpx 16rpx rgba(15, 23, 42, 0.04);
box-sizing: border-box; box-sizing: border-box;
z-index: 10;
} }
.footer-bar button { .footer-summary {
flex: 1; flex: 1;
display: flex;
align-items: center;
gap: 14rpx;
min-width: 0;
}
.total-summary {
display: flex;
flex-direction: row;
align-items: baseline;
gap: 6rpx;
min-width: 0;
white-space: nowrap;
}
.summary-label {
color: #6b7280;
font-size: 22rpx;
line-height: 1.2;
white-space: nowrap;
}
.summary-saving {
color: #16a34a;
}
.summary-total-value {
color: #e85c2f;
font-size: 32rpx;
font-weight: 700;
line-height: 1.2;
white-space: nowrap;
}
.detail-trigger {
flex-shrink: 0;
padding-left: 14rpx;
border-left: 1rpx solid #edf0f3;
color: #6b7280;
font-size: 24rpx;
line-height: 1.2;
white-space: nowrap;
}
.benefit-detail-panel {
position: absolute;
right: 0;
bottom: 100%;
left: 0;
max-height: calc(100vh - 220rpx);
overflow: hidden;
padding: 20rpx 24rpx 18rpx;
border-top: 1rpx solid #eef2f7;
border-radius: 24rpx 24rpx 0 0;
background: #fff;
box-shadow: 0 -8rpx 24rpx rgba(15, 23, 42, 0.08);
box-sizing: border-box;
opacity: 0;
visibility: hidden;
pointer-events: none;
transform: translateY(100%);
transform-origin: bottom center;
transition:
opacity 180ms ease,
transform 180ms ease,
visibility 180ms ease;
}
.benefit-detail-panel-visible {
opacity: 1;
visibility: visible;
pointer-events: auto;
transform: translateY(0);
}
.detail-header,
.detail-row {
display: flex;
align-items: center;
gap: 12rpx;
min-height: 60rpx;
color: #6b7280;
font-size: 27rpx;
}
.detail-header {
min-height: 48rpx;
color: #9ca3af;
font-size: 24rpx;
}
.detail-project-name {
flex: 1;
min-width: 0;
}
.detail-project-name-value {
overflow: hidden;
color: #374151;
text-overflow: ellipsis;
white-space: nowrap;
}
.detail-quantity,
.detail-discount {
flex: 0 0 72rpx;
text-align: center;
white-space: nowrap;
}
.detail-amount {
flex: 0 0 130rpx;
color: #374151;
text-align: right;
white-space: nowrap;
}
.detail-empty {
padding: 22rpx 0;
color: #9ca3af;
font-size: 26rpx;
text-align: center;
}
.detail-row-total {
margin-top: 8rpx;
padding-top: 14rpx;
border-top: 1rpx solid #eef2f7;
color: #1f2937;
font-weight: 600;
}
.detail-row-total .detail-amount {
color: #e85c2f;
font-weight: 700;
}
.footer-actions {
flex: 0 0 250rpx;
}
.footer-actions button {
width: 100%;
min-height: 88rpx; min-height: 88rpx;
padding: 0; padding: 0;
display: flex; display: flex;
@ -1897,12 +2144,6 @@ async function submitEntries() {
line-height: 1.2; line-height: 1.2;
} }
.cancel-button {
color: #0877f1;
background: #fff;
border: 1rpx solid #0877f1;
}
.submit-button { .submit-button {
color: #fff; color: #fff;
background: linear-gradient(135deg, #0f84ff 0%, #0877f1 100%); background: linear-gradient(135deg, #0f84ff 0%, #0877f1 100%);