fix CustomerPackageDetail.vue style
This commit is contained in:
@@ -78,6 +78,7 @@
|
|||||||
--td-error-color-focus: var(--td-error-color-2);
|
--td-error-color-focus: var(--td-error-color-2);
|
||||||
--td-error-color-active: var(--td-error-color-7);
|
--td-error-color-active: var(--td-error-color-7);
|
||||||
--td-error-color-disabled: var(--td-error-color-3);
|
--td-error-color-disabled: var(--td-error-color-3);
|
||||||
|
--td-button-danger-text-disabled-color: var(--td-error-color-disabled);
|
||||||
--td-error-color-light: var(--td-error-color-1);
|
--td-error-color-light: var(--td-error-color-1);
|
||||||
--td-error-color-light-active: var(--td-error-color-2);
|
--td-error-color-light-active: var(--td-error-color-2);
|
||||||
--td-success-color-focus: var(--td-success-color-2);
|
--td-success-color-focus: var(--td-success-color-2);
|
||||||
@@ -203,6 +204,7 @@
|
|||||||
--td-error-color-focus: var(--td-error-color-2);
|
--td-error-color-focus: var(--td-error-color-2);
|
||||||
--td-error-color-active: var(--td-error-color-5);
|
--td-error-color-active: var(--td-error-color-5);
|
||||||
--td-error-color-disabled: var(--td-error-color-3);
|
--td-error-color-disabled: var(--td-error-color-3);
|
||||||
|
--td-button-danger-text-disabled-color: var(--td-error-color-disabled);
|
||||||
--td-error-color-light: var(--td-error-color-1);
|
--td-error-color-light: var(--td-error-color-1);
|
||||||
--td-error-color-light-active: var(--td-error-color-2);
|
--td-error-color-light-active: var(--td-error-color-2);
|
||||||
--td-success-color-focus: var(--td-success-color-2);
|
--td-success-color-focus: var(--td-success-color-2);
|
||||||
|
|||||||
@@ -1,94 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div v-can="permissionCodes.customerPackage.cancel" class="package-operation-edit">
|
|
||||||
<t-cell-group v-if="customerPackage" title="待取消套餐" theme="card" v-entrance-transition>
|
|
||||||
<t-cell title="客户" :note="customerPackage.customer?.name || customerPackage.customerId || '未知客户'" />
|
|
||||||
<t-cell title="套餐" :note="customerPackage.packageNameSnapshot || '未命名套餐'" />
|
|
||||||
<t-cell title="当前状态" :note="getCustomerPackageStatusText(customerPackage.status)" />
|
|
||||||
</t-cell-group>
|
|
||||||
<t-form class="form" label-align="left" v-entrance-transition="70">
|
|
||||||
<t-form-item label="取消原因" required-mark>
|
|
||||||
<t-textarea v-model.trim="reason" borderless autosize maxlength="500" placeholder="请输入取消原因" />
|
|
||||||
</t-form-item>
|
|
||||||
</t-form>
|
|
||||||
<t-empty v-if="!customerPackage" :description="isLoading ? '正在加载套餐' : '套餐不存在'" />
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { useUserStore } from "timi-web-api";
|
|
||||||
import PackageAPI from "@/api/PackageAPI";
|
|
||||||
import { useNavBarStore } from "@/store/navBarStore";
|
|
||||||
import type { GaoCustomerPackage } from "@/types/Package";
|
|
||||||
import { getCustomerPackageStatusText } from "@/utils/package";
|
|
||||||
import { getErrorMessage } from "@/utils/error";
|
|
||||||
import { Message } from "@/utils/message";
|
|
||||||
import { permissionCodes } from "@/utils/permission";
|
|
||||||
|
|
||||||
defineOptions({ name: "CustomerPackageCancel" });
|
|
||||||
|
|
||||||
const route = useRoute();
|
|
||||||
const router = useRouter();
|
|
||||||
const userStore = useUserStore();
|
|
||||||
const navBarStore = useNavBarStore();
|
|
||||||
const customerPackage = ref<GaoCustomerPackage>();
|
|
||||||
const reason = ref("");
|
|
||||||
const isLoading = ref(false);
|
|
||||||
const isSaving = ref(false);
|
|
||||||
|
|
||||||
async function loadData(): Promise<void> {
|
|
||||||
const id = String(route.params.id || "");
|
|
||||||
if (!id) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
isLoading.value = true;
|
|
||||||
try {
|
|
||||||
customerPackage.value = await PackageAPI.customerPackageDetail(id);
|
|
||||||
} catch (error) {
|
|
||||||
Message.error(getErrorMessage(error, "套餐加载失败"));
|
|
||||||
} finally {
|
|
||||||
isLoading.value = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function save(): Promise<void> {
|
|
||||||
if (isSaving.value) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!customerPackage.value?.id || !reason.value.trim()) {
|
|
||||||
Message.warning("请输入取消原因");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
isSaving.value = true;
|
|
||||||
try {
|
|
||||||
await PackageAPI.cancel(customerPackage.value.id, reason.value.trim());
|
|
||||||
Message.success("客户套餐已取消");
|
|
||||||
await router.replace(`/management/customer-packages/${customerPackage.value.id}`);
|
|
||||||
} catch (error) {
|
|
||||||
Message.error(getErrorMessage(error, "套餐取消失败,请刷新后确认状态"));
|
|
||||||
} finally {
|
|
||||||
isSaving.value = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(async () => {
|
|
||||||
if (userStore.hasPermissions(permissionCodes.customerPackage.cancel)) {
|
|
||||||
navBarStore.setRightText("确认取消", "var(--td-danger-color)", save);
|
|
||||||
}
|
|
||||||
await loadData();
|
|
||||||
});
|
|
||||||
|
|
||||||
onUnmounted(() => navBarStore.clearRight());
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped lang="less">
|
|
||||||
.package-operation-edit {
|
|
||||||
gap: 1rem;
|
|
||||||
padding: calc(var(--app-nav-height) + 1rem) 0 2rem;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
|
|
||||||
.form {
|
|
||||||
border-radius: .75rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -34,10 +34,18 @@ import PackagePhotoField from "@/components/PackagePhotoField.vue";
|
|||||||
import { useNavBarStore } from "@/store/navBarStore";
|
import { useNavBarStore } from "@/store/navBarStore";
|
||||||
import type { GaoCustomerPackage, PackageAttachment } from "@/types/Package";
|
import type { GaoCustomerPackage, PackageAttachment } from "@/types/Package";
|
||||||
import type { PhotoItem } from "@/types/Photo";
|
import type { PhotoItem } from "@/types/Photo";
|
||||||
import { comparePackageQuantity, createPackageRequestNo, getPackageQuantityText, getPaymentStatusText, normalizePackageQuantity } from "@/utils/package";
|
import {
|
||||||
|
canCreatePackageDelivery,
|
||||||
|
comparePackageQuantity,
|
||||||
|
createPackageRequestNo,
|
||||||
|
getPackageQuantityText,
|
||||||
|
getPaymentStatusText,
|
||||||
|
isStoreManagerRequiredForPackageDelivery,
|
||||||
|
normalizePackageQuantity
|
||||||
|
} from "@/utils/package";
|
||||||
import { getErrorMessage } from "@/utils/error";
|
import { getErrorMessage } from "@/utils/error";
|
||||||
import { Message } from "@/utils/message";
|
import { Message } from "@/utils/message";
|
||||||
import { permissionCodes } from "@/utils/permission";
|
import { gaoRoleCodes, permissionCodes } from "@/utils/permission";
|
||||||
|
|
||||||
defineOptions({ name: "CustomerPackageDelivery" });
|
defineOptions({ name: "CustomerPackageDelivery" });
|
||||||
|
|
||||||
@@ -54,6 +62,10 @@ const deliveredDraft = ref("");
|
|||||||
const deliveredPickerVisible = ref(false);
|
const deliveredPickerVisible = ref(false);
|
||||||
const isLoading = ref(false);
|
const isLoading = ref(false);
|
||||||
const isSaving = ref(false);
|
const isSaving = ref(false);
|
||||||
|
const isStoreManager = computed(() => userStore.hasRoles(gaoRoleCodes.storeManager) && !userStore.hasRoles(gaoRoleCodes.globalManager));
|
||||||
|
const canCreateDelivery = computed(() => canCreatePackageDelivery(customerPackage.value)
|
||||||
|
&& (!isStoreManagerRequiredForPackageDelivery(customerPackage.value) || isStoreManager.value)
|
||||||
|
&& userStore.hasPermissions(permissionCodes.packageDelivery.create));
|
||||||
|
|
||||||
function confirmDeliveredAt(value: string | number): void {
|
function confirmDeliveredAt(value: string | number): void {
|
||||||
deliveredAtText.value = String(value);
|
deliveredAtText.value = String(value);
|
||||||
@@ -129,8 +141,12 @@ async function save(): Promise<void> {
|
|||||||
if (!customerPackageId || !itemList) {
|
if (!customerPackageId || !itemList) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (customerPackage.value?.paymentStatus !== "PAID") {
|
if (!canCreatePackageDelivery(customerPackage.value)) {
|
||||||
Message.warning("套餐未付清,不能提交发放");
|
Message.warning("套餐未有已核销收款,不能提交发放");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (isStoreManagerRequiredForPackageDelivery(customerPackage.value) && !isStoreManager.value) {
|
||||||
|
Message.warning("部分收款套餐仅店长可发放");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!photoItems.value.some(item => item.tempFileId)) {
|
if (!photoItems.value.some(item => item.tempFileId)) {
|
||||||
@@ -162,10 +178,10 @@ async function save(): Promise<void> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
if (userStore.hasPermissions(permissionCodes.packageDelivery.create)) {
|
await loadData();
|
||||||
|
if (canCreateDelivery.value) {
|
||||||
navBarStore.setRightText("提交发放", "var(--td-brand-color)", save);
|
navBarStore.setRightText("提交发放", "var(--td-brand-color)", save);
|
||||||
}
|
}
|
||||||
await loadData();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
onUnmounted(() => navBarStore.clearRight());
|
onUnmounted(() => navBarStore.clearRight());
|
||||||
|
|||||||
@@ -2,44 +2,100 @@
|
|||||||
<div v-can="permissionCodes.customerPackage.read" class="customer-package-detail">
|
<div v-can="permissionCodes.customerPackage.read" class="customer-package-detail">
|
||||||
<template v-if="customerPackage">
|
<template v-if="customerPackage">
|
||||||
<customer-profile-card class="customer" :customer="customerPackage.customer" v-entrance-transition />
|
<customer-profile-card class="customer" :customer="customerPackage.customer" v-entrance-transition />
|
||||||
<section class="hero" v-entrance-transition="40">
|
<t-cell-group class="package" theme="card">
|
||||||
<div class="title-row">
|
<t-cell>
|
||||||
<h2 v-text="customerPackage.packageNameSnapshot || '未命名套餐'" />
|
<template #title>
|
||||||
<t-tag :content="getCustomerPackageStatusText(customerPackage.status)" variant="light-outline" :theme="getStatusTheme(customerPackage.status)" />
|
<div class="title">
|
||||||
</div>
|
<h3 class="value" v-text="customerPackage.packageNameSnapshot || '未命名套餐'" />
|
||||||
<div class="meta" v-text="customerPackage.purchasedAt ? `购买于 ${Time.toDateTime(customerPackage.purchasedAt)}` : '购买时间未知'" />
|
<t-tag :content="getCustomerPackageStatusText(customerPackage.status)" variant="light-outline" :theme="getStatusTheme(customerPackage.status)" />
|
||||||
</section>
|
</div>
|
||||||
<t-cell-group title="金额与状态" theme="card" v-entrance-transition="70">
|
</template>
|
||||||
<t-cell title="标准价快照" :note="formatPackageAmount(customerPackage.standardAmountSnapshot)" />
|
<template #description>
|
||||||
<t-cell title="成交价" :note="formatPackageAmount(customerPackage.saleAmount)" />
|
<span>购买于</span>
|
||||||
<t-cell title="已核销收款" :note="formatPackageAmount(customerPackage.paidAmount)" />
|
<pass-time-label class="word-space" :timestamp="customerPackage.purchasedAt" underline />
|
||||||
<t-cell title="待核销收款" :note="formatPackageAmount(customerPackage.pendingPaidAmount)" />
|
</template>
|
||||||
<t-cell title="有效退款" :note="formatPackageAmount(customerPackage.refundedAmount)" />
|
|
||||||
<t-cell title="收款状态" :note="getPaymentStatusText(customerPackage.paymentStatus)" />
|
|
||||||
<t-cell title="退款状态" :note="getRefundStatusText(customerPackage.refundStatus)" />
|
|
||||||
</t-cell-group>
|
|
||||||
<t-cell-group title="产品权益" theme="card" v-entrance-transition="140">
|
|
||||||
<t-cell v-for="item in customerPackage.itemList || []" :key="item.id" :title="item.productNameSnapshot" :description="`${getPackageQuantityText(item.deliveredQuantity)} ${item.unitSnapshot} 已核销`">
|
|
||||||
<template #note><div class="quantity" v-text="`可发 ${getPackageQuantityText(item.availableQuantity)}|待核销 ${getPackageQuantityText(item.pendingQuantity)}`" /></template>
|
|
||||||
</t-cell>
|
</t-cell>
|
||||||
<div v-if="!customerPackage.itemList?.length" class="empty" v-text="'暂无产品权益'" />
|
|
||||||
</t-cell-group>
|
|
||||||
<t-cell-group v-if="customerPackage.purchaseEventRecord" title="购买记录照片" theme="card" v-entrance-transition="210">
|
|
||||||
<t-cell
|
<t-cell
|
||||||
v-if="canReadEventRecord && customerPackage.purchaseEventRecord.id"
|
v-if="canReadPackageConfig && customerPackage.packageConfigId"
|
||||||
|
title="套餐方案详情"
|
||||||
|
arrow
|
||||||
|
hover
|
||||||
|
@click="toPackageConfigDetail"
|
||||||
|
/>
|
||||||
|
<t-cell
|
||||||
|
v-if="canReadEventRecord && customerPackage.purchaseEventRecord?.id"
|
||||||
title="购买登记记录"
|
title="购买登记记录"
|
||||||
note="查看关联的事件登记记录"
|
note="查看关联的事件登记记录"
|
||||||
arrow
|
arrow
|
||||||
hover
|
|
||||||
@click="toPurchaseEventRecord"
|
@click="toPurchaseEventRecord"
|
||||||
/>
|
/>
|
||||||
<photo-scroller :model-value="purchasePhotoURLList" :photo-items="purchasePhotoItems" title="购买照片" :show-add="false" :show-remove="false" :show-viewer-actions="false" />
|
|
||||||
</t-cell-group>
|
</t-cell-group>
|
||||||
|
<t-cell-group title="金额" theme="card" v-entrance-transition="70">
|
||||||
|
<t-cell class="amount standard" title="标准价快照" :note="formatPackageAmount(customerPackage.standardAmountSnapshot)" />
|
||||||
|
<t-cell class="amount sale" title="成交价" :note="formatPackageAmount(customerPackage.saleAmount)" />
|
||||||
|
<t-cell class="amount paid" title="已核销收款" :note="formatPackageAmount(customerPackage.paidAmount)" />
|
||||||
|
<t-cell class="amount pending" title="待核销收款" :note="formatPackageAmount(customerPackage.pendingPaidAmount)" />
|
||||||
|
<t-cell class="amount remaining" title="剩余待收款" :note="formatPackageAmount(getRemainingReceivableAmount(customerPackage))" />
|
||||||
|
<t-cell class="amount refunded" title="有效退款" :note="formatPackageAmount(customerPackage.refundedAmount)" />
|
||||||
|
<t-cell
|
||||||
|
v-if="canReadFund && 0 < (customerPackage.pendingPaidAmount || 0)"
|
||||||
|
title="去核销收款"
|
||||||
|
note="查看待核销的登记收款"
|
||||||
|
arrow
|
||||||
|
hover
|
||||||
|
@click="toPendingFundList"
|
||||||
|
/>
|
||||||
|
</t-cell-group>
|
||||||
|
<t-cell-group title="状态" theme="card" v-entrance-transition="140">
|
||||||
|
<t-cell title="收款状态" :note="getPaymentStatusText(customerPackage.paymentStatus)" />
|
||||||
|
<t-cell title="退款状态" :note="getRefundStatusText(customerPackage.refundStatus)" />
|
||||||
|
</t-cell-group>
|
||||||
|
<card-container description="产品权益" v-entrance-transition="210">
|
||||||
|
<t-cell-group>
|
||||||
|
<div
|
||||||
|
v-for="item in customerPackage.itemList || []"
|
||||||
|
:key="item.id"
|
||||||
|
class="benefit"
|
||||||
|
>
|
||||||
|
<div class="head">
|
||||||
|
<strong v-text="item.productNameSnapshot || '未命名产品'" />
|
||||||
|
<t-tag theme="primary" variant="light" :content="item.unitSnapshot || '未设置单位'" />
|
||||||
|
</div>
|
||||||
|
<div class="stats">
|
||||||
|
<div class="stat">
|
||||||
|
<span>总数</span>
|
||||||
|
<strong v-text="getPackageQuantityText(item.totalQuantity)" />
|
||||||
|
</div>
|
||||||
|
<div class="stat available">
|
||||||
|
<span>可发</span>
|
||||||
|
<strong v-text="getPackageQuantityText(item.availableQuantity)" />
|
||||||
|
</div>
|
||||||
|
<div class="stat pending">
|
||||||
|
<span>待核销</span>
|
||||||
|
<strong v-text="getPackageQuantityText(item.pendingQuantity)" />
|
||||||
|
</div>
|
||||||
|
<div class="stat delivered">
|
||||||
|
<span>已核销</span>
|
||||||
|
<strong v-text="getPackageQuantityText(item.deliveredQuantity)" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-if="!customerPackage.itemList?.length" class="empty" v-text="'暂无产品权益'" />
|
||||||
|
</t-cell-group>
|
||||||
|
</card-container>
|
||||||
<section class="actions" v-entrance-transition="240">
|
<section class="actions" v-entrance-transition="240">
|
||||||
<t-button v-if="canReceivePayment" theme="primary" variant="outline" @click="toPayment">登记收款</t-button>
|
<t-button
|
||||||
<t-button v-if="canCreateDelivery" theme="primary" variant="outline" @click="toDelivery">创建发放单</t-button>
|
v-if="canReceivePayment"
|
||||||
<t-button v-if="canCancelPackage" theme="default" variant="outline" @click="toCancel">取消套餐</t-button>
|
class="action"
|
||||||
<t-button v-if="canRefund" theme="danger" variant="outline" @click="toRefund">处理退款</t-button>
|
theme="primary"
|
||||||
|
@click="toPayment"
|
||||||
|
>登记收款</t-button>
|
||||||
|
<t-button
|
||||||
|
v-if="canCreateDelivery"
|
||||||
|
class="action"
|
||||||
|
theme="primary"
|
||||||
|
@click="toDelivery"
|
||||||
|
>创建发放单</t-button>
|
||||||
</section>
|
</section>
|
||||||
<t-cell-group title="业务记录" theme="card" v-entrance-transition="280">
|
<t-cell-group title="业务记录" theme="card" v-entrance-transition="280">
|
||||||
<t-cell v-if="canReadDelivery" title="发放历史" :note="`${customerPackage.deliveryList?.length || 0} 条`" arrow hover @click="toDeliveryList" />
|
<t-cell v-if="canReadDelivery" title="发放历史" :note="`${customerPackage.deliveryList?.length || 0} 条`" arrow hover @click="toDeliveryList" />
|
||||||
@@ -58,45 +114,67 @@
|
|||||||
<template #note><span v-text="getDeliveryItemSummary(item)" /></template>
|
<template #note><span v-text="getDeliveryItemSummary(item)" /></template>
|
||||||
</t-cell>
|
</t-cell>
|
||||||
</t-cell-group>
|
</t-cell-group>
|
||||||
<t-cell-group v-if="customerPackage.fundRecordList?.length" title="最近资金记录" theme="card" v-entrance-transition="360">
|
<t-button
|
||||||
<t-cell
|
v-if="canCancelPackage"
|
||||||
v-for="item in customerPackage.fundRecordList"
|
class="cell-button"
|
||||||
:key="item.id"
|
theme="danger"
|
||||||
:title="`${getFundOperationText(item.operationType)} · ${getFundStatusText(item.status)}`"
|
@click="toCancel"
|
||||||
:description="item.occurredAt ? Time.toDateTime(item.occurredAt) : '时间未知'"
|
>取消套餐</t-button>
|
||||||
arrow
|
<t-button
|
||||||
hover
|
v-if="canRefund"
|
||||||
@click="toFundDetail(item)"
|
class="cell-button bg-orange white"
|
||||||
>
|
variant="text"
|
||||||
<template #note><span v-text="formatPackageAmount(item.amount)" /></template>
|
@click="toRefund"
|
||||||
</t-cell>
|
>处理退款</t-button>
|
||||||
</t-cell-group>
|
|
||||||
</template>
|
</template>
|
||||||
<t-empty v-else :description="isLoading ? '正在加载客户套餐' : '客户套餐不存在'" />
|
<t-empty v-else :description="isLoading ? '正在加载客户套餐' : '客户套餐不存在'" />
|
||||||
|
<bottom-popup
|
||||||
|
v-model="cancelPopupVisible"
|
||||||
|
title="取消客户套餐"
|
||||||
|
height="min(35vh, 20rem)"
|
||||||
|
left-button-text="取消"
|
||||||
|
right-button-text="确认取消"
|
||||||
|
@left-button-click="closeCancelPopup"
|
||||||
|
@right-button-click="confirmCancel"
|
||||||
|
>
|
||||||
|
<template #rightButton="{ onClick }">
|
||||||
|
<t-button
|
||||||
|
variant="text"
|
||||||
|
size="small"
|
||||||
|
theme="danger"
|
||||||
|
:loading="isCancelling"
|
||||||
|
:disabled="!canConfirmCancel || isCancelling"
|
||||||
|
@click="onClick"
|
||||||
|
>确认取消</t-button>
|
||||||
|
</template>
|
||||||
|
<t-form class="cancel-form" label-align="top">
|
||||||
|
<t-form-item label="取消原因" required-mark>
|
||||||
|
<t-textarea v-model.trim="cancelReason" borderless autosize maxlength="500" placeholder="请输入取消原因" />
|
||||||
|
</t-form-item>
|
||||||
|
</t-form>
|
||||||
|
</bottom-popup>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { Time } from "timi-web";
|
import { BottomPopup } from "timi-tdesign-mobile";
|
||||||
|
import { PassTimeLabel, Time } from "timi-web";
|
||||||
import { useUserStore } from "timi-web-api";
|
import { useUserStore } from "timi-web-api";
|
||||||
import PackageAPI from "@/api/PackageAPI";
|
import PackageAPI from "@/api/PackageAPI";
|
||||||
import CustomerProfileCard from "@/components/CustomerProfileCard.vue";
|
import CustomerProfileCard from "@/components/CustomerProfileCard.vue";
|
||||||
import PhotoScroller from "@/components/PhotoScroller.vue";
|
|
||||||
import { useCurrentStoreStore } from "@/store/currentStoreStore";
|
import { useCurrentStoreStore } from "@/store/currentStoreStore";
|
||||||
import type { GaoCustomerPackage, GaoFundRecord, GaoPackageDelivery, CustomerPackageStatus } from "@/types/Package";
|
import type { CustomerPackageStatus, GaoCustomerPackage, GaoPackageDelivery } from "@/types/Package";
|
||||||
import type { PhotoItem } from "@/types/Photo";
|
|
||||||
import { getAttachmentReadURL } from "@/utils/attachment";
|
|
||||||
import { getErrorMessage } from "@/utils/error";
|
import { getErrorMessage } from "@/utils/error";
|
||||||
import {
|
import {
|
||||||
|
canCreatePackageDelivery,
|
||||||
formatPackageAmount,
|
formatPackageAmount,
|
||||||
getCustomerPackageStatusText,
|
getCustomerPackageStatusText,
|
||||||
getDeliveryOperationText,
|
getDeliveryOperationText,
|
||||||
getDeliveryStatusText,
|
getDeliveryStatusText,
|
||||||
getFundOperationText,
|
|
||||||
getFundStatusText,
|
|
||||||
getPackageQuantityText,
|
getPackageQuantityText,
|
||||||
getPaymentStatusText,
|
getPaymentStatusText,
|
||||||
getRefundStatusText
|
getRefundStatusText,
|
||||||
|
isStoreManagerRequiredForPackageDelivery
|
||||||
} from "@/utils/package";
|
} from "@/utils/package";
|
||||||
import { Message } from "@/utils/message";
|
import { Message } from "@/utils/message";
|
||||||
import { gaoRoleCodes, permissionCodes } from "@/utils/permission";
|
import { gaoRoleCodes, permissionCodes } from "@/utils/permission";
|
||||||
@@ -110,19 +188,30 @@ const currentStoreStore = useCurrentStoreStore();
|
|||||||
const customerPackage = ref<GaoCustomerPackage>();
|
const customerPackage = ref<GaoCustomerPackage>();
|
||||||
const isLoading = ref(false);
|
const isLoading = ref(false);
|
||||||
const canReceivePayment = computed(() => customerPackage.value?.status === "ACTIVE" && customerPackage.value.paymentStatus !== "PAID" && userStore.hasPermissions(permissionCodes.fundReceive.create));
|
const canReceivePayment = computed(() => customerPackage.value?.status === "ACTIVE" && customerPackage.value.paymentStatus !== "PAID" && userStore.hasPermissions(permissionCodes.fundReceive.create));
|
||||||
const canCreateDelivery = computed(() => customerPackage.value?.status === "ACTIVE" && customerPackage.value.paymentStatus === "PAID" && userStore.hasPermissions(permissionCodes.packageDelivery.create));
|
const isStoreManager = computed(() => userStore.hasRoles(gaoRoleCodes.storeManager) && !userStore.hasRoles(gaoRoleCodes.globalManager));
|
||||||
|
const canCreateDelivery = computed(() => customerPackage.value?.status === "ACTIVE"
|
||||||
|
&& canCreatePackageDelivery(customerPackage.value)
|
||||||
|
&& (!isStoreManagerRequiredForPackageDelivery(customerPackage.value) || isStoreManager.value)
|
||||||
|
&& userStore.hasPermissions(permissionCodes.packageDelivery.create));
|
||||||
const canCancelPackage = computed(() => customerPackage.value?.status === "ACTIVE" && userStore.hasPermissions(permissionCodes.customerPackage.cancel));
|
const canCancelPackage = computed(() => customerPackage.value?.status === "ACTIVE" && userStore.hasPermissions(permissionCodes.customerPackage.cancel));
|
||||||
const canRefund = computed(() => customerPackage.value?.status === "CANCELLED" && customerPackage.value.refundStatus === "PENDING" && userStore.hasPermissions(permissionCodes.fund.refund));
|
const canRefund = computed(() => customerPackage.value?.status === "CANCELLED" && customerPackage.value.refundStatus === "PENDING" && userStore.hasPermissions(permissionCodes.fund.refund));
|
||||||
const canReadEventRecord = computed(() => userStore.hasPermissions(permissionCodes.eventRecord.read));
|
const canReadEventRecord = computed(() => userStore.hasPermissions(permissionCodes.eventRecord.read));
|
||||||
|
const canReadPackageConfig = computed(() => userStore.hasPermissions(permissionCodes.packageConfig.read));
|
||||||
const canReadDelivery = computed(() => userStore.hasPermissions(permissionCodes.packageDelivery.read));
|
const canReadDelivery = computed(() => userStore.hasPermissions(permissionCodes.packageDelivery.read));
|
||||||
const canReadFund = computed(() => userStore.hasPermissions(permissionCodes.fundRecord.read));
|
const canReadFund = computed(() => userStore.hasPermissions(permissionCodes.fundRecord.read));
|
||||||
const purchasePhotoItems = computed<PhotoItem[]>(() => (customerPackage.value?.purchaseEventRecord?.attachmentList || []).filter(item => !!item.id).map(item => ({
|
const cancelPopupVisible = ref(false);
|
||||||
attachment: item,
|
const cancelReason = ref("");
|
||||||
attachType: item.attachType || "PHOTO",
|
const isCancelling = ref(false);
|
||||||
previewUrl: getAttachmentReadURL(item.id || ""),
|
const canConfirmCancel = computed(() => !!cancelReason.value.trim());
|
||||||
url: getAttachmentReadURL(item.id || "")
|
|
||||||
})));
|
/** 计算扣除已核销和待核销收款后的剩余待收款金额。 */
|
||||||
const purchasePhotoURLList = computed(() => purchasePhotoItems.value.map(item => item.url));
|
function getRemainingReceivableAmount(packageEntity: GaoCustomerPackage): number | undefined {
|
||||||
|
if (packageEntity.saleAmount === undefined) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
const remainingAmount = packageEntity.saleAmount - (packageEntity.paidAmount || 0) - (packageEntity.pendingPaidAmount || 0);
|
||||||
|
return 0 < remainingAmount ? remainingAmount : 0;
|
||||||
|
}
|
||||||
|
|
||||||
function getStatusTheme(status?: CustomerPackageStatus): "success" | "default" {
|
function getStatusTheme(status?: CustomerPackageStatus): "success" | "default" {
|
||||||
return status === "ACTIVE" ? "success" : "default";
|
return status === "ACTIVE" ? "success" : "default";
|
||||||
@@ -159,10 +248,25 @@ async function toPayment(): Promise<void> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function toPackageConfigDetail(): Promise<void> {
|
||||||
|
const id = customerPackage.value?.packageConfigId;
|
||||||
|
if (id && canReadPackageConfig.value) {
|
||||||
|
await router.push({
|
||||||
|
name: "PackageConfigDetail",
|
||||||
|
params: { id },
|
||||||
|
query: { transitionDepth: String(getCurrentRouteDepth() + 1) }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function toPurchaseEventRecord(): Promise<void> {
|
async function toPurchaseEventRecord(): Promise<void> {
|
||||||
const id = customerPackage.value?.purchaseEventRecord?.id;
|
const id = customerPackage.value?.purchaseEventRecord?.id;
|
||||||
if (id && canReadEventRecord.value) {
|
if (id && canReadEventRecord.value) {
|
||||||
await router.push(`/management/registration-event-records/${id}`);
|
await router.push({
|
||||||
|
name: "EventRecordDetail",
|
||||||
|
params: { id },
|
||||||
|
query: { transitionDepth: String(getCurrentRouteDepth() + 1) }
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -173,8 +277,36 @@ async function toDelivery(): Promise<void> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function toCancel(): Promise<void> {
|
async function toCancel(): Promise<void> {
|
||||||
if (customerPackage.value?.id) {
|
if (!customerPackage.value?.id || !canCancelPackage.value) {
|
||||||
await router.push(`/management/customer-packages/${customerPackage.value.id}/cancel`);
|
return;
|
||||||
|
}
|
||||||
|
cancelReason.value = "";
|
||||||
|
cancelPopupVisible.value = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeCancelPopup(): void {
|
||||||
|
if (isCancelling.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
cancelPopupVisible.value = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function confirmCancel(): Promise<void> {
|
||||||
|
const id = customerPackage.value?.id;
|
||||||
|
const reason = cancelReason.value.trim();
|
||||||
|
if (!id || !canCancelPackage.value || !reason || isCancelling.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
isCancelling.value = true;
|
||||||
|
try {
|
||||||
|
await PackageAPI.cancel(id, reason);
|
||||||
|
cancelPopupVisible.value = false;
|
||||||
|
Message.success("客户套餐已取消");
|
||||||
|
await loadData();
|
||||||
|
} catch (error) {
|
||||||
|
Message.error(getErrorMessage(error, "套餐取消失败,请刷新后确认状态"));
|
||||||
|
} finally {
|
||||||
|
isCancelling.value = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -186,26 +318,54 @@ async function toRefund(): Promise<void> {
|
|||||||
|
|
||||||
async function toDeliveryList(): Promise<void> {
|
async function toDeliveryList(): Promise<void> {
|
||||||
if (customerPackage.value?.id) {
|
if (customerPackage.value?.id) {
|
||||||
await router.push({ path: "/management/package-deliveries", query: { customerPackageId: customerPackage.value.id } });
|
await router.push({
|
||||||
|
path: "/management/package-deliveries",
|
||||||
|
query: {
|
||||||
|
customerPackageId: customerPackage.value.id,
|
||||||
|
transitionDepth: String(getCurrentRouteDepth() + 1)
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function toFundList(): Promise<void> {
|
async function toFundList(): Promise<void> {
|
||||||
|
await navigateToFundList();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function toPendingFundList(): Promise<void> {
|
||||||
|
await navigateToFundList(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function navigateToFundList(showPendingReceive = false): Promise<void> {
|
||||||
if (customerPackage.value?.id) {
|
if (customerPackage.value?.id) {
|
||||||
await router.push({ path: "/management/fund-records", query: { customerPackageId: customerPackage.value.id } });
|
const query = {
|
||||||
|
customerPackageId: customerPackage.value.id,
|
||||||
|
transitionDepth: String(getCurrentRouteDepth() + 1),
|
||||||
|
...(showPendingReceive ? { operationType: "RECEIVE", status: "PENDING" } : {})
|
||||||
|
};
|
||||||
|
await router.push({ path: "/management/fund-records", query });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function toDeliveryDetail(item: GaoPackageDelivery): Promise<void> {
|
async function toDeliveryDetail(item: GaoPackageDelivery): Promise<void> {
|
||||||
if (item.id) {
|
if (item.id) {
|
||||||
await router.push(`/management/package-deliveries/${item.id}`);
|
await router.push({
|
||||||
|
name: "PackageDeliveryDetail",
|
||||||
|
params: { id: item.id },
|
||||||
|
query: { transitionDepth: String(getCurrentRouteDepth() + 1) }
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function toFundDetail(item: GaoFundRecord): Promise<void> {
|
function getCurrentRouteDepth(): number {
|
||||||
if (item.id) {
|
const transitionDepth = Array.isArray(route.query.transitionDepth)
|
||||||
await router.push(`/management/fund-records/${item.id}`);
|
? route.query.transitionDepth[0]
|
||||||
|
: route.query.transitionDepth;
|
||||||
|
const depth = Number(transitionDepth);
|
||||||
|
if (transitionDepth && Number.isInteger(depth) && 0 <= depth && depth <= 20) {
|
||||||
|
return depth;
|
||||||
}
|
}
|
||||||
|
return Number(route.meta.depth ?? 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(loadData);
|
onMounted(loadData);
|
||||||
@@ -223,44 +383,125 @@ watch(() => route.params.id, loadData);
|
|||||||
margin: 0 1rem;
|
margin: 0 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.hero {
|
.package {
|
||||||
margin: 0 1rem;
|
|
||||||
padding: 1rem;
|
|
||||||
background: var(--td-bg-color-container);
|
|
||||||
box-shadow: var(--td-shadow-1);
|
|
||||||
border-radius: 1rem;
|
|
||||||
|
|
||||||
.title-row {
|
:deep(.t-cell__title) {
|
||||||
gap: .5rem;
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
flex: 1;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
}
|
|
||||||
|
|
||||||
h2 {
|
.value {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
font-size: 1.125rem;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
.meta {
|
|
||||||
color: var(--td-text-color-secondary);
|
|
||||||
margin-top: .5rem;
|
|
||||||
font-size: .8125rem;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.quantity {
|
.amount {
|
||||||
color: var(--td-brand-color);
|
|
||||||
font-size: .75rem;
|
:deep(.t-cell__note) {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.t-cell__title-text),
|
||||||
|
:deep(.t-cell__note) {
|
||||||
|
color: var(--td-text-color-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.sale :deep(.t-cell__title-text),
|
||||||
|
&.sale :deep(.t-cell__note) {
|
||||||
|
color: var(--td-brand-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.paid :deep(.t-cell__title-text),
|
||||||
|
&.paid :deep(.t-cell__note) {
|
||||||
|
color: var(--td-success-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.pending :deep(.t-cell__title-text),
|
||||||
|
&.pending :deep(.t-cell__note),
|
||||||
|
&.remaining :deep(.t-cell__title-text),
|
||||||
|
&.remaining :deep(.t-cell__note) {
|
||||||
|
color: var(--td-warning-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.refunded :deep(.t-cell__title-text),
|
||||||
|
&.refunded :deep(.t-cell__note) {
|
||||||
|
color: var(--td-error-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.benefit {
|
||||||
|
padding: .875rem 1rem;
|
||||||
|
border-bottom: 1px solid var(--td-component-border);
|
||||||
|
|
||||||
|
&:last-of-type {
|
||||||
|
border-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.head {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
|
||||||
|
strong {
|
||||||
|
font-size: .9375rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.stats {
|
||||||
|
gap: .5rem;
|
||||||
|
margin-top: .75rem;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||||
|
|
||||||
|
.stat {
|
||||||
|
gap: .25rem;
|
||||||
|
min-width: 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
span {
|
||||||
|
color: var(--td-text-color-secondary);
|
||||||
|
font-size: .6875rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
strong {
|
||||||
|
max-width: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
font-size: 1.5rem;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.available strong {
|
||||||
|
color: var(--td-brand-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.pending strong {
|
||||||
|
color: var(--td-warning-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.delivered strong {
|
||||||
|
color: var(--td-success-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.actions {
|
.actions {
|
||||||
gap: .5rem;
|
gap: 1rem;
|
||||||
padding: 0 1rem;
|
padding: 0 1rem;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
|
|
||||||
.t-button {
|
.action {
|
||||||
|
--td-button-border-radius: 1rem;
|
||||||
|
|
||||||
flex: 1 1 8rem;
|
flex: 1 1 8rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,8 +48,8 @@ const route = useRoute();
|
|||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const navBarStore = useNavBarStore();
|
const navBarStore = useNavBarStore();
|
||||||
const recordList = ref<GaoFundRecord[]>([]);
|
const recordList = ref<GaoFundRecord[]>([]);
|
||||||
const operationType = ref("ALL");
|
const operationType = ref(route.query.operationType === "RECEIVE" ? "RECEIVE" : "ALL");
|
||||||
const status = ref("ALL");
|
const status = ref(route.query.status === "PENDING" ? "PENDING" : "ALL");
|
||||||
const pageIndex = ref(0);
|
const pageIndex = ref(0);
|
||||||
const total = ref(0);
|
const total = ref(0);
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
|
|||||||
@@ -42,13 +42,13 @@ import { useUserStore } from "timi-web-api";
|
|||||||
import PackageAPI from "@/api/PackageAPI";
|
import PackageAPI from "@/api/PackageAPI";
|
||||||
import PhotoScroller from "@/components/PhotoScroller.vue";
|
import PhotoScroller from "@/components/PhotoScroller.vue";
|
||||||
import { useNavBarStore } from "@/store/navBarStore";
|
import { useNavBarStore } from "@/store/navBarStore";
|
||||||
import type { DeliveryStatus, GaoPackageDelivery } from "@/types/Package";
|
import type { DeliveryStatus, GaoCustomerPackage, GaoPackageDelivery } from "@/types/Package";
|
||||||
import type { PhotoItem } from "@/types/Photo";
|
import type { PhotoItem } from "@/types/Photo";
|
||||||
import { getAttachmentReadURL } from "@/utils/attachment";
|
import { getAttachmentReadURL } from "@/utils/attachment";
|
||||||
import { getDeliveryOperationText, getDeliveryStatusText, getPackageQuantityText } from "@/utils/package";
|
import { getDeliveryOperationText, getDeliveryStatusText, getPackageQuantityText, isStoreManagerRequiredForPackageDelivery } from "@/utils/package";
|
||||||
import { getErrorMessage } from "@/utils/error";
|
import { getErrorMessage } from "@/utils/error";
|
||||||
import { Message } from "@/utils/message";
|
import { Message } from "@/utils/message";
|
||||||
import { permissionCodes } from "@/utils/permission";
|
import { gaoRoleCodes, permissionCodes } from "@/utils/permission";
|
||||||
|
|
||||||
type ReasonAction = "reject" | "cancel";
|
type ReasonAction = "reject" | "cancel";
|
||||||
|
|
||||||
@@ -59,6 +59,7 @@ const router = useRouter();
|
|||||||
const userStore = useUserStore();
|
const userStore = useUserStore();
|
||||||
const navBarStore = useNavBarStore();
|
const navBarStore = useNavBarStore();
|
||||||
const delivery = ref<GaoPackageDelivery>();
|
const delivery = ref<GaoPackageDelivery>();
|
||||||
|
const customerPackage = ref<GaoCustomerPackage>();
|
||||||
const isLoading = ref(false);
|
const isLoading = ref(false);
|
||||||
const isOperating = ref(false);
|
const isOperating = ref(false);
|
||||||
const reasonDialogVisible = ref(false);
|
const reasonDialogVisible = ref(false);
|
||||||
@@ -66,7 +67,9 @@ const reasonText = ref("");
|
|||||||
const reasonAction = ref<ReasonAction>("reject");
|
const reasonAction = ref<ReasonAction>("reject");
|
||||||
const isPending = computed(() => delivery.value?.operationType === "DELIVER" && delivery.value.status === "PENDING");
|
const isPending = computed(() => delivery.value?.operationType === "DELIVER" && delivery.value.status === "PENDING");
|
||||||
const isVerifiedDelivery = computed(() => delivery.value?.operationType === "DELIVER" && delivery.value.status === "VERIFIED");
|
const isVerifiedDelivery = computed(() => delivery.value?.operationType === "DELIVER" && delivery.value.status === "VERIFIED");
|
||||||
const canVerify = computed(() => userStore.hasPermissions(permissionCodes.packageDelivery.verify));
|
const isStoreManager = computed(() => userStore.hasRoles(gaoRoleCodes.storeManager) && !userStore.hasRoles(gaoRoleCodes.globalManager));
|
||||||
|
const canVerify = computed(() => userStore.hasPermissions(permissionCodes.packageDelivery.verify)
|
||||||
|
&& (!isStoreManagerRequiredForPackageDelivery(customerPackage.value) || isStoreManager.value));
|
||||||
const canCancel = computed(() => userStore.hasPermissions(permissionCodes.packageDelivery.create));
|
const canCancel = computed(() => userStore.hasPermissions(permissionCodes.packageDelivery.create));
|
||||||
const canRevoke = computed(() => userStore.hasPermissions(permissionCodes.packageDelivery.revoke));
|
const canRevoke = computed(() => userStore.hasPermissions(permissionCodes.packageDelivery.revoke));
|
||||||
const reasonTitle = computed(() => reasonAction.value === "reject" ? "拒绝发放" : "撤销发放");
|
const reasonTitle = computed(() => reasonAction.value === "reject" ? "拒绝发放" : "撤销发放");
|
||||||
@@ -92,7 +95,9 @@ async function loadData(): Promise<void> {
|
|||||||
isLoading.value = true;
|
isLoading.value = true;
|
||||||
try {
|
try {
|
||||||
delivery.value = await PackageAPI.deliveryDetail(id);
|
delivery.value = await PackageAPI.deliveryDetail(id);
|
||||||
|
customerPackage.value = delivery.value.customerPackageId ? await PackageAPI.customerPackageDetail(delivery.value.customerPackageId) : undefined;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
customerPackage.value = undefined;
|
||||||
Message.error(getErrorMessage(error, "发放单加载失败"));
|
Message.error(getErrorMessage(error, "发放单加载失败"));
|
||||||
} finally {
|
} finally {
|
||||||
isLoading.value = false;
|
isLoading.value = false;
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import PackageConfigEdit from "@/pages/package/PackageConfigEdit.vue";
|
|||||||
import PackageConfigDetail from "@/pages/package/PackageConfigDetail.vue";
|
import PackageConfigDetail from "@/pages/package/PackageConfigDetail.vue";
|
||||||
import CustomerPackageList from "@/pages/package/CustomerPackageList.vue";
|
import CustomerPackageList from "@/pages/package/CustomerPackageList.vue";
|
||||||
import CustomerPackageDetail from "@/pages/package/CustomerPackageDetail.vue";
|
import CustomerPackageDetail from "@/pages/package/CustomerPackageDetail.vue";
|
||||||
import CustomerPackageCancel from "@/pages/package/CustomerPackageCancel.vue";
|
|
||||||
import CustomerPackagePayment from "@/pages/package/CustomerPackagePayment.vue";
|
import CustomerPackagePayment from "@/pages/package/CustomerPackagePayment.vue";
|
||||||
import CustomerPackageDelivery from "@/pages/package/CustomerPackageDelivery.vue";
|
import CustomerPackageDelivery from "@/pages/package/CustomerPackageDelivery.vue";
|
||||||
import CustomerPackageRefund from "@/pages/package/CustomerPackageRefund.vue";
|
import CustomerPackageRefund from "@/pages/package/CustomerPackageRefund.vue";
|
||||||
@@ -65,12 +64,6 @@ const packageRoutes: RouteRecordRaw[] = [
|
|||||||
component: CustomerPackageDetail,
|
component: CustomerPackageDetail,
|
||||||
meta: pageMeta("客户套餐详情", 4)
|
meta: pageMeta("客户套餐详情", 4)
|
||||||
},
|
},
|
||||||
{
|
|
||||||
path: "/management/customer-packages/:id/cancel",
|
|
||||||
name: "CustomerPackageCancel",
|
|
||||||
component: CustomerPackageCancel,
|
|
||||||
meta: pageMeta("取消客户套餐", 5)
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
path: "/management/customer-packages/:id/payment",
|
path: "/management/customer-packages/:id/payment",
|
||||||
name: "CustomerPackagePayment",
|
name: "CustomerPackagePayment",
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import type {
|
|||||||
DeliveryStatus,
|
DeliveryStatus,
|
||||||
FundOperationType,
|
FundOperationType,
|
||||||
FundRecordStatus,
|
FundRecordStatus,
|
||||||
|
GaoCustomerPackage,
|
||||||
PackageConfigStatus,
|
PackageConfigStatus,
|
||||||
PaymentStatus,
|
PaymentStatus,
|
||||||
RefundStatus
|
RefundStatus
|
||||||
@@ -117,6 +118,20 @@ export function getPackageItemAvailableQuantity(item: CustomerPackageItem): stri
|
|||||||
return getPackageQuantityText(item.availableQuantity);
|
return getPackageQuantityText(item.availableQuantity);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 判断套餐是否存在可用于发放的已核销收款。免费套餐可直接发放。 */
|
||||||
|
export function canCreatePackageDelivery(customerPackage?: Pick<GaoCustomerPackage, "saleAmount" | "paidAmount">): boolean {
|
||||||
|
const saleAmount = customerPackage?.saleAmount || 0;
|
||||||
|
const paidAmount = customerPackage?.paidAmount || 0;
|
||||||
|
return saleAmount === 0 || 0 < paidAmount;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 判断未付清套餐的发放操作是否必须由店长执行。 */
|
||||||
|
export function isStoreManagerRequiredForPackageDelivery(customerPackage?: Pick<GaoCustomerPackage, "saleAmount" | "paidAmount">): boolean {
|
||||||
|
const saleAmount = customerPackage?.saleAmount || 0;
|
||||||
|
const paidAmount = customerPackage?.paidAmount || 0;
|
||||||
|
return 0 < paidAmount && paidAmount < saleAmount;
|
||||||
|
}
|
||||||
|
|
||||||
function toQuantityParts(value: number | string): { value: bigint; scale: number } | undefined {
|
function toQuantityParts(value: number | string): { value: bigint; scale: number } | undefined {
|
||||||
const normalized = normalizePackageQuantity(value);
|
const normalized = normalizePackageQuantity(value);
|
||||||
if (!normalized) {
|
if (!normalized) {
|
||||||
|
|||||||
Reference in New Issue
Block a user