add package
This commit is contained in:
@@ -0,0 +1,190 @@
|
||||
<template>
|
||||
<div v-can="permissionCodes.fundRecord.read" class="package-record-detail">
|
||||
<template v-if="record">
|
||||
<t-cell-group title="资金记录" theme="card" v-entrance-transition>
|
||||
<t-cell title="操作类型" :note="getFundOperationText(record.operationType)" />
|
||||
<t-cell title="状态"><template #note><t-tag :content="getFundStatusText(record.status)" :theme="getStatusTheme(record.status)" variant="light-outline" /></template></t-cell>
|
||||
<t-cell title="金额" :note="formatPackageAmount(record.amount)" />
|
||||
<t-cell title="客户套餐" :note="record.customerPackageId || record.bizId || '未知'" />
|
||||
<t-cell title="发生时间" :note="record.occurredAt ? Time.toDateTime(record.occurredAt) : '未知'" />
|
||||
<t-cell title="提交人" :note="record.operator?.name || record.operator?.nick || record.operatorUserId || '未知'" />
|
||||
<t-cell title="处理人" :note="record.verifier?.name || record.verifier?.nick || record.verifierUserId || '未处理'" />
|
||||
<t-cell v-if="record.sourceRecordId" title="原资金记录" :note="record.sourceRecordId" />
|
||||
<t-cell v-if="record.reason" title="原因" :note="record.reason" />
|
||||
<t-cell v-if="record.remark" title="备注" :note="record.remark" />
|
||||
</t-cell-group>
|
||||
<photo-scroller :model-value="photoURLList" :photo-items="photoItems" title="业务照片" :show-add="false" :show-remove="false" :show-viewer-actions="false" />
|
||||
<section v-if="isPendingReceive || canRevoke" class="actions" v-entrance-transition="70">
|
||||
<t-button v-if="canVerifyReceive" theme="primary" @click="verifyReceive">核销收款</t-button>
|
||||
<t-button v-if="canVerifyReceive" theme="default" variant="outline" @click="openReasonDialog('reject')">拒绝收款</t-button>
|
||||
<t-button v-if="canCancelReceive" theme="default" variant="outline" @click="openReasonDialog('cancel')">撤销收款</t-button>
|
||||
<t-button v-if="canRevoke" theme="danger" variant="outline" @click="toRevoke">资金冲正</t-button>
|
||||
</section>
|
||||
</template>
|
||||
<t-empty v-else :description="isLoading ? '正在加载资金记录' : '资金记录不存在'" />
|
||||
<t-dialog v-model:visible="reasonDialogVisible" :title="reasonTitle" :confirm-btn="reasonActionText" @confirm="submitReason">
|
||||
<t-textarea v-model.trim="reasonText" borderless autosize placeholder="请输入原因" />
|
||||
</t-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { DialogPlugin } from "tdesign-mobile-vue";
|
||||
import { Time } from "timi-web";
|
||||
import { useUserStore } from "timi-web-api";
|
||||
import PackageAPI from "@/api/PackageAPI";
|
||||
import PhotoScroller from "@/components/PhotoScroller.vue";
|
||||
import { useNavBarStore } from "@/store/navBarStore";
|
||||
import type { FundRecordStatus, GaoFundRecord } from "@/types/Package";
|
||||
import type { PhotoItem } from "@/types/Photo";
|
||||
import { getAttachmentReadURL } from "@/utils/attachment";
|
||||
import { getErrorMessage } from "@/utils/error";
|
||||
import { formatPackageAmount, getFundOperationText, getFundStatusText } from "@/utils/package";
|
||||
import { Message } from "@/utils/message";
|
||||
import { permissionCodes } from "@/utils/permission";
|
||||
|
||||
type ReasonAction = "reject" | "cancel";
|
||||
|
||||
defineOptions({ name: "FundRecordDetail" });
|
||||
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const userStore = useUserStore();
|
||||
const navBarStore = useNavBarStore();
|
||||
const record = ref<GaoFundRecord>();
|
||||
const isLoading = ref(false);
|
||||
const isOperating = ref(false);
|
||||
const reasonDialogVisible = ref(false);
|
||||
const reasonText = ref("");
|
||||
const reasonAction = ref<ReasonAction>("reject");
|
||||
const isPendingReceive = computed(() => record.value?.operationType === "RECEIVE" && record.value.status === "PENDING");
|
||||
const canVerifyReceive = computed(() => isPendingReceive.value && userStore.hasPermissions(permissionCodes.fundReceive.verify));
|
||||
const canCancelReceive = computed(() => isPendingReceive.value && userStore.hasPermissions(permissionCodes.fundReceive.create));
|
||||
const canRevoke = computed(() => record.value?.status === "VERIFIED" && record.value.operationType !== "REVOKE" && userStore.hasPermissions(permissionCodes.fund.revoke));
|
||||
const reasonTitle = computed(() => reasonAction.value === "reject" ? "拒绝收款" : "撤销收款");
|
||||
const reasonActionText = computed(() => reasonAction.value === "reject" ? "拒绝" : "撤销");
|
||||
const photoItems = computed<PhotoItem[]>(() => (record.value?.attachmentList || [])
|
||||
.filter(item => !!item.id)
|
||||
.map(item => ({
|
||||
attachment: item,
|
||||
attachType: item.attachType || "PHOTO",
|
||||
url: getAttachmentReadURL(item.id || "")
|
||||
})));
|
||||
const photoURLList = computed(() => photoItems.value.map(item => item.url));
|
||||
|
||||
function getStatusTheme(status?: FundRecordStatus): "success" | "danger" | "default" {
|
||||
return status === "VERIFIED" ? "success" : status === "REJECTED" ? "danger" : "default";
|
||||
}
|
||||
|
||||
async function loadData(): Promise<void> {
|
||||
const id = String(route.params.id || "");
|
||||
if (!id) {
|
||||
return;
|
||||
}
|
||||
isLoading.value = true;
|
||||
try {
|
||||
record.value = await PackageAPI.fundRecordDetail(id);
|
||||
} catch (error) {
|
||||
Message.error(getErrorMessage(error, "资金记录加载失败"));
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function refreshRelatedPackage(): Promise<void> {
|
||||
const customerPackageId = record.value?.customerPackageId || record.value?.bizId;
|
||||
if (customerPackageId) {
|
||||
await PackageAPI.customerPackageDetail(customerPackageId);
|
||||
}
|
||||
}
|
||||
|
||||
async function verifyReceive(): Promise<void> {
|
||||
if (!record.value?.id || !canVerifyReceive.value || isOperating.value) {
|
||||
return;
|
||||
}
|
||||
const dialog = DialogPlugin.confirm({
|
||||
title: "核销收款",
|
||||
content: "核销后该金额才会计入套餐的已收款。确定继续?",
|
||||
confirmBtn: "核销",
|
||||
onConfirm: async () => {
|
||||
isOperating.value = true;
|
||||
try {
|
||||
await PackageAPI.verifyPayment(record.value!.id!);
|
||||
await refreshRelatedPackage();
|
||||
Message.success("收款已核销");
|
||||
await loadData();
|
||||
} catch (error) {
|
||||
Message.error(getErrorMessage(error, "收款核销失败,请刷新后确认状态"));
|
||||
} finally {
|
||||
isOperating.value = false;
|
||||
dialog.destroy();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function openReasonDialog(action: ReasonAction): void {
|
||||
if (!isPendingReceive.value || isOperating.value) {
|
||||
return;
|
||||
}
|
||||
reasonAction.value = action;
|
||||
reasonText.value = "";
|
||||
reasonDialogVisible.value = true;
|
||||
}
|
||||
|
||||
async function submitReason(): Promise<void> {
|
||||
if (!record.value?.id || !reasonText.value.trim() || isOperating.value) {
|
||||
Message.warning("请输入原因");
|
||||
return;
|
||||
}
|
||||
isOperating.value = true;
|
||||
try {
|
||||
if (reasonAction.value === "reject") {
|
||||
await PackageAPI.rejectPayment(record.value.id, reasonText.value.trim());
|
||||
} else {
|
||||
await PackageAPI.cancelPayment(record.value.id, reasonText.value.trim());
|
||||
}
|
||||
reasonDialogVisible.value = false;
|
||||
await refreshRelatedPackage();
|
||||
Message.success(reasonAction.value === "reject" ? "收款已拒绝" : "收款已撤销");
|
||||
await loadData();
|
||||
} catch (error) {
|
||||
Message.error(getErrorMessage(error, "收款状态操作失败,请刷新后确认状态"));
|
||||
} finally {
|
||||
isOperating.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function toRevoke(): Promise<void> {
|
||||
if (record.value?.id && canRevoke.value) {
|
||||
await router.push(`/management/fund-records/${record.value.id}/revoke`);
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(loadData);
|
||||
onUnmounted(() => navBarStore.clearRight());
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.package-record-detail {
|
||||
gap: 1rem;
|
||||
padding: calc(var(--app-nav-height) + 1rem) 0 2rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
:deep(.photo-scroller) {
|
||||
padding: 0 1rem;
|
||||
}
|
||||
|
||||
.actions {
|
||||
gap: .5rem;
|
||||
padding: 0 1rem;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
|
||||
.t-button {
|
||||
flex: 1 1 8rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user