Files
gao-customer-h5/src/pages/salesOrder/SalesOrderPayment.vue
T
2026-09-22 01:29:34 +08:00

249 lines
7.3 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div v-can="permissionCodes.fundReceive.create" class="sales-order-operation">
<t-cell-group
v-if="order"
title="销售订单"
theme="card"
v-entrance-transition
>
<t-cell title="订单号" :note="order.orderNo || order.id" />
<t-cell
title="剩余待收款"
:note="formatPackageAmount(order.unpaidAmount)"
/>
</t-cell-group>
<t-form
v-if="order"
class="form"
label-width="7rem"
v-entrance-transition="70"
>
<t-form-item label="收款金额" required-mark>
<t-input
v-model.trim="amountText"
type="number"
align="right"
borderless
placeholder="输入收款金额"
>
<template #suffix-icon>
<t-button
size="extra-small"
theme="primary"
variant="outline"
content="全额收款"
@click="fillUnpaidAmount"
/>
</template>
</t-input>
</t-form-item>
<t-form-item label="收款时间" content-align="right">
<div class="time-actions">
<t-button
theme="primary"
variant="outline"
size="extra-small"
:content="occurredAtText || '此刻'"
@click="openOccurredAtPicker"
/>
<t-button
v-if="occurredAtText"
theme="primary"
variant="text"
size="extra-small"
content="此刻"
@click="resetOccurredAt"
/>
</div>
</t-form-item>
<t-form-item v-if="canDirectVerify" label="直接核销收款" content-align="right">
<t-switch v-model="directVerify" size="small" />
<template #help v-if="directVerify">
<span class="red">直接核销后,收款会立即计入订单已收款,不再进入待核销队列。</span>
</template>
</t-form-item>
<t-form-item label="备注">
<t-textarea
v-model="remark"
borderless
autosize
maxlength="500"
placeholder="请输入备注"
/>
</t-form-item>
<t-form-item label-width="0">
<media-selector
v-if="order"
:model-value="photoUrlList"
v-model:photo-items="photoItems"
class="photo-field"
:upload="uploadPhotoList"
:edit-mode="true"
required
title="收款凭证照片"
/>
</t-form-item>
</t-form>
<t-empty v-if="!order" :description="loading ? '正在加载订单' : '订单不存在'" />
<t-popup v-model="pickerVisible" placement="bottom">
<t-date-time-picker v-if="pickerVisible" v-model="draftTime" format="YYYY-MM-DD HH:mm" :mode="['date', 'minute']" title="收款时间" @cancel="pickerVisible = false" @confirm="confirmOccurredAt" />
</t-popup>
</div>
</template>
<script setup lang="ts">
import { useUserStore, type Attachment } from "timi-web-api";
import { Time } from "timi-web";
import MediaSelector from "@/components/mediaSelector/MediaSelector.vue";
import SalesOrderAPI from "@/api/SalesOrderAPI";
import { useNavBarStore } from "@/store/navBarStore";
import type { SalesOrder } from "@/types/SalesOrder";
import type { PhotoItem } from "@/types/Photo";
import { createPackageRequestNo, formatPackageAmount, formatPackageAmountInput, parsePackageAmount } from "@/utils/package";
import { getErrorMessage } from "@/utils/error";
import { emitListDataChanged, listDataEventKeys } from "@/utils/listDataEvents";
import { Message } from "@/utils/message";
import { gaoRoleCodes, permissionCodes } from "@/utils/permission";
import { uploadPhotoList } from "@/utils/photo";
defineOptions({ name: "SalesOrderPayment" });
const route = useRoute();
const router = useRouter();
const navBarStore = useNavBarStore();
const userStore = useUserStore();
const order = ref<SalesOrder>();
const loading = ref(false);
const saving = ref(false);
const amountText = ref("");
const occurredAtText = ref("");
const draftTime = ref("");
const pickerVisible = ref(false);
const remark = ref("");
const photoItems = ref<PhotoItem[]>([]);
const photoUrlList = computed(() => photoItems.value.map(item => item.url));
const requestNo = ref("");
const directVerify = ref(false);
const canDirectVerify = computed(() => userStore.hasRoles(gaoRoleCodes.storeManager)
&& userStore.hasPermissions(permissionCodes.fundReceive.create)
&& userStore.hasPermissions(permissionCodes.fundReceive.verify));
function openOccurredAtPicker(): void {
draftTime.value = occurredAtText.value || Time.formatDateTime(new Date());
pickerVisible.value = true;
}
function confirmOccurredAt(value?: string | number): void {
occurredAtText.value = String(value || draftTime.value);
pickerVisible.value = false;
}
function resetOccurredAt(): void {
occurredAtText.value = "";
draftTime.value = "";
}
function fillUnpaidAmount(): void {
amountText.value = formatPackageAmountInput(order.value?.unpaidAmount);
}
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<Attachment>[] {
return photoItems.value.flatMap(item => item.tempFileId ? [{
tempFileId: item.tempFileId,
attachType: "PHOTO"
}] : []);
}
async function loadData(): Promise<void> {
const id = String(route.params.id || "");
if (!id) return;
loading.value = true;
try {
order.value = await SalesOrderAPI.detail(id);
} catch (error) {
Message.error(getErrorMessage(error, "订单加载失败"));
} finally {
loading.value = false;
}
}
async function save(): Promise<void> {
if (saving.value || !order.value?.id) return;
if (directVerify.value && !canDirectVerify.value) {
Message.warning("当前账号没有店长直接核销权限");
return;
}
const amount = parsePackageAmount(amountText.value);
const attachmentList = buildAttachments();
if (amount === undefined || amount === 0) {
Message.warning("请输入合法的正金额");
return;
}
if (!attachmentList.length) {
Message.warning("收款必须上传至少一张照片");
return;
}
if (!requestNo.value) requestNo.value = createPackageRequestNo();
saving.value = true;
try {
const request = {
salesOrderId: order.value.id,
requestNo: requestNo.value,
amount,
occurredAt: resolveTimestamp(occurredAtText.value),
attachmentList,
remark: remark.value.trim() || null
};
const record = directVerify.value
? await SalesOrderAPI.receivePaymentAndVerify(request)
: await SalesOrderAPI.receivePayment(request);
emitListDataChanged(listDataEventKeys.salesOrderChanged, order.value.id);
Message.success(directVerify.value ? "收款登记并核销成功" : "收款登记已提交");
await router.replace(record.id && userStore.hasPermissions(permissionCodes.fundReceive.create)
? `/management/fund-records/${record.id}`
: `/management/sales-orders/${order.value.id}`);
} catch (error) {
Message.error(getErrorMessage(error, "收款登记失败,重试会复用同一请求号"));
} finally {
saving.value = false;
}
}
onMounted(async () => {
if (!userStore.hasPermissions(permissionCodes.fundReceive.create)) return;
navBarStore.setRightText("提交", "var(--td-brand-color)", save);
await loadData();
});
onUnmounted(() => navBarStore.clearRight());
</script>
<style scoped lang="less">
.sales-order-operation {
gap: 1rem;
padding: calc(var(--app-nav-height) + 1rem) 0 2rem;
display: flex;
flex-direction: column;
.picker-text {
width: 100%;
text-align: right;
}
.time-actions {
gap: .5rem;
display: flex;
align-items: center;
justify-content: flex-end;
}
.photo-field {
padding: 0 1rem;
}
}
</style>