support gift
This commit is contained in:
@@ -26,6 +26,7 @@
|
|||||||
<div class="title">
|
<div class="title">
|
||||||
<div class="heading">
|
<div class="heading">
|
||||||
<strong v-text="item.productNameSnapshot || '未命名产品'" />
|
<strong v-text="item.productNameSnapshot || '未命名产品'" />
|
||||||
|
<t-tag v-if="item.gifted" size="small" theme="success" content="赠送" />
|
||||||
<span v-text="getItemDescription(item)" />
|
<span v-text="getItemDescription(item)" />
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
|
|||||||
@@ -34,6 +34,7 @@
|
|||||||
>
|
>
|
||||||
<template #description>
|
<template #description>
|
||||||
<div class="item-description">
|
<div class="item-description">
|
||||||
|
<t-tag v-if="isGiftedFulfillmentItem(item)" size="small" theme="success" content="赠送" />
|
||||||
<product-sku-snapshot-tags
|
<product-sku-snapshot-tags
|
||||||
:product-name="item.productNameSnapshot"
|
:product-name="item.productNameSnapshot"
|
||||||
:snapshot="item.skuSpecSnapshot"
|
:snapshot="item.skuSpecSnapshot"
|
||||||
@@ -200,6 +201,10 @@ function getFulfillmentQuantityText(item: OrderFulfillmentItem): string {
|
|||||||
return `履约数量 ${getPackageQuantityText(item.quantity)} ${item.unitSnapshot || "单位"}`;
|
return `履约数量 ${getPackageQuantityText(item.quantity)} ${item.unitSnapshot || "单位"}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isGiftedFulfillmentItem(item: OrderFulfillmentItem): boolean {
|
||||||
|
return Boolean(order.value?.itemList?.find(orderItem => orderItem.id === item.sourceOrderItemId)?.gifted);
|
||||||
|
}
|
||||||
|
|
||||||
async function loadData(): Promise<void> {
|
async function loadData(): Promise<void> {
|
||||||
const id = String(route.params.id || "");
|
const id = String(route.params.id || "");
|
||||||
if (!id) return;
|
if (!id) return;
|
||||||
|
|||||||
@@ -44,20 +44,19 @@
|
|||||||
:customer="selectedCustomer"
|
:customer="selectedCustomer"
|
||||||
v-entrance-transition
|
v-entrance-transition
|
||||||
/>
|
/>
|
||||||
<t-form-item v-if="isCustomerPackageCreate" label="套餐" required-mark>
|
<t-form-item
|
||||||
<t-input
|
v-if="isCustomerPackageCreate"
|
||||||
:model-value="config?.name || ''"
|
label="套餐"
|
||||||
readonly
|
required-mark
|
||||||
borderless
|
content-align="right"
|
||||||
placeholder="请选择套餐"
|
arrow
|
||||||
@click="configSelectorVisible = true"
|
@click="configSelectorVisible = true"
|
||||||
/>
|
>选择套餐</t-form-item>
|
||||||
</t-form-item>
|
|
||||||
<div v-if="config && !isSaleable" class="saleable-tip red">套餐当前不可购买,请检查套餐状态和有效期</div>
|
<div v-if="config && !isSaleable" class="saleable-tip red">套餐当前不可购买,请检查套餐状态和有效期</div>
|
||||||
<section class="package-purchase-form">
|
<t-form-item v-if="config" label-width="0">
|
||||||
<div v-if="config" class="config-summary">
|
<div class="config-summary">
|
||||||
<div class="header">
|
<div class="header">
|
||||||
<div class="text-bold" v-text="config.name || '未命名套餐'" />
|
<div class="name text-bold clip-text" v-text="config.name || '未命名套餐'" />
|
||||||
<div class="price">
|
<div class="price">
|
||||||
<span class="label gray">标准价:</span>
|
<span class="label gray">标准价:</span>
|
||||||
<span class="value red text-bold" v-text="formatPackageAmount(config.standardAmount)" />
|
<span class="value red text-bold" v-text="formatPackageAmount(config.standardAmount)" />
|
||||||
@@ -65,80 +64,93 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="description" v-text="config.description" />
|
<div class="description" v-text="config.description" />
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="center-tips" v-text="'当前套餐未返回,无法完成购买。'" />
|
</t-form-item>
|
||||||
<t-form-item label="成交价(元)" label-width="6rem" required-mark>
|
<t-form-item label="成交价(元)" label-width="6rem" required-mark>
|
||||||
<t-input
|
<t-input
|
||||||
v-model.trim="saleAmountText"
|
v-model.trim="saleAmountText"
|
||||||
type="number"
|
type="number"
|
||||||
align="right"
|
align="right"
|
||||||
borderless
|
borderless
|
||||||
:readonly="!canPriceOverride"
|
:readonly="!canPriceOverride"
|
||||||
placeholder="请输入成交价"
|
placeholder="请输入成交价"
|
||||||
/>
|
/>
|
||||||
</t-form-item>
|
</t-form-item>
|
||||||
<t-form-item label-width="0">
|
<t-form-item v-if="config" label="购买权益明细" label-align="top">
|
||||||
<t-form class="items" label-width="2.5rem">
|
<t-form class="items" label-width="2.5rem">
|
||||||
<div class="title">
|
<span v-if="!canPriceOverride">当前账号无定制权限,仅可等值转换单位</span>
|
||||||
<strong>购买权益明细</strong>
|
<t-form-item
|
||||||
<span v-if="!canPriceOverride">当前账号无定制权限,仅可等值转换单位</span>
|
v-for="(item, index) in packagePurchaseItemList"
|
||||||
</div>
|
:key="`${index}-${item.skuId}-${item.skuUnitId}-${item.gifted}`"
|
||||||
<t-form-item
|
class="item"
|
||||||
v-for="(item, index) in packagePurchaseItemList"
|
>
|
||||||
:key="`${index}-${item.skuId}-${item.skuUnitId}`"
|
<template #label>
|
||||||
class="item"
|
<t-image
|
||||||
>
|
v-if="getItemPhotoURL(item)"
|
||||||
<template #label>
|
:src="getItemPhotoURL(item)"
|
||||||
<t-image
|
:alt="getItemName(item)"
|
||||||
v-if="getItemPhotoURL(item)"
|
fit="cover"
|
||||||
:src="getItemPhotoURL(item)"
|
class="avatar"
|
||||||
:alt="getItemName(item)"
|
/>
|
||||||
fit="cover"
|
<div
|
||||||
class="avatar"
|
v-else
|
||||||
/>
|
class="avatar fallback"
|
||||||
<div
|
v-text="getItemName(item).slice(0, 1)"
|
||||||
v-else
|
/>
|
||||||
class="avatar fallback"
|
</template>
|
||||||
v-text="getItemName(item).slice(0, 1)"
|
<div class="content">
|
||||||
/>
|
<div class="main">
|
||||||
</template>
|
|
||||||
<div class="content">
|
|
||||||
<span class="name" v-text="getItemLabel(item)" />
|
<span class="name" v-text="getItemLabel(item)" />
|
||||||
|
<t-tag v-if="item.gifted" size="small" theme="success" content="赠" />
|
||||||
<t-input
|
<t-input
|
||||||
class="input"
|
class="input"
|
||||||
:model-value="item.totalQuantity"
|
:model-value="item.totalQuantity"
|
||||||
type="number"
|
type="number"
|
||||||
:readonly="!canPriceOverride"
|
:readonly="!canEditItem(item)"
|
||||||
placeholder="总权益数量"
|
placeholder="总权益数量"
|
||||||
borderless
|
borderless
|
||||||
align="right"
|
align="right"
|
||||||
@update:model-value="updateItemQuantity(index, $event)"
|
@update:model-value="updateItemQuantity(index, $event)"
|
||||||
>
|
>
|
||||||
<template #suffix-icon>
|
|
||||||
<div class="actions">
|
|
||||||
<t-button
|
|
||||||
size="extra-small"
|
|
||||||
variant="outline"
|
|
||||||
:content="getItemUnit(item)?.name || '选择单位'"
|
|
||||||
@click="openItemUnitPicker(index)"
|
|
||||||
/>
|
|
||||||
<t-button
|
|
||||||
v-if="canPriceOverride"
|
|
||||||
class="remove"
|
|
||||||
size="extra-small"
|
|
||||||
theme="danger"
|
|
||||||
variant="text"
|
|
||||||
content="移除"
|
|
||||||
@click="removePurchaseItem(index)"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</t-input>
|
</t-input>
|
||||||
|
<div class="actions">
|
||||||
|
<t-button
|
||||||
|
size="extra-small"
|
||||||
|
variant="outline"
|
||||||
|
:content="getItemUnit(item)?.name || '选择单位'"
|
||||||
|
@click="openItemUnitPicker(index)"
|
||||||
|
/>
|
||||||
|
<t-button
|
||||||
|
v-if="canRemoveItem(item)"
|
||||||
|
class="remove"
|
||||||
|
size="extra-small"
|
||||||
|
theme="danger"
|
||||||
|
variant="text"
|
||||||
|
content="移除"
|
||||||
|
@click="removePurchaseItem(index)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</t-form-item>
|
<t-input
|
||||||
<div v-if="!packagePurchaseItemList.length" class="center-tips" v-text="'当前套餐暂无权益明细'" />
|
v-if="item.gifted"
|
||||||
</t-form>
|
class="gift-remark"
|
||||||
</t-form-item>
|
:model-value="item.remark || ''"
|
||||||
</section>
|
align="right"
|
||||||
|
borderless
|
||||||
|
placeholder="赠送说明(选填)"
|
||||||
|
@update:model-value="updateItemRemark(index, $event)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</t-form-item>
|
||||||
|
<product-selector
|
||||||
|
v-if="canGiftCreate"
|
||||||
|
:model-value="undefined"
|
||||||
|
label=""
|
||||||
|
placeholder="添加可售赠送产品"
|
||||||
|
@update:model-value="addGiftProduct"
|
||||||
|
/>
|
||||||
|
<div v-if="!packagePurchaseItemList.length" class="center-tips" v-text="'当前套餐暂无权益明细'" />
|
||||||
|
</t-form>
|
||||||
|
</t-form-item>
|
||||||
<t-form-item label="购买时间" content-align="right">
|
<t-form-item label="购买时间" content-align="right">
|
||||||
<div class="time-actions">
|
<div class="time-actions">
|
||||||
<t-button
|
<t-button
|
||||||
@@ -254,9 +266,14 @@ import { useSettingStore } from "@/store/settingStore";
|
|||||||
import type { Customer } from "@/types/Customer";
|
import type { Customer } from "@/types/Customer";
|
||||||
import type { PhotoItem } from "@/types/Photo";
|
import type { PhotoItem } from "@/types/Photo";
|
||||||
import type { GaoPackageConfig, PackageAttachment, PackagePurchaseItem } from "@/types/Package";
|
import type { GaoPackageConfig, PackageAttachment, PackagePurchaseItem } from "@/types/Package";
|
||||||
import type { ProductSku, ProductSkuUnit } from "@/types/Product";
|
import type { Product, ProductSku, ProductSkuUnit } from "@/types/Product";
|
||||||
import { getErrorMessage } from "@/utils/error";
|
import { getErrorMessage } from "@/utils/error";
|
||||||
import { addPackageQuantity, convertPackageQuantity, formatPackageAmount, normalizePackageQuantity } from "@/utils/package";
|
import {
|
||||||
|
addPackageQuantity,
|
||||||
|
convertPackageQuantity,
|
||||||
|
formatPackageAmount,
|
||||||
|
normalizePackageQuantity
|
||||||
|
} from "@/utils/package";
|
||||||
import { Message } from "@/utils/message";
|
import { Message } from "@/utils/message";
|
||||||
import { permissionCodes } from "@/utils/permission";
|
import { permissionCodes } from "@/utils/permission";
|
||||||
import { uploadPhotoList } from "@/utils/photo";
|
import { uploadPhotoList } from "@/utils/photo";
|
||||||
@@ -280,6 +297,7 @@ const config = ref<GaoPackageConfig>();
|
|||||||
const configList = ref<GaoPackageConfig[]>([]);
|
const configList = ref<GaoPackageConfig[]>([]);
|
||||||
const configSelectorVisible = ref(false);
|
const configSelectorVisible = ref(false);
|
||||||
const skuMap = reactive<Record<string, ProductSku>>({});
|
const skuMap = reactive<Record<string, ProductSku>>({});
|
||||||
|
const giftProductMap = reactive<Record<string, Product>>({});
|
||||||
const unitPickerVisible = ref(false);
|
const unitPickerVisible = ref(false);
|
||||||
const unitPickerIndex = ref(-1);
|
const unitPickerIndex = ref(-1);
|
||||||
const unitPickerDraft = ref<Array<string | number>>([]);
|
const unitPickerDraft = ref<Array<string | number>>([]);
|
||||||
@@ -417,6 +435,7 @@ function buildAttachments(): PackageAttachment[] {
|
|||||||
const {
|
const {
|
||||||
isSaleable,
|
isSaleable,
|
||||||
canPriceOverride,
|
canPriceOverride,
|
||||||
|
canGiftCreate,
|
||||||
saleAmountText,
|
saleAmountText,
|
||||||
packagePurchaseItemList,
|
packagePurchaseItemList,
|
||||||
applyConfig,
|
applyConfig,
|
||||||
@@ -439,16 +458,19 @@ const unitPickerOptions = computed(() => {
|
|||||||
|
|
||||||
function getItemLabel(item: PackagePurchaseItem): string {
|
function getItemLabel(item: PackagePurchaseItem): string {
|
||||||
const configItem = config.value?.itemList?.find(configItem => configItem.skuId === item.skuId);
|
const configItem = config.value?.itemList?.find(configItem => configItem.skuId === item.skuId);
|
||||||
if (!configItem) {
|
const product = item.gifted ? giftProductMap[item.skuId] : undefined;
|
||||||
return "产品权益";
|
const productName = item.gifted
|
||||||
}
|
? product?.name || item.productNameSnapshot || "未命名产品"
|
||||||
const productName = configItem.productNameSnapshot || "未命名产品";
|
: configItem?.productNameSnapshot || "未命名产品";
|
||||||
const skuText = getProductSkuSnapshotText(productName, configItem.skuNameSnapshot, configItem.specSummarySnapshot);
|
const skuName = item.gifted ? item.skuNameSnapshot || getItemSku(item)?.name : configItem?.skuNameSnapshot;
|
||||||
|
const specSummary = item.gifted ? item.specSummarySnapshot || getItemSku(item)?.specSummary : configItem?.specSummarySnapshot;
|
||||||
|
const skuText = getProductSkuSnapshotText(productName, skuName, specSummary);
|
||||||
return skuText ? `${productName} · ${skuText}` : productName;
|
return skuText ? `${productName} · ${skuText}` : productName;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getItemName(item: PackagePurchaseItem): string {
|
function getItemName(item: PackagePurchaseItem): string {
|
||||||
return config.value?.itemList?.find(configItem => configItem.skuId === item.skuId)?.productNameSnapshot || "未命名产品";
|
return item.gifted ? giftProductMap[item.skuId]?.name || item.productNameSnapshot || "未命名产品"
|
||||||
|
: config.value?.itemList?.find(configItem => configItem.skuId === item.skuId)?.productNameSnapshot || "未命名产品";
|
||||||
}
|
}
|
||||||
|
|
||||||
function getItemPhotoURL(item: PackagePurchaseItem): string {
|
function getItemPhotoURL(item: PackagePurchaseItem): string {
|
||||||
@@ -464,6 +486,9 @@ function getItemUnit(item: PackagePurchaseItem): ProductSkuUnit | undefined {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getSelectableUnitList(item: PackagePurchaseItem): ProductSkuUnit[] {
|
function getSelectableUnitList(item: PackagePurchaseItem): ProductSkuUnit[] {
|
||||||
|
if (item.gifted) {
|
||||||
|
return (getItemSku(item)?.unitList || []).filter(unit => unit.allowSale !== false && !!unit.id);
|
||||||
|
}
|
||||||
const maxBaseQuantityPerUnit = Math.max(...(config.value?.itemList || [])
|
const maxBaseQuantityPerUnit = Math.max(...(config.value?.itemList || [])
|
||||||
.filter(configItem => configItem.skuId === item.skuId)
|
.filter(configItem => configItem.skuId === item.skuId)
|
||||||
.map(configItem => Number(configItem.baseQuantityPerUnitSnapshot || 0)));
|
.map(configItem => Number(configItem.baseQuantityPerUnitSnapshot || 0)));
|
||||||
@@ -473,7 +498,7 @@ function getSelectableUnitList(item: PackagePurchaseItem): ProductSkuUnit[] {
|
|||||||
|
|
||||||
function openItemUnitPicker(index: number): void {
|
function openItemUnitPicker(index: number): void {
|
||||||
const item = packagePurchaseItemList.value[index];
|
const item = packagePurchaseItemList.value[index];
|
||||||
if (!item || !getSelectableUnitList(item).length) {
|
if (!item || !canEditItem(item) || !getSelectableUnitList(item).length) {
|
||||||
Message.warning("当前 SKU 没有可用于套餐权益的销售单位");
|
Message.warning("当前 SKU 没有可用于套餐权益的销售单位");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -488,7 +513,7 @@ function confirmItemUnitPicker(value: Array<string | number>): void {
|
|||||||
const skuUnitId = String(value[0] || "");
|
const skuUnitId = String(value[0] || "");
|
||||||
const currentUnit = item && getItemUnit(item);
|
const currentUnit = item && getItemUnit(item);
|
||||||
const targetUnit = item && getSelectableUnitList(item).find(unit => unit.id === skuUnitId);
|
const targetUnit = item && getSelectableUnitList(item).find(unit => unit.id === skuUnitId);
|
||||||
if (!item || !currentUnit || !targetUnit || !skuUnitId || skuUnitId === item.skuUnitId) {
|
if (!item || !canEditItem(item) || !currentUnit || !targetUnit || !skuUnitId || skuUnitId === item.skuUnitId) {
|
||||||
unitPickerVisible.value = false;
|
unitPickerVisible.value = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -498,7 +523,7 @@ function confirmItemUnitPicker(value: Array<string | number>): void {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const duplicateIndex = packagePurchaseItemList.value.findIndex((candidate, candidateIndex) => candidateIndex !== index
|
const duplicateIndex = packagePurchaseItemList.value.findIndex((candidate, candidateIndex) => candidateIndex !== index
|
||||||
&& candidate.skuId === item.skuId && candidate.skuUnitId === skuUnitId);
|
&& candidate.skuId === item.skuId && candidate.skuUnitId === skuUnitId && Boolean(candidate.gifted) === Boolean(item.gifted));
|
||||||
if (duplicateIndex < 0) {
|
if (duplicateIndex < 0) {
|
||||||
packagePurchaseItemList.value[index] = { ...item, skuUnitId, totalQuantity: convertedQuantity };
|
packagePurchaseItemList.value[index] = { ...item, skuUnitId, totalQuantity: convertedQuantity };
|
||||||
} else {
|
} else {
|
||||||
@@ -522,7 +547,63 @@ function confirmItemUnitPicker(value: Array<string | number>): void {
|
|||||||
unitPickerVisible.value = false;
|
unitPickerVisible.value = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function addGiftProduct(value?: { skuId: string; skuUnitId: string }): Promise<void> {
|
||||||
|
if (!value || !canGiftCreate.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const sku = skuMap[value.skuId] || await ProductAPI.skuDetail(value.skuId);
|
||||||
|
skuMap[value.skuId] = sku;
|
||||||
|
const productId = sku.productId || "";
|
||||||
|
const product = productId ? giftProductMap[value.skuId] || await ProductAPI.detail(productId) : undefined;
|
||||||
|
if (!product) {
|
||||||
|
Message.warning("赠送产品信息加载失败,请重新选择");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
giftProductMap[value.skuId] = product;
|
||||||
|
const existingIndex = packagePurchaseItemList.value.findIndex(item => item.gifted
|
||||||
|
&& item.skuId === value.skuId && item.skuUnitId === value.skuUnitId);
|
||||||
|
if (0 <= existingIndex) {
|
||||||
|
const existingItem = packagePurchaseItemList.value[existingIndex];
|
||||||
|
const totalQuantity = addPackageQuantity(existingItem.totalQuantity, "1");
|
||||||
|
if (!totalQuantity) {
|
||||||
|
Message.warning("赠送数量合并后不能精确保留三位小数");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
packagePurchaseItemList.value[existingIndex] = { ...existingItem, totalQuantity };
|
||||||
|
} else {
|
||||||
|
const maxSort = Math.max(-1, ...packagePurchaseItemList.value.map(item => item.sort ?? -1));
|
||||||
|
packagePurchaseItemList.value.push({
|
||||||
|
skuId: value.skuId,
|
||||||
|
skuUnitId: value.skuUnitId,
|
||||||
|
totalQuantity: "1",
|
||||||
|
gifted: true,
|
||||||
|
sort: maxSort + 1,
|
||||||
|
remark: null,
|
||||||
|
productNameSnapshot: product.name,
|
||||||
|
skuNameSnapshot: sku.name,
|
||||||
|
specSummarySnapshot: sku.specSummary
|
||||||
|
});
|
||||||
|
}
|
||||||
|
resetRequestNo();
|
||||||
|
} catch (error) {
|
||||||
|
Message.error(getErrorMessage(error, "赠送产品加载失败"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function canEditItem(item: PackagePurchaseItem): boolean {
|
||||||
|
return Boolean(item.gifted) ? canGiftCreate.value : canPriceOverride.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
function canRemoveItem(item: PackagePurchaseItem): boolean {
|
||||||
|
return canEditItem(item);
|
||||||
|
}
|
||||||
|
|
||||||
function updateItemQuantity(index: number, value: string | number): void {
|
function updateItemQuantity(index: number, value: string | number): void {
|
||||||
|
const item = packagePurchaseItemList.value[index];
|
||||||
|
if (!item || !canEditItem(item)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
packagePurchaseItemList.value = packagePurchaseItemList.value.map((item, itemIndex) => itemIndex === index ? {
|
packagePurchaseItemList.value = packagePurchaseItemList.value.map((item, itemIndex) => itemIndex === index ? {
|
||||||
...item,
|
...item,
|
||||||
totalQuantity: String(value)
|
totalQuantity: String(value)
|
||||||
@@ -530,8 +611,18 @@ function updateItemQuantity(index: number, value: string | number): void {
|
|||||||
resetRequestNo();
|
resetRequestNo();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function updateItemRemark(index: number, value: string | number): void {
|
||||||
|
const item = packagePurchaseItemList.value[index];
|
||||||
|
if (!item?.gifted || !canGiftCreate.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
packagePurchaseItemList.value[index] = { ...item, remark: String(value) || null };
|
||||||
|
resetRequestNo();
|
||||||
|
}
|
||||||
|
|
||||||
function removePurchaseItem(index: number): void {
|
function removePurchaseItem(index: number): void {
|
||||||
if (!canPriceOverride.value) {
|
const item = packagePurchaseItemList.value[index];
|
||||||
|
if (!item || !canRemoveItem(item)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
packagePurchaseItemList.value = packagePurchaseItemList.value.filter((_, itemIndex) => itemIndex !== index);
|
packagePurchaseItemList.value = packagePurchaseItemList.value.filter((_, itemIndex) => itemIndex !== index);
|
||||||
@@ -662,84 +753,86 @@ watch(config, value => {
|
|||||||
margin: 1rem;
|
margin: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.saleable-tip {
|
.config-summary {
|
||||||
padding: 0 1rem .5rem;
|
gap: .25rem;
|
||||||
font-size: .8125rem;
|
width: 100%;
|
||||||
line-height: 1.5;
|
padding: .5rem .75rem;
|
||||||
}
|
|
||||||
|
|
||||||
.time-actions {
|
|
||||||
gap: .5rem;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
background: var(--td-error-color-1);
|
||||||
justify-content: flex-end;
|
border-radius: .75rem;
|
||||||
}
|
flex-direction: column;
|
||||||
|
|
||||||
.package-purchase-form {
|
.header {
|
||||||
gap: .75rem;
|
|
||||||
display: grid;
|
|
||||||
margin-top: 1rem;
|
|
||||||
|
|
||||||
.config-summary {
|
|
||||||
gap: .25rem;
|
|
||||||
margin: 0 1rem;
|
|
||||||
padding: .5rem .75rem;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
background: var(--td-error-color-1);
|
justify-content: space-between;
|
||||||
border-radius: .75rem;
|
|
||||||
flex-direction: column;
|
|
||||||
|
|
||||||
.header {
|
.name {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.price {
|
||||||
|
flex: none;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
align-items: baseline;
|
||||||
|
|
||||||
.price {
|
.label {
|
||||||
display: flex;
|
font-size: .75rem;
|
||||||
align-items: baseline;
|
}
|
||||||
|
|
||||||
.label {
|
.value {
|
||||||
font-size: .75rem;
|
font-size: 1.75rem;
|
||||||
}
|
line-height: 1;
|
||||||
|
|
||||||
.value {
|
|
||||||
font-size: 1.75rem;
|
|
||||||
line-height: 1;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.items {
|
.description {
|
||||||
gap: .5rem;
|
width: 100%;
|
||||||
margin: 0;
|
font-size: .75rem;
|
||||||
display: grid;
|
min-width: 0;
|
||||||
|
max-width: 100%;
|
||||||
|
word-break: break-word;
|
||||||
|
white-space: normal;
|
||||||
|
overflow-wrap: anywhere;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.title {
|
.items {
|
||||||
font-size: .8125rem;
|
gap: .5rem;
|
||||||
margin-bottom: .5rem;
|
flex: 1;
|
||||||
|
margin: 0;
|
||||||
|
display: grid;
|
||||||
|
|
||||||
|
.title {
|
||||||
|
font-size: .8125rem;
|
||||||
|
margin-bottom: .5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item {
|
||||||
|
padding: 10px;
|
||||||
|
box-shadow: .25rem .25rem 1rem rgba(0, 0, 0, .08);
|
||||||
|
border-radius: 1rem;
|
||||||
|
|
||||||
|
.avatar {
|
||||||
|
width: 2rem;
|
||||||
|
height: 2rem;
|
||||||
|
flex: none;
|
||||||
|
border-radius: .5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.item {
|
&::after {
|
||||||
padding: 10px;
|
display: none;
|
||||||
box-shadow: .25rem .25rem 1rem rgba(0, 0, 0, .08);
|
}
|
||||||
border-radius: 1rem;
|
|
||||||
|
|
||||||
.avatar {
|
.content {
|
||||||
width: 2rem;
|
gap: .25rem;
|
||||||
height: 2rem;
|
display: flex;
|
||||||
flex: none;
|
flex-direction: column;
|
||||||
border-radius: .5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
&::after {
|
.main {
|
||||||
display: none;
|
gap: .25rem;
|
||||||
}
|
|
||||||
|
|
||||||
.content {
|
|
||||||
display: flex;
|
display: flex;
|
||||||
height: 2rem;
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
|
||||||
|
|
||||||
.name {
|
.name {
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
@@ -749,13 +842,19 @@ watch(config, value => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.input {
|
.input {
|
||||||
min-width: 0;
|
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
margin-top: .2rem;
|
||||||
|
}
|
||||||
|
|
||||||
.actions {
|
.actions {
|
||||||
gap: .25rem;
|
gap: .25rem;
|
||||||
display: flex;
|
margin: .1rem 0 0 .75rem;
|
||||||
}
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gift-remark {
|
||||||
|
padding: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -763,6 +862,13 @@ watch(config, value => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.time-actions {
|
||||||
|
gap: .5rem;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
.file-input {
|
.file-input {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ interface UsePackagePurchaseOptions {
|
|||||||
/** 套餐配置直接购买流程,购买请求只依赖套餐配置 ID,不再关联购买事件。 */
|
/** 套餐配置直接购买流程,购买请求只依赖套餐配置 ID,不再关联购买事件。 */
|
||||||
export function usePackagePurchase(options: UsePackagePurchaseOptions) {
|
export function usePackagePurchase(options: UsePackagePurchaseOptions) {
|
||||||
const canPriceOverride = computed(() => options.hasPermission(permissionCodes.customerPackage.priceOverride));
|
const canPriceOverride = computed(() => options.hasPermission(permissionCodes.customerPackage.priceOverride));
|
||||||
|
const canGiftCreate = computed(() => options.hasPermission(permissionCodes.customerPackage.giftCreate));
|
||||||
const saleAmountText = ref("");
|
const saleAmountText = ref("");
|
||||||
const packagePurchaseItemList = ref<PackagePurchaseItem[]>([]);
|
const packagePurchaseItemList = ref<PackagePurchaseItem[]>([]);
|
||||||
const packageRequestNo = ref("");
|
const packageRequestNo = ref("");
|
||||||
@@ -45,6 +46,7 @@ export function usePackagePurchase(options: UsePackagePurchaseOptions) {
|
|||||||
skuId: item.skuId,
|
skuId: item.skuId,
|
||||||
skuUnitId: item.skuUnitId,
|
skuUnitId: item.skuUnitId,
|
||||||
totalQuantity: normalizePackageQuantity(item.defaultQuantity) || "",
|
totalQuantity: normalizePackageQuantity(item.defaultQuantity) || "",
|
||||||
|
gifted: false,
|
||||||
sort: item.sort ?? index,
|
sort: item.sort ?? index,
|
||||||
remark: item.remark ?? null
|
remark: item.remark ?? null
|
||||||
}));
|
}));
|
||||||
@@ -77,6 +79,7 @@ export function usePackagePurchase(options: UsePackagePurchaseOptions) {
|
|||||||
skuId: item.skuId,
|
skuId: item.skuId,
|
||||||
skuUnitId: item.skuUnitId,
|
skuUnitId: item.skuUnitId,
|
||||||
totalQuantity,
|
totalQuantity,
|
||||||
|
gifted: Boolean(item.gifted),
|
||||||
sort: item.sort ?? index,
|
sort: item.sort ?? index,
|
||||||
remark: item.remark ?? null
|
remark: item.remark ?? null
|
||||||
}] : [];
|
}] : [];
|
||||||
@@ -124,7 +127,7 @@ export function usePackagePurchase(options: UsePackagePurchaseOptions) {
|
|||||||
}
|
}
|
||||||
if (quantity !== "0") {
|
if (quantity !== "0") {
|
||||||
hasPositiveQuantity = true;
|
hasPositiveQuantity = true;
|
||||||
const key = `${item.skuId}\u0000${item.skuUnitId}`;
|
const key = `${item.skuId}\u0000${item.skuUnitId}\u0000${Boolean(item.gifted)}`;
|
||||||
if (keySet.has(key)) {
|
if (keySet.has(key)) {
|
||||||
Message.warning("最终套餐权益不能重复相同 SKU 和单位");
|
Message.warning("最终套餐权益不能重复相同 SKU 和单位");
|
||||||
return false;
|
return false;
|
||||||
@@ -142,6 +145,7 @@ export function usePackagePurchase(options: UsePackagePurchaseOptions) {
|
|||||||
return {
|
return {
|
||||||
isSaleable,
|
isSaleable,
|
||||||
canPriceOverride,
|
canPriceOverride,
|
||||||
|
canGiftCreate,
|
||||||
saleAmountText,
|
saleAmountText,
|
||||||
packagePurchaseItemList,
|
packagePurchaseItemList,
|
||||||
applyConfig,
|
applyConfig,
|
||||||
|
|||||||
@@ -97,6 +97,7 @@
|
|||||||
<div class="main">
|
<div class="main">
|
||||||
<div class="title">
|
<div class="title">
|
||||||
<span class="name text-bold" v-text="item.productNameSnapshot || '未命名产品'" />
|
<span class="name text-bold" v-text="item.productNameSnapshot || '未命名产品'" />
|
||||||
|
<t-tag v-if="item.gifted" size="small" theme="success" content="赠送" />
|
||||||
<t-tag
|
<t-tag
|
||||||
:content="getItemFulfillmentStatus(item).text"
|
:content="getItemFulfillmentStatus(item).text"
|
||||||
:theme="getItemFulfillmentStatus(item).theme"
|
:theme="getItemFulfillmentStatus(item).theme"
|
||||||
|
|||||||
@@ -231,7 +231,8 @@ function sumProductQuantity(field: "quantity" | "fulfilledBaseQuantity"): string
|
|||||||
function getProductText(item: NonNullable<SalesOrder["itemList"]>[number]): string {
|
function getProductText(item: NonNullable<SalesOrder["itemList"]>[number]): string {
|
||||||
const quantity = getPackageQuantityText(item.quantity);
|
const quantity = getPackageQuantityText(item.quantity);
|
||||||
const skuText = getProductSkuSnapshotText(item.productNameSnapshot, item.skuNameSnapshot, item.specSummarySnapshot);
|
const skuText = getProductSkuSnapshotText(item.productNameSnapshot, item.skuNameSnapshot, item.specSummarySnapshot);
|
||||||
return `${item.productNameSnapshot || "未命名产品"}${skuText ? ` · ${skuText}` : ""} × ${quantity}${item.unitSnapshot ? ` ${item.unitSnapshot}` : ""}`;
|
const giftText = item.gifted ? "赠送 · " : "";
|
||||||
|
return `${giftText}${item.productNameSnapshot || "未命名产品"}${skuText ? ` · ${skuText}` : ""} × ${quantity}${item.unitSnapshot ? ` ${item.unitSnapshot}` : ""}`;
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -61,8 +61,12 @@ export type PackagePurchaseItem = {
|
|||||||
skuId: string;
|
skuId: string;
|
||||||
skuUnitId: string;
|
skuUnitId: string;
|
||||||
totalQuantity: number | string;
|
totalQuantity: number | string;
|
||||||
|
gifted?: boolean;
|
||||||
sort?: number;
|
sort?: number;
|
||||||
remark?: string | null;
|
remark?: string | null;
|
||||||
|
productNameSnapshot?: string;
|
||||||
|
skuNameSnapshot?: string;
|
||||||
|
specSummarySnapshot?: string | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type PackageAttachment = Partial<Attachment> & { tempFileId?: string; attachType?: string; name?: string };
|
export type PackageAttachment = Partial<Attachment> & { tempFileId?: string; attachType?: string; name?: string };
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ export type FundRecordStatus = "PENDING" | "VERIFIED" | "REJECTED" | "CANCELLED"
|
|||||||
export type GaoSalesOrderItem = {
|
export type GaoSalesOrderItem = {
|
||||||
salesOrderId?: string;
|
salesOrderId?: string;
|
||||||
itemType?: GaoSalesOrderItemType;
|
itemType?: GaoSalesOrderItemType;
|
||||||
|
gifted?: boolean;
|
||||||
skuId?: string;
|
skuId?: string;
|
||||||
skuUnitId?: string;
|
skuUnitId?: string;
|
||||||
attachmentList?: Attachment[];
|
attachmentList?: Attachment[];
|
||||||
|
|||||||
@@ -70,7 +70,8 @@ export const permissionCodes = {
|
|||||||
create: "GAO:CUSTOMER_PACKAGE:CREATE",
|
create: "GAO:CUSTOMER_PACKAGE:CREATE",
|
||||||
read: "GAO:CUSTOMER_PACKAGE:READ",
|
read: "GAO:CUSTOMER_PACKAGE:READ",
|
||||||
cancel: "GAO:CUSTOMER_PACKAGE:CANCEL",
|
cancel: "GAO:CUSTOMER_PACKAGE:CANCEL",
|
||||||
priceOverride: "GAO:CUSTOMER_PACKAGE:PRICE_OVERRIDE"
|
priceOverride: "GAO:CUSTOMER_PACKAGE:PRICE_OVERRIDE",
|
||||||
|
giftCreate: "GAO:CUSTOMER_PACKAGE:GIFT_CREATE"
|
||||||
},
|
},
|
||||||
salesOrder: {
|
salesOrder: {
|
||||||
create: "GAO:SALES_ORDER:CREATE",
|
create: "GAO:SALES_ORDER:CREATE",
|
||||||
@@ -207,6 +208,7 @@ export const gaoStoreScopePermissionCodes = [
|
|||||||
permissionCodes.customerPackage.read,
|
permissionCodes.customerPackage.read,
|
||||||
permissionCodes.customerPackage.cancel,
|
permissionCodes.customerPackage.cancel,
|
||||||
permissionCodes.customerPackage.priceOverride,
|
permissionCodes.customerPackage.priceOverride,
|
||||||
|
permissionCodes.customerPackage.giftCreate,
|
||||||
permissionCodes.salesOrder.create,
|
permissionCodes.salesOrder.create,
|
||||||
permissionCodes.salesOrder.read,
|
permissionCodes.salesOrder.read,
|
||||||
permissionCodes.salesOrder.update,
|
permissionCodes.salesOrder.update,
|
||||||
|
|||||||
Reference in New Issue
Block a user