diff --git a/src/pages/fulfillment/OrderFulfillmentCreate.vue b/src/pages/fulfillment/OrderFulfillmentCreate.vue
index 9d1bc40..d3d7cc7 100644
--- a/src/pages/fulfillment/OrderFulfillmentCreate.vue
+++ b/src/pages/fulfillment/OrderFulfillmentCreate.vue
@@ -5,27 +5,78 @@
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 全部履约
+
+
+
+
+
-
-
+
+
+
+
+
+
+
+
+
-
@@ -65,6 +116,7 @@ import { getErrorMessage } from "@/utils/error";
import { Message } from "@/utils/message";
import { permissionCodes } from "@/utils/permission";
import { uploadPhotoList } from "@/utils/photo";
+import { getProductPhotoURL } from "@/utils/product";
type SourceItem = SalesOrderItem | CustomerPackageItem;
defineOptions({ name: "OrderFulfillmentCreate" });
@@ -87,6 +139,7 @@ const orderLoading = ref(false);
const orderOptions = ref([]);
const isPackage = computed(() => !!customerPackage.value);
const sourceItems = computed(() => customerPackage.value?.itemList || order.value?.itemList || []);
+
async function loadData(): Promise {
const salesOrderId = String(route.query.salesOrderId || "");
const customerPackageId = String(route.query.customerPackageId || "");
@@ -94,34 +147,71 @@ async function loadData(): Promise {
try {
order.value = await SalesOrderAPI.detail(salesOrderId);
if (customerPackageId) customerPackage.value = await PackageAPI.customerPackageDetail(customerPackageId);
- } catch (error) { Message.error(getErrorMessage(error, "履约来源加载失败")); }
+ } catch (error) {
+ Message.error(getErrorMessage(error, "履约来源加载失败"));
+ }
}
-function getItemTitle(item: SourceItem): string { return `${item.productNameSnapshot || "未命名产品"} · ${item.unitSnapshot || "未设置单位"}`; }
-function getItemDescription(item: SourceItem): string {
+
+function getAvailableQuantity(item: SourceItem): string {
if (isPackage.value) {
- return `本次可履约 ${getPackageQuantityText((item as CustomerPackageItem).availableQuantity)}`;
+ return getPackageQuantityText((item as CustomerPackageItem).availableQuantity);
}
const orderItem = item as SalesOrderItem;
const availableQuantity = Math.max(0, Number(orderItem.quantity || 0) - Number(orderItem.reservedQuantity || 0) - Number(orderItem.fulfilledQuantity || 0));
- return `本次可履约 ${getPackageQuantityText(availableQuantity.toFixed(3))}`;
+ return getPackageQuantityText(availableQuantity.toFixed(3));
}
-function confirmTime(value: string | number): void { deliveredAtText.value = String(value); pickerVisible.value = false; }
+
+function getItemDescription(item: SourceItem): string {
+ return ` · 剩余 ${getAvailableQuantity(item)}`;
+}
+
+function fillAllQuantity(item: SourceItem): void {
+ if (item.id) {
+ quantityMap[item.id] = getAvailableQuantity(item);
+ }
+}
+
+function confirmTime(value: string | number): void {
+ deliveredAtText.value = String(value);
+ pickerVisible.value = false;
+}
+
function resolveTimestamp(value: string): number | undefined {
if (!value) return undefined;
const timestamp = new Date(value.replace(" ", "T")).getTime();
return Number.isFinite(timestamp) ? timestamp : undefined;
}
+
function buildAttachments(): Partial[] {
- return photoItems.value.flatMap(item => item.tempFileId ? [{ tempFileId: item.tempFileId, attachType: "PHOTO" }] : []);
+ return photoItems.value.flatMap(item => item.tempFileId ? [{
+ tempFileId: item.tempFileId,
+ attachType: "PHOTO"
+ }] : []);
}
+
function validate(): boolean {
- if (!order.value?.id) { Message.warning("请先从订单详情进入履约创建"); return false; }
- if (!buildAttachments().length) { Message.warning("履约必须上传至少一张照片"); return false; }
+ if (!order.value?.id) {
+ Message.warning("请先从订单详情进入履约创建");
+ return false;
+ }
+ if (!buildAttachments().length) {
+ Message.warning("履约必须上传至少一张照片");
+ return false;
+ }
const selected = sourceItems.value.filter(item => !!item.id && quantityMap[item.id]);
- if (!selected.length) { Message.warning("请填写至少一项履约数量"); return false; }
- for (const item of selected) { if (!normalizePackageQuantity(quantityMap[item.id || ""])) { Message.warning("履约数量必须是正数,最多三位小数"); return false; } }
+ if (!selected.length) {
+ Message.warning("请填写至少一项履约数量");
+ return false;
+ }
+ for (const item of selected) {
+ if (!normalizePackageQuantity(quantityMap[item.id || ""])) {
+ Message.warning("履约数量必须是正数,最多三位小数");
+ return false;
+ }
+ }
return true;
}
+
async function save(): Promise {
if (isSaving.value || !validate()) return;
if (!requestNo.value) requestNo.value = createPackageRequestNo();
@@ -141,12 +231,21 @@ async function save(): Promise {
attachmentList: buildAttachments(),
remark: remark.value.trim() || null
});
- Message.success("履约单已提交"); await router.replace(`/management/order-fulfillments/${result.id}`);
- } catch (error) { Message.error(getErrorMessage(error, "履约单创建失败,重试会复用同一请求号")); } finally { isSaving.value = false; }
+ Message.success("履约单已提交");
+ await router.replace(`/management/order-fulfillments/${result.id}`);
+ } catch (error) {
+ Message.error(getErrorMessage(error, "履约单创建失败,重试会复用同一请求号"));
+ } finally {
+ isSaving.value = false;
+ }
}
+
async function openOrderPicker(): Promise {
if (order.value?.id) {
- await router.push(`/management/sales-orders/${order.value.id}`);
+ await router.push({
+ path: `/management/sales-orders/${order.value.id}`,
+ query: { transitionDepth: String(getCurrentRouteDepth() + 1) }
+ });
return;
}
orderPickerVisible.value = true;
@@ -155,9 +254,13 @@ async function openOrderPicker(): Promise {
try {
const result = await SalesOrderAPI.list({ index: 0, size: 100, orderType: "DIRECT_PRODUCT", status: "ACTIVE" });
orderOptions.value = result.list || [];
- } catch (error) { Message.error(getErrorMessage(error, "可履约订单加载失败")); }
- finally { orderLoading.value = false; }
+ } catch (error) {
+ Message.error(getErrorMessage(error, "可履约订单加载失败"));
+ } finally {
+ orderLoading.value = false;
+ }
}
+
async function selectOrder(item: SalesOrder): Promise {
if (!item.id) return;
order.value = item;
@@ -165,16 +268,94 @@ async function selectOrder(item: SalesOrder): Promise {
for (const key of Object.keys(quantityMap)) delete quantityMap[key];
await router.replace({ query: { salesOrderId: item.id } });
}
-onMounted(async () => { navBarStore.setRightText("提交履约", "var(--td-brand-color)", save); await loadData(); });
+
+function getCurrentRouteDepth(): number {
+ const transitionDepth = Array.isArray(route.query.transitionDepth)
+ ? 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(async () => {
+ navBarStore.setRightText("提交", "var(--td-brand-color)", save);
+ await loadData();
+});
onUnmounted(() => navBarStore.clearRight());
diff --git a/src/types/Package.ts b/src/types/Package.ts
index 6d966af..6cd7580 100644
--- a/src/types/Package.ts
+++ b/src/types/Package.ts
@@ -36,6 +36,7 @@ export type CustomerPackageItem = {
customerPackageId?: string;
productId?: string;
productUnitId?: string;
+ attachmentList?: Attachment[];
productNameSnapshot?: string;
unitSnapshot?: string;
baseQuantityPerUnitSnapshot?: number | string;
diff --git a/src/types/SalesOrder.ts b/src/types/SalesOrder.ts
index 29c97b5..d32ffce 100644
--- a/src/types/SalesOrder.ts
+++ b/src/types/SalesOrder.ts
@@ -14,6 +14,7 @@ export type SalesOrderItem = {
salesOrderId?: string;
productId?: string;
productUnitId?: string;
+ attachmentList?: Attachment[];
productNameSnapshot?: string;
unitSnapshot?: string;
baseQuantityPerUnitSnapshot?: number | string;
diff --git a/src/utils/product.ts b/src/utils/product.ts
index 508fe5f..2b7cc34 100644
--- a/src/utils/product.ts
+++ b/src/utils/product.ts
@@ -78,7 +78,7 @@ export function getStockOperationText(type?: StockOperationType): string {
} as Record)[type || ""] || "未知操作";
}
-export function getProductPhotoURL(item: Product): string {
+export function getProductPhotoURL(item: Pick): string {
const attachment = item.attachmentList?.find(item => item.attachType === "PHOTO" && !!item.id);
return attachment?.id ? getAttachmentReadURL(attachment.id, 128) : "";
}