fix FundRecordRevoke.vue style
This commit is contained in:
@@ -4,7 +4,7 @@
|
||||
<section class="hero" v-entrance-transition>
|
||||
<div class="header">
|
||||
<span class="eyebrow" v-text="'资金流水'" />
|
||||
<t-tag :content="getFundStatusText(record.status)" :theme="getStatusTheme(record.status)" variant="light-outline" />
|
||||
<t-tag :content="getRecordStatusText(record)" :theme="getRecordStatusTheme(record)" variant="light-outline" />
|
||||
</div>
|
||||
<h1 class="title" v-text="getFundOperationText(record.operationType)" />
|
||||
<div class="amount">
|
||||
@@ -12,19 +12,28 @@
|
||||
<strong class="value red text-bold" v-text="formatPackageAmount(record.amount)" />
|
||||
</div>
|
||||
</section>
|
||||
<t-cell-group title="资金记录" theme="card" v-entrance-transition>
|
||||
<t-cell-group title="订单信息" theme="card" v-entrance-transition="70">
|
||||
<t-cell title="订单总价" :note="formatPackageAmount(record.salesOrderAmount)" />
|
||||
<t-cell title="剩余待收款(流水快照)" :note="formatPackageAmount(record.salesOrderUnpaidAmountSnapshot)" />
|
||||
</t-cell-group>
|
||||
<t-cell-group title="资金记录" theme="card" v-entrance-transition="140">
|
||||
<t-cell title="操作类型" :note="getFundOperationText(record.operationType)" />
|
||||
<t-cell title="状态">
|
||||
<template #note>
|
||||
<t-tag
|
||||
:content="getFundStatusText(record.status)"
|
||||
:theme="getStatusTheme(record.status)"
|
||||
:content="getRecordStatusText(record)"
|
||||
:theme="getRecordStatusTheme(record)"
|
||||
variant="light-outline"
|
||||
/>
|
||||
</template>
|
||||
</t-cell>
|
||||
<t-cell title="金额" :note="formatPackageAmount(record.amount)" />
|
||||
<t-cell v-if="record.sourceRecordId" title="原资金记录" :note="record.sourceRecordId" />
|
||||
<t-cell
|
||||
v-if="record.sourceRecordId"
|
||||
title="冲正流水"
|
||||
arrow
|
||||
@click="toSourceRecord"
|
||||
/>
|
||||
<t-cell title="备注" :note="Text.display(record.remark)" />
|
||||
<t-cell v-if="record.reason" title="拒绝/撤销原因" :note="Text.display(record.reason)" />
|
||||
<t-cell
|
||||
@@ -43,7 +52,7 @@
|
||||
/>
|
||||
</div>
|
||||
</t-cell-group>
|
||||
<t-cell-group theme="card">
|
||||
<t-cell-group theme="card" v-entrance-transition="210">
|
||||
<t-cell
|
||||
title="发起人"
|
||||
:note="record.operator?.name || record.operator?.nick || '未知'"
|
||||
@@ -66,7 +75,7 @@
|
||||
<section
|
||||
v-if="isPendingReceive || canRevoke"
|
||||
class="actions"
|
||||
v-entrance-transition="70"
|
||||
v-entrance-transition="280"
|
||||
>
|
||||
<div class="pair">
|
||||
<t-button
|
||||
@@ -144,12 +153,23 @@ const isLoading = ref(false);
|
||||
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 canRevoke = computed(() => record.value?.status === "VERIFIED"
|
||||
&& record.value.operationType !== "REVOKE"
|
||||
&& !record.value.revokeRecordId
|
||||
&& userStore.hasPermissions(permissionCodes.fund.revoke));
|
||||
|
||||
function getStatusTheme(status?: FundRecordStatus): "success" | "warning" | "danger" | "default" {
|
||||
return status === "VERIFIED" ? "success" : status === "PENDING" ? "warning" : status === "REJECTED" ? "danger" : "default";
|
||||
}
|
||||
|
||||
function getRecordStatusText(recordValue: FundRecord): string {
|
||||
return recordValue.revokeRecordId ? "已被冲正" : getFundStatusText(recordValue.status);
|
||||
}
|
||||
|
||||
function getRecordStatusTheme(recordValue: FundRecord): "success" | "warning" | "danger" | "default" {
|
||||
return recordValue.revokeRecordId ? "danger" : getStatusTheme(recordValue.status);
|
||||
}
|
||||
|
||||
async function loadData(): Promise<void> {
|
||||
const id = String(route.params.id || "");
|
||||
if (!id) {
|
||||
@@ -179,6 +199,18 @@ async function toSalesOrder(): Promise<void> {
|
||||
});
|
||||
}
|
||||
|
||||
// ---------- 原资金记录 ----------
|
||||
|
||||
async function toSourceRecord(): Promise<void> {
|
||||
if (!record.value?.sourceRecordId) {
|
||||
return;
|
||||
}
|
||||
await router.push({
|
||||
path: `/management/fund-records/${record.value.sourceRecordId}`,
|
||||
query: { transitionDepth: String(getCurrentRouteDepth() + 1) }
|
||||
});
|
||||
}
|
||||
|
||||
function getCurrentRouteDepth(): number {
|
||||
const transitionDepth = Array.isArray(route.query.transitionDepth)
|
||||
? route.query.transitionDepth[0]
|
||||
@@ -216,7 +248,11 @@ async function verifyReceive(): Promise<void> {
|
||||
const dialog = DialogPlugin.confirm({
|
||||
title: "核销收款",
|
||||
content: "核销后该金额才会计入套餐的已收款。确定继续?",
|
||||
cancelBtn: "取消",
|
||||
confirmBtn: "核销",
|
||||
onCancel() {
|
||||
dialog.destroy();
|
||||
},
|
||||
onConfirm: async () => {
|
||||
isOperating.value = true;
|
||||
try {
|
||||
|
||||
@@ -30,9 +30,9 @@
|
||||
<div class="title">
|
||||
<span v-text="getFundOperationText(item.operationType)" />
|
||||
<t-tag
|
||||
:content="getFundStatusText(item.status)"
|
||||
:content="getRecordStatusText(item)"
|
||||
variant="light-outline"
|
||||
:theme="getStatusTheme(item.status)"
|
||||
:theme="getRecordStatusTheme(item)"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
@@ -48,14 +48,29 @@
|
||||
<span class="text-bold" v-text="getVerifierText(item)" />
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<pass-time-label :timestamp="item.occurredAt" underline />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template #note>
|
||||
<div class="amount">
|
||||
<div class="amounts">
|
||||
<div class="order-amount">
|
||||
<span class="label">订单总价</span>
|
||||
<span>¥</span>
|
||||
<span v-text="formatPackageAmount(item.salesOrderAmount)" />
|
||||
</div>
|
||||
<div class="unpaid-amount">
|
||||
<span class="label">剩余待收</span>
|
||||
<span>¥</span>
|
||||
<span v-text="formatPackageAmount(item.salesOrderUnpaidAmountSnapshot)" />
|
||||
</div>
|
||||
<div class="record-amount">
|
||||
<span class="label">流水金额</span>
|
||||
<span class="red">¥</span>
|
||||
<span class="value red" v-text="formatPackageAmount(item.amount)" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</t-cell>
|
||||
</t-cell-group>
|
||||
@@ -72,7 +87,7 @@ import { useNavBarStore } from "@/store/navBarStore";
|
||||
import type { FundOperationType, FundRecord, FundRecordStatus } from "@/types/SalesOrder";
|
||||
import { getErrorMessage } from "@/utils/error";
|
||||
import { formatPackageAmount } from "@/utils/package";
|
||||
import { getFundBizTypeText, getFundOperationText, getFundStatusText } from "@/utils/salesOrder";
|
||||
import { getFundOperationText, getFundStatusText } from "@/utils/salesOrder";
|
||||
import { Message } from "@/utils/message";
|
||||
import { permissionCodes } from "@/utils/permission";
|
||||
import { PassTimeLabel } from "timi-web";
|
||||
@@ -166,6 +181,14 @@ function getStatusTheme(statusValue?: FundRecordStatus): "success" | "warning" |
|
||||
return statusValue === "VERIFIED" ? "success" : statusValue === "PENDING" ? "warning" : statusValue === "REJECTED" ? "danger" : "default";
|
||||
}
|
||||
|
||||
function getRecordStatusText(item: FundRecord): string {
|
||||
return item.revokeRecordId ? "已被冲正" : getFundStatusText(item.status);
|
||||
}
|
||||
|
||||
function getRecordStatusTheme(item: FundRecord): "success" | "warning" | "danger" | "default" {
|
||||
return item.revokeRecordId ? "danger" : getStatusTheme(item.status);
|
||||
}
|
||||
|
||||
async function toDetail(item: FundRecord): Promise<void> {
|
||||
if (item.id) {
|
||||
await router.push({
|
||||
@@ -256,7 +279,7 @@ onUnmounted(() => navBarStore.clearRight());
|
||||
gap: 0;
|
||||
color: var(--td-text-color-secondary);
|
||||
display: flex;
|
||||
font-size: .8125rem;
|
||||
font-size: .75rem;
|
||||
flex-wrap: wrap;
|
||||
line-height: 1.5;
|
||||
flex-direction: column;
|
||||
@@ -277,9 +300,26 @@ onUnmounted(() => navBarStore.clearRight());
|
||||
}
|
||||
}
|
||||
|
||||
.amount {
|
||||
.amounts {
|
||||
display: flex;
|
||||
font-size: .75rem;
|
||||
align-items: end;
|
||||
flex-direction: column;
|
||||
|
||||
.value {
|
||||
.label {
|
||||
color: var(--td-text-color-placeholder);
|
||||
}
|
||||
|
||||
.order-amount,
|
||||
.unpaid-amount,
|
||||
.record-amount {
|
||||
gap: .125rem;
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.record-amount .value {
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,33 +2,89 @@
|
||||
<div v-can="permissionCodes.fund.revoke" class="package-operation-edit">
|
||||
<t-cell-group v-if="record" title="原资金记录" theme="card" v-entrance-transition>
|
||||
<t-cell title="操作类型" :note="getFundOperationText(record.operationType)" />
|
||||
<t-cell title="金额" :note="formatPackageAmount(record.amount)" />
|
||||
<t-cell title="原状态" :note="getFundStatusText(record.status)" />
|
||||
<t-cell title="销售订单" :note="record.bizId || '未知'" />
|
||||
<t-cell title="金额">
|
||||
<template #note>
|
||||
<div class="red" v-text="formatPackageAmount(record.amount)" />
|
||||
</template>
|
||||
</t-cell>
|
||||
<t-cell title="原状态" :note="getRecordStatusText(record)" />
|
||||
<t-cell
|
||||
title="销售订单详情"
|
||||
:arrow="!!record.bizId && canReadSalesOrder"
|
||||
@click="toSalesOrder"
|
||||
/>
|
||||
</t-cell-group>
|
||||
<t-form v-if="record" class="form" label-align="left" label-width="6rem" v-entrance-transition="70">
|
||||
<t-form-item label="冲正原因" required-mark><t-textarea v-model.trim="reason" borderless autosize maxlength="500" placeholder="请输入冲正原因" /></t-form-item>
|
||||
<t-form-item label="冲正时间" arrow @click="pickerVisible = true"><span class="picker-text" v-text="occurredAtText || '使用当前时间'" /></t-form-item>
|
||||
<t-form-item label="备注"><t-textarea v-model.trim="remark" borderless autosize maxlength="500" placeholder="备注(可选)" /></t-form-item>
|
||||
</t-form>
|
||||
<t-form v-if="record" class="form" v-entrance-transition="70">
|
||||
<t-form-item label="冲正原因" required-mark>
|
||||
<t-textarea
|
||||
v-model="reason"
|
||||
borderless
|
||||
autosize
|
||||
maxlength="500"
|
||||
placeholder="请输入冲正原因"
|
||||
/>
|
||||
</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 label="备注">
|
||||
<t-textarea
|
||||
v-model="remark"
|
||||
borderless
|
||||
autosize
|
||||
maxlength="500"
|
||||
placeholder="备注(可选)"
|
||||
/>
|
||||
</t-form-item>
|
||||
<div class="photo-wrap">
|
||||
<photo-scroller
|
||||
v-if="record"
|
||||
:model-value="photoUrlList"
|
||||
v-model:photo-items="photoItems"
|
||||
class="photo-field"
|
||||
:upload="uploadPhotoList"
|
||||
:edit-mode="true"
|
||||
required
|
||||
title="冲正凭证照片"
|
||||
/>
|
||||
</div>
|
||||
</t-form>
|
||||
<t-empty v-if="!record" :description="isLoading ? '正在加载资金记录' : '资金记录不存在'" />
|
||||
<t-popup v-model="pickerVisible" placement="bottom">
|
||||
<t-date-time-picker v-model="draftTime" format="YYYY-MM-DD HH:mm" :mode="['date', 'minute']" title="冲正时间" @cancel="pickerVisible = false" @confirm="confirmTime" />
|
||||
<t-popup v-model="occurredAtPickerVisible" placement="bottom">
|
||||
<t-date-time-picker
|
||||
v-if="occurredAtPickerVisible"
|
||||
v-model="occurredAtDraftText"
|
||||
:start="minimumDateTimeText"
|
||||
:mode="['date', 'minute']"
|
||||
format="YYYY-MM-DD HH:mm"
|
||||
title="冲正时间"
|
||||
@cancel="occurredAtPickerVisible = false"
|
||||
@confirm="confirmOccurredAt"
|
||||
/>
|
||||
</t-popup>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Time } from "timi-web";
|
||||
import { useUserStore, type Attachment } from "timi-web-api";
|
||||
import SalesOrderAPI from "@/api/SalesOrderAPI";
|
||||
import PhotoScroller from "@/components/PhotoScroller.vue";
|
||||
@@ -52,25 +108,49 @@ const record = ref<FundRecord>();
|
||||
const reason = ref("");
|
||||
const remark = ref("");
|
||||
const occurredAtText = ref("");
|
||||
const draftTime = ref("");
|
||||
const pickerVisible = ref(false);
|
||||
const occurredAtDraftText = ref("");
|
||||
const occurredAtPickerVisible = ref(false);
|
||||
const photoItems = ref<PhotoItem[]>([]);
|
||||
const isLoading = ref(false);
|
||||
const isSaving = ref(false);
|
||||
const requestNo = ref("");
|
||||
const photoUrlList = computed(() => photoItems.value.map(item => item.url));
|
||||
const canReadSalesOrder = computed(() => userStore.hasPermissions(permissionCodes.salesOrder.read));
|
||||
|
||||
function confirmTime(value: string | number): void {
|
||||
occurredAtText.value = String(value);
|
||||
pickerVisible.value = false;
|
||||
const minimumDateTimeText = "1970-01-01 00:00";
|
||||
|
||||
function openOccurredAtPicker(): void {
|
||||
occurredAtDraftText.value = occurredAtText.value || Time.formatDateTime(new Date());
|
||||
occurredAtPickerVisible.value = true;
|
||||
}
|
||||
|
||||
function resolveTimestamp(value: string): number | undefined {
|
||||
function confirmOccurredAt(value?: string | number): void {
|
||||
occurredAtText.value = String(value || occurredAtDraftText.value);
|
||||
occurredAtPickerVisible.value = false;
|
||||
}
|
||||
|
||||
function resetOccurredAt(): void {
|
||||
occurredAtText.value = "";
|
||||
occurredAtDraftText.value = "";
|
||||
}
|
||||
|
||||
function buildOccurredAt(): number {
|
||||
return Time.toTimestamp(parseDateTime(occurredAtText.value));
|
||||
}
|
||||
|
||||
function getRecordStatusText(recordValue: FundRecord): string {
|
||||
return recordValue.revokeRecordId ? "已被冲正" : getFundStatusText(recordValue.status);
|
||||
}
|
||||
|
||||
function parseDateTime(value: string): Date {
|
||||
if (!value) {
|
||||
return undefined;
|
||||
return new Date();
|
||||
}
|
||||
const timestamp = new Date(value.replace(" ", "T")).getTime();
|
||||
return Number.isFinite(timestamp) ? timestamp : undefined;
|
||||
|
||||
const [dateText, timeText = "00:00"] = value.split(" ");
|
||||
const [year, month, day] = dateText.split("-").map(Number);
|
||||
const [hour, minute] = timeText.split(":").map(Number);
|
||||
return new Date(year, month - 1, day, hour || 0, minute || 0, 0, 0);
|
||||
}
|
||||
|
||||
function buildAttachments(): Partial<Attachment>[] {
|
||||
@@ -92,12 +172,35 @@ async function loadData(): Promise<void> {
|
||||
}
|
||||
}
|
||||
|
||||
// ---------- 订单详情 ----------
|
||||
|
||||
async function toSalesOrder(): Promise<void> {
|
||||
if (!record.value?.bizId || !canReadSalesOrder.value) {
|
||||
return;
|
||||
}
|
||||
await router.push({
|
||||
path: `/management/sales-orders/${record.value.bizId}`,
|
||||
query: { transitionDepth: String(getCurrentRouteDepth() + 1) }
|
||||
});
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
async function save(): Promise<void> {
|
||||
if (isSaving.value) {
|
||||
return;
|
||||
}
|
||||
if (!record.value?.id || record.value.status !== "VERIFIED" || record.value.operationType === "REVOKE") {
|
||||
Message.warning("只有已核销且非冲正的资金记录可以冲正");
|
||||
if (!record.value?.id || record.value.revokeRecordId || record.value.status !== "VERIFIED" || record.value.operationType === "REVOKE") {
|
||||
Message.warning("只有已核销、未被冲正且不是冲正记录的资金记录可以冲正");
|
||||
return;
|
||||
}
|
||||
if (!reason.value.trim()) {
|
||||
@@ -116,7 +219,7 @@ async function save(): Promise<void> {
|
||||
const result = await SalesOrderAPI.fundRecordRevoke({
|
||||
requestNo: requestNo.value,
|
||||
fundRecordId: record.value.id,
|
||||
occurredAt: resolveTimestamp(occurredAtText.value),
|
||||
occurredAt: buildOccurredAt(),
|
||||
reason: reason.value.trim(),
|
||||
attachmentList: buildAttachments(),
|
||||
remark: remark.value.trim() || null
|
||||
@@ -136,7 +239,7 @@ async function save(): Promise<void> {
|
||||
|
||||
onMounted(async () => {
|
||||
if (userStore.hasPermissions(permissionCodes.fund.revoke)) {
|
||||
navBarStore.setRightText("提交冲正", "var(--td-danger-color)", save);
|
||||
navBarStore.setRightText("提交", "var(--td-error-color)", save);
|
||||
}
|
||||
await loadData();
|
||||
});
|
||||
@@ -151,22 +254,15 @@ onUnmounted(() => navBarStore.clearRight());
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.form {
|
||||
margin: 0 1rem;
|
||||
padding: 0 .5rem;
|
||||
background: var(--td-bg-color-container);
|
||||
box-shadow: var(--td-shadow-1);
|
||||
border-radius: 1rem;
|
||||
overflow: hidden;
|
||||
.time-actions {
|
||||
gap: .5rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.picker-text {
|
||||
width: 100%;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.photo-field {
|
||||
padding: 0 1rem;
|
||||
.photo-wrap {
|
||||
padding: 1rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -40,7 +40,15 @@
|
||||
/>
|
||||
</div>
|
||||
</t-form-item>
|
||||
<t-form-item label="备注"><t-textarea v-model.trim="remark" borderless autosize maxlength="500" /></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">
|
||||
<photo-scroller
|
||||
v-if="order"
|
||||
|
||||
@@ -141,7 +141,10 @@ export type FundRecord = {
|
||||
operationType?: FundOperationType;
|
||||
status?: FundRecordStatus;
|
||||
amount?: number;
|
||||
salesOrderAmount?: number;
|
||||
salesOrderUnpaidAmountSnapshot?: number;
|
||||
sourceRecordId?: string;
|
||||
revokeRecordId?: string;
|
||||
requestNo?: string;
|
||||
operatorUserId?: string;
|
||||
verifierUserId?: string;
|
||||
|
||||
Reference in New Issue
Block a user