|
|
@@ -16,21 +16,56 @@
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</section>
|
|
|
|
</section>
|
|
|
|
<t-cell-group title="订单信息" class="info" theme="card" v-entrance-transition="70">
|
|
|
|
<t-form class="info" title="订单信息" @submit="saveEdit" v-entrance-transition="70">
|
|
|
|
<t-cell v-if="order.sourceEventRecord?.id" title="来源登记记录" arrow @click="toEventRecord" />
|
|
|
|
<t-form-item v-if="order.sourceEventRecord?.id" label="来源登记记录" arrow @click="toEventRecord">
|
|
|
|
<t-cell title="备注" :note="Text.display(order.remark)" />
|
|
|
|
<span>查看来源记录</span>
|
|
|
|
<div class="photo-wrap bg-white">
|
|
|
|
</t-form-item>
|
|
|
|
<photo-scroller
|
|
|
|
<t-form-item label="备注">
|
|
|
|
:model-value="photoURLList"
|
|
|
|
<t-textarea v-if="isEditing" v-model.trim="editRemark" class="remark-editor" borderless autosize maxlength="500" placeholder="记录本单需要注意的内容" />
|
|
|
|
:photo-items="photoItems"
|
|
|
|
<span v-else v-text="Text.display(order.remark)" />
|
|
|
|
title="订单照片"
|
|
|
|
</t-form-item>
|
|
|
|
:show-add="false"
|
|
|
|
<t-form-item label-width="0">
|
|
|
|
:show-remove="false"
|
|
|
|
<div class="photo-wrap">
|
|
|
|
:show-viewer-actions="false"
|
|
|
|
<photo-scroller
|
|
|
|
v-entrance-transition="105"
|
|
|
|
:model-value="photoURLList"
|
|
|
|
/>
|
|
|
|
v-model:photo-items="photoItems"
|
|
|
|
</div>
|
|
|
|
:edit-mode="isEditing"
|
|
|
|
</t-cell-group>
|
|
|
|
:show-add="isEditing"
|
|
|
|
|
|
|
|
:show-remove="isEditing"
|
|
|
|
|
|
|
|
:show-viewer-actions="isEditing"
|
|
|
|
|
|
|
|
:upload="isEditing ? uploadPhotoList : undefined"
|
|
|
|
|
|
|
|
title="订单照片"
|
|
|
|
|
|
|
|
v-entrance-transition="105"
|
|
|
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
</t-form-item>
|
|
|
|
|
|
|
|
<t-form-item v-if="canUpdate" label-width="0">
|
|
|
|
|
|
|
|
<div class="edit-actions">
|
|
|
|
|
|
|
|
<t-button
|
|
|
|
|
|
|
|
v-if="!isEditing"
|
|
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
|
|
size="extra-small"
|
|
|
|
|
|
|
|
theme="primary"
|
|
|
|
|
|
|
|
@click="toEdit"
|
|
|
|
|
|
|
|
>编辑</t-button>
|
|
|
|
|
|
|
|
<template v-else>
|
|
|
|
|
|
|
|
<t-button
|
|
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
|
|
size="extra-small"
|
|
|
|
|
|
|
|
:disabled="isSaving"
|
|
|
|
|
|
|
|
@click="cancelEdit"
|
|
|
|
|
|
|
|
>取消</t-button>
|
|
|
|
|
|
|
|
<t-button
|
|
|
|
|
|
|
|
type="submit"
|
|
|
|
|
|
|
|
theme="primary"
|
|
|
|
|
|
|
|
size="extra-small"
|
|
|
|
|
|
|
|
:loading="isSaving"
|
|
|
|
|
|
|
|
:disabled="isSaving"
|
|
|
|
|
|
|
|
>保存</t-button>
|
|
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
</t-form-item>
|
|
|
|
|
|
|
|
</t-form>
|
|
|
|
<t-cell-group
|
|
|
|
<t-cell-group
|
|
|
|
v-if="order.itemList?.length"
|
|
|
|
v-if="order.itemList?.length"
|
|
|
|
class="product"
|
|
|
|
class="product"
|
|
|
@@ -126,7 +161,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
<script setup lang="ts">
|
|
|
|
<script setup lang="ts">
|
|
|
|
import { PassTimeLabel, Text, Time } from "timi-web";
|
|
|
|
import { PassTimeLabel, Text, Time } from "timi-web";
|
|
|
|
import { useUserStore } from "timi-web-api";
|
|
|
|
import { type Attachment, useUserStore } from "timi-web-api";
|
|
|
|
import SalesOrderAPI from "@/api/SalesOrderAPI";
|
|
|
|
import SalesOrderAPI from "@/api/SalesOrderAPI";
|
|
|
|
import CustomerProfileCard from "@/components/CustomerProfileCard.vue";
|
|
|
|
import CustomerProfileCard from "@/components/CustomerProfileCard.vue";
|
|
|
|
import PhotoScroller from "@/components/PhotoScroller.vue";
|
|
|
|
import PhotoScroller from "@/components/PhotoScroller.vue";
|
|
|
@@ -144,6 +179,7 @@ import {
|
|
|
|
} from "@/utils/salesOrder";
|
|
|
|
} from "@/utils/salesOrder";
|
|
|
|
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";
|
|
|
|
|
|
|
|
|
|
|
|
defineOptions({ name: "SalesOrderDetail" });
|
|
|
|
defineOptions({ name: "SalesOrderDetail" });
|
|
|
|
const route = useRoute();
|
|
|
|
const route = useRoute();
|
|
|
@@ -152,7 +188,12 @@ const navBarStore = useNavBarStore();
|
|
|
|
const userStore = useUserStore();
|
|
|
|
const userStore = useUserStore();
|
|
|
|
const order = ref<SalesOrder>();
|
|
|
|
const order = ref<SalesOrder>();
|
|
|
|
const loading = ref(false);
|
|
|
|
const loading = ref(false);
|
|
|
|
|
|
|
|
const isEditing = ref(false);
|
|
|
|
|
|
|
|
const isSaving = ref(false);
|
|
|
|
|
|
|
|
const editRemark = ref("");
|
|
|
|
|
|
|
|
const photoItems = ref<PhotoItem[]>([]);
|
|
|
|
const canReadFund = computed(() => userStore.hasPermissions(permissionCodes.fundRecord.read));
|
|
|
|
const canReadFund = computed(() => userStore.hasPermissions(permissionCodes.fundRecord.read));
|
|
|
|
|
|
|
|
const canUpdate = computed(() => userStore.hasPermissions(permissionCodes.salesOrder.update));
|
|
|
|
const canCreateFulfillment = computed(() => userStore.hasPermissions(permissionCodes.orderFulfillment.create));
|
|
|
|
const canCreateFulfillment = computed(() => userStore.hasPermissions(permissionCodes.orderFulfillment.create));
|
|
|
|
const canOperateStock = computed(() => userStore.hasPermissions(permissionCodes.productStock.create));
|
|
|
|
const canOperateStock = computed(() => userStore.hasPermissions(permissionCodes.productStock.create));
|
|
|
|
const canReceive = computed(() => !!order.value && order.value.status !== "CANCELLED" && order.value.paymentStatus !== "PAID" && userStore.hasPermissions(permissionCodes.fundReceive.create));
|
|
|
|
const canReceive = computed(() => !!order.value && order.value.status !== "CANCELLED" && order.value.paymentStatus !== "PAID" && userStore.hasPermissions(permissionCodes.fundReceive.create));
|
|
|
@@ -160,16 +201,29 @@ const canRefund = computed(() => !!order.value && order.value.status === "CANCEL
|
|
|
|
const canConfirmNoRefund = computed(() => !!order.value && order.value.status === "CANCELLED" && order.value.refundStatus === "PENDING" && userStore.hasPermissions(permissionCodes.fund.refund));
|
|
|
|
const canConfirmNoRefund = computed(() => !!order.value && order.value.status === "CANCELLED" && order.value.refundStatus === "PENDING" && userStore.hasPermissions(permissionCodes.fund.refund));
|
|
|
|
const canCancel = computed(() => !!order.value && order.value.status === "ACTIVE" && userStore.hasPermissions(permissionCodes.salesOrder.cancel));
|
|
|
|
const canCancel = computed(() => !!order.value && order.value.status === "ACTIVE" && userStore.hasPermissions(permissionCodes.salesOrder.cancel));
|
|
|
|
const canFulfillByPayment = computed(() => order.value?.paymentStatus === "PAID" || (order.value?.paymentStatus === "PARTIALLY_PAID" && canOperateStock.value));
|
|
|
|
const canFulfillByPayment = computed(() => order.value?.paymentStatus === "PAID" || (order.value?.paymentStatus === "PARTIALLY_PAID" && canOperateStock.value));
|
|
|
|
const photoItems = computed<PhotoItem[]>(() => (order.value?.attachmentList || [])
|
|
|
|
|
|
|
|
.filter(item => !!item.id)
|
|
|
|
|
|
|
|
.map(item => ({
|
|
|
|
|
|
|
|
attachment: item,
|
|
|
|
|
|
|
|
attachType: item.attachType || "PHOTO",
|
|
|
|
|
|
|
|
previewUrl: getAttachmentReadURL(item.id || ""),
|
|
|
|
|
|
|
|
url: getAttachmentReadURL(item.id || "", 90)
|
|
|
|
|
|
|
|
})));
|
|
|
|
|
|
|
|
const photoURLList = computed(() => photoItems.value.map(item => item.url));
|
|
|
|
const photoURLList = computed(() => photoItems.value.map(item => item.url));
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function toPhotoItems(attachmentList?: readonly Partial<Attachment>[]): PhotoItem[] {
|
|
|
|
|
|
|
|
return (attachmentList || [])
|
|
|
|
|
|
|
|
.filter(item => !!item.id)
|
|
|
|
|
|
|
|
.map(item => ({
|
|
|
|
|
|
|
|
attachment: item,
|
|
|
|
|
|
|
|
attachType: item.attachType || "PHOTO",
|
|
|
|
|
|
|
|
previewUrl: getAttachmentReadURL(item.id || ""),
|
|
|
|
|
|
|
|
url: getAttachmentReadURL(item.id || "", 90)
|
|
|
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function buildAttachmentList(): Partial<Attachment>[] {
|
|
|
|
|
|
|
|
return photoItems.value.flatMap((item): Partial<Attachment>[] => {
|
|
|
|
|
|
|
|
const attachmentId = item.attachment?.id;
|
|
|
|
|
|
|
|
if (attachmentId) {
|
|
|
|
|
|
|
|
return [{ id: attachmentId, attachType: item.attachType || item.attachment?.attachType || "PHOTO" }];
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
return item.tempFileId ? [{ tempFileId: item.tempFileId, attachType: item.attachType || "PHOTO" }] : [];
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function getPaidAtText(paidAt?: number | null): string {
|
|
|
|
function getPaidAtText(paidAt?: number | null): string {
|
|
|
|
return paidAt ? Time.toDateTime(paidAt) : "待后端回填";
|
|
|
|
return paidAt ? Time.toDateTime(paidAt) : "待后端回填";
|
|
|
|
}
|
|
|
|
}
|
|
|
@@ -232,10 +286,18 @@ function getItemFulfillmentStatus(item: SalesOrderItem): { text: string; theme:
|
|
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
isEditing.value = false;
|
|
|
|
|
|
|
|
navBarStore.clearRight();
|
|
|
|
|
|
|
|
if (!id) {
|
|
|
|
|
|
|
|
order.value = undefined;
|
|
|
|
|
|
|
|
photoItems.value = [];
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
}
|
|
|
|
loading.value = true;
|
|
|
|
loading.value = true;
|
|
|
|
try {
|
|
|
|
try {
|
|
|
|
order.value = await SalesOrderAPI.detail(id);
|
|
|
|
order.value = await SalesOrderAPI.detail(id);
|
|
|
|
|
|
|
|
editRemark.value = order.value.remark || "";
|
|
|
|
|
|
|
|
photoItems.value = toPhotoItems(order.value.attachmentList);
|
|
|
|
} catch (error) {
|
|
|
|
} catch (error) {
|
|
|
|
Message.error(getErrorMessage(error, "订单加载失败"));
|
|
|
|
Message.error(getErrorMessage(error, "订单加载失败"));
|
|
|
|
} finally {
|
|
|
|
} finally {
|
|
|
@@ -243,6 +305,42 @@ async function loadData(): Promise<void> {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function toEdit(): void {
|
|
|
|
|
|
|
|
if (!order.value || !canUpdate.value) return;
|
|
|
|
|
|
|
|
editRemark.value = order.value.remark || "";
|
|
|
|
|
|
|
|
photoItems.value = toPhotoItems(order.value.attachmentList);
|
|
|
|
|
|
|
|
isEditing.value = true;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function cancelEdit(): void {
|
|
|
|
|
|
|
|
if (isSaving.value) return;
|
|
|
|
|
|
|
|
editRemark.value = order.value?.remark || "";
|
|
|
|
|
|
|
|
photoItems.value = toPhotoItems(order.value?.attachmentList);
|
|
|
|
|
|
|
|
isEditing.value = false;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async function saveEdit(): Promise<void> {
|
|
|
|
|
|
|
|
const orderId = order.value?.id;
|
|
|
|
|
|
|
|
if (!orderId || !canUpdate.value || !isEditing.value || isSaving.value) return;
|
|
|
|
|
|
|
|
isSaving.value = true;
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
|
|
const result = await SalesOrderAPI.update({
|
|
|
|
|
|
|
|
id: orderId,
|
|
|
|
|
|
|
|
attachmentList: buildAttachmentList(),
|
|
|
|
|
|
|
|
remark: editRemark.value.trim() || null
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
order.value = result;
|
|
|
|
|
|
|
|
editRemark.value = result.remark || "";
|
|
|
|
|
|
|
|
photoItems.value = toPhotoItems(result.attachmentList);
|
|
|
|
|
|
|
|
isEditing.value = false;
|
|
|
|
|
|
|
|
Message.success("订单信息已保存");
|
|
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
|
|
Message.error(getErrorMessage(error, "订单信息保存失败"));
|
|
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
|
|
isSaving.value = false;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async function toEventRecord(): Promise<void> {
|
|
|
|
async function toEventRecord(): Promise<void> {
|
|
|
|
if (order.value?.sourceEventRecord?.id) await router.push(`/management/registration-event-records/${order.value.sourceEventRecord.id}`);
|
|
|
|
if (order.value?.sourceEventRecord?.id) await router.push(`/management/registration-event-records/${order.value.sourceEventRecord.id}`);
|
|
|
|
}
|
|
|
|
}
|
|
|
@@ -302,6 +400,7 @@ function getCurrentRouteDepth(): number {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
onMounted(loadData);
|
|
|
|
onMounted(loadData);
|
|
|
|
|
|
|
|
watch(() => route.params.id, loadData);
|
|
|
|
onUnmounted(() => navBarStore.clearRight());
|
|
|
|
onUnmounted(() => navBarStore.clearRight());
|
|
|
|
</script>
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
@@ -365,9 +464,32 @@ onUnmounted(() => navBarStore.clearRight());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.info {
|
|
|
|
.info {
|
|
|
|
|
|
|
|
margin: 0 1rem;
|
|
|
|
|
|
|
|
padding: .5rem 0;
|
|
|
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
|
|
|
background: var(--td-bg-color-container, #fff);
|
|
|
|
|
|
|
|
box-shadow: var(--td-shadow-1);
|
|
|
|
|
|
|
|
border-radius: 1.25rem;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.form-title {
|
|
|
|
|
|
|
|
padding: .75rem 1rem .5rem;
|
|
|
|
|
|
|
|
font-size: .875rem;
|
|
|
|
|
|
|
|
font-weight: 700;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.photo-wrap {
|
|
|
|
.photo-wrap {
|
|
|
|
padding: .75rem;
|
|
|
|
flex: 1;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.edit-actions {
|
|
|
|
|
|
|
|
gap: .5rem;
|
|
|
|
|
|
|
|
flex: 1;
|
|
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
|
|
justify-content: flex-end;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.remark-editor {
|
|
|
|
|
|
|
|
width: 100%;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|