feat: 修复了公账支付的东西
Some checks are pending
CI / Test (ubuntu-latest) (push) Waiting to run
CI / Test (windows-latest) (push) Waiting to run
CI / Lint (ubuntu-latest) (push) Waiting to run
CI / Lint (windows-latest) (push) Waiting to run
CI / Check (ubuntu-latest) (push) Waiting to run
CI / Check (windows-latest) (push) Waiting to run
CI / CI OK (push) Blocked by required conditions
CodeQL / Analyze (javascript-typescript) (push) Waiting to run
Deploy Website on push / Deploy Push Playground Ftp (push) Waiting to run
Deploy Website on push / Deploy Push Docs Ftp (push) Waiting to run
Deploy Website on push / Deploy Push Antd Ftp (push) Waiting to run
Deploy Website on push / Deploy Push Element Ftp (push) Waiting to run
Deploy Website on push / Deploy Push Naive Ftp (push) Waiting to run
Deploy Website on push / Rerun on failure (push) Blocked by required conditions
Release Drafter / update_release_draft (push) Waiting to run
Some checks are pending
CI / Test (ubuntu-latest) (push) Waiting to run
CI / Test (windows-latest) (push) Waiting to run
CI / Lint (ubuntu-latest) (push) Waiting to run
CI / Lint (windows-latest) (push) Waiting to run
CI / Check (ubuntu-latest) (push) Waiting to run
CI / Check (windows-latest) (push) Waiting to run
CI / CI OK (push) Blocked by required conditions
CodeQL / Analyze (javascript-typescript) (push) Waiting to run
Deploy Website on push / Deploy Push Playground Ftp (push) Waiting to run
Deploy Website on push / Deploy Push Docs Ftp (push) Waiting to run
Deploy Website on push / Deploy Push Antd Ftp (push) Waiting to run
Deploy Website on push / Deploy Push Element Ftp (push) Waiting to run
Deploy Website on push / Deploy Push Naive Ftp (push) Waiting to run
Deploy Website on push / Rerun on failure (push) Blocked by required conditions
Release Drafter / update_release_draft (push) Waiting to run
This commit is contained in:
@@ -7,37 +7,43 @@ import { onMounted, ref } from 'vue';
|
||||
import { Button } from 'ant-design-vue';
|
||||
|
||||
import DailyUploadForm from '#/views/finance/public-account-pay/components/daily-upload-form.vue';
|
||||
import { buildDailyUploadFormData } from '#/views/finance/public-account-pay/utils/daily-upload';
|
||||
|
||||
const props = defineProps<{
|
||||
dailyId: number;
|
||||
storeName?: string;
|
||||
platformAmount?: string;
|
||||
billDateTxt?: string;
|
||||
dailyId: number;
|
||||
platformAmount?: string;
|
||||
storeName?: string;
|
||||
unlockMode?: string;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
back: [];
|
||||
waiting: [payload?: {
|
||||
bill_status?: number;
|
||||
contact_phones?: { name?: string; phone: string }[];
|
||||
}];
|
||||
waiting: [
|
||||
payload?: {
|
||||
bill_status?: number;
|
||||
contact_phones?: { name?: string; phone: string }[];
|
||||
},
|
||||
];
|
||||
}>();
|
||||
|
||||
const formRef = ref<InstanceType<typeof DailyUploadForm>>();
|
||||
const submitting = ref(false);
|
||||
|
||||
const unlockHint = props.unlockMode === 'voucher'
|
||||
? '提交成功后立刻解锁,接诊和开方可继续。'
|
||||
: '提交后仍锁定,需平台管理员确认到账后才会解锁。';
|
||||
const unlockHint =
|
||||
props.unlockMode === 'voucher'
|
||||
? '提交成功后立刻解锁,接诊和开方可继续。'
|
||||
: '提交后仍锁定,需平台管理员确认到账后才会解锁。';
|
||||
|
||||
onMounted(() => {
|
||||
formRef.value?.load({
|
||||
id: props.dailyId,
|
||||
store_name: props.storeName,
|
||||
platform_amount: props.platformAmount,
|
||||
bill_date_txt: props.billDateTxt,
|
||||
});
|
||||
formRef.value?.load(
|
||||
buildDailyUploadFormData({
|
||||
id: props.dailyId,
|
||||
store_name: props.storeName,
|
||||
platform_amount: props.platformAmount,
|
||||
bill_date_txt: props.billDateTxt,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
/** 可无附件直接声明已支付;若已选文件一并提交 */
|
||||
@@ -51,9 +57,9 @@ async function onDeclarePaid() {
|
||||
}
|
||||
|
||||
function onFormSuccess(payload: {
|
||||
unlocked: boolean;
|
||||
bill_status?: number;
|
||||
contact_phones?: { name?: string; phone: string }[];
|
||||
unlocked: boolean;
|
||||
}) {
|
||||
if (!payload.unlocked) {
|
||||
emit('waiting', payload);
|
||||
@@ -73,7 +79,9 @@ function onFormSuccess(payload: {
|
||||
@success="onFormSuccess"
|
||||
/>
|
||||
<div class="pap-lock-pay-actions">
|
||||
<Button size="large" class="pap-lock-btn is-ghost" @click="emit('back')">返回说明</Button>
|
||||
<Button size="large" class="pap-lock-btn is-ghost" @click="emit('back')">
|
||||
返回说明
|
||||
</Button>
|
||||
<Button
|
||||
type="primary"
|
||||
size="large"
|
||||
@@ -96,19 +104,21 @@ function onFormSuccess(payload: {
|
||||
max-width: 100%;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.pap-lock-badge {
|
||||
display: inline-block;
|
||||
padding: 3px 10px;
|
||||
margin-bottom: var(--pap-gap-sm, 14px);
|
||||
font-size: var(--pap-fs-badge, 12px);
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.08em;
|
||||
color: hsl(var(--pap-accent));
|
||||
letter-spacing: 0.08em;
|
||||
background: hsl(var(--pap-accent) / 12%);
|
||||
border: 1px solid hsl(var(--pap-accent) / 40%);
|
||||
border-radius: 999px;
|
||||
background: hsl(var(--pap-accent) / 12%);
|
||||
box-shadow: 0 0 6px hsl(var(--pap-accent) / 18%);
|
||||
}
|
||||
|
||||
.pap-lock-title {
|
||||
margin: 0 0 var(--pap-gap-sm, 12px);
|
||||
font-size: var(--pap-fs-title, 28px);
|
||||
@@ -116,27 +126,32 @@ function onFormSuccess(payload: {
|
||||
line-height: 1.25;
|
||||
color: hsl(var(--foreground));
|
||||
}
|
||||
|
||||
.pap-lock-desc {
|
||||
margin: 0 0 var(--pap-gap-md, 20px);
|
||||
font-size: var(--pap-fs-desc, 15px);
|
||||
line-height: 1.55;
|
||||
color: hsl(var(--muted-foreground));
|
||||
}
|
||||
|
||||
.pap-lock-pay-actions {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: var(--pap-gap-sm, 12px);
|
||||
margin-top: var(--pap-gap-md, 20px);
|
||||
}
|
||||
|
||||
.pap-lock-btn {
|
||||
width: 100%;
|
||||
height: var(--pap-btn-h, 48px);
|
||||
font-size: var(--pap-fs-btn, 16px);
|
||||
}
|
||||
|
||||
.pap-lock-btn.is-ghost {
|
||||
color: hsl(var(--foreground));
|
||||
border-color: hsl(var(--pap-accent) / 40%);
|
||||
}
|
||||
|
||||
.pap-lock-pay-tip {
|
||||
margin: var(--pap-gap-sm, 12px) 0 0;
|
||||
font-size: var(--pap-fs-label, 12px);
|
||||
@@ -144,6 +159,7 @@ function onFormSuccess(payload: {
|
||||
color: hsl(var(--muted-foreground));
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@media (max-width: 560px) {
|
||||
.pap-lock-pay-actions {
|
||||
grid-template-columns: 1fr;
|
||||
|
||||
@@ -7,15 +7,15 @@ import { computed, ref, watch } from 'vue';
|
||||
|
||||
import { useUserStore } from '@vben/stores';
|
||||
|
||||
import { Button, Modal, message } from 'ant-design-vue';
|
||||
import { Button, message, Modal } from 'ant-design-vue';
|
||||
|
||||
import { useAuthStore } from '#/store';
|
||||
import {
|
||||
PUBLIC_ACCOUNT_LOCK_PROMPT_Z_INDEX,
|
||||
clearPublicAccountLock,
|
||||
closePublicAccountLockHandle,
|
||||
getPublicAccountLockState,
|
||||
normalizeLockAlertColor,
|
||||
PUBLIC_ACCOUNT_LOCK_PROMPT_Z_INDEX,
|
||||
} from '#/utils/publicAccountLock';
|
||||
import {
|
||||
generatePublicAccountDailyBill,
|
||||
@@ -23,15 +23,15 @@ import {
|
||||
getPublicAccountLockStatus,
|
||||
} from '#/views/finance/public-account-pay/api';
|
||||
|
||||
import LockOrdersPanel from './LockOrdersPanel.vue';
|
||||
import LockPayPanel from './LockPayPanel.vue';
|
||||
import LockWaitPanel from './LockWaitPanel.vue';
|
||||
import LockOrdersPanel from './LockOrdersPanel.vue';
|
||||
import LockWarningCanvas from './LockWarningCanvas.vue';
|
||||
|
||||
const userStore = useUserStore();
|
||||
const authStore = useAuthStore();
|
||||
const lockState = getPublicAccountLockState();
|
||||
const panelMode = ref<'hint' | 'pay' | 'wait' | 'orders'>('hint');
|
||||
const panelMode = ref<'hint' | 'orders' | 'pay' | 'wait'>('hint');
|
||||
const payDailyId = ref(0);
|
||||
const openingPay = ref(false);
|
||||
const loggingOut = ref(false);
|
||||
@@ -205,9 +205,9 @@ function backToHint() {
|
||||
|
||||
/** 只有这次上传成功且未解锁,才切到等待审核 */
|
||||
function onWaiting(payload?: {
|
||||
status?: number;
|
||||
bill_status?: number;
|
||||
contact_phones?: { name?: string; phone: string }[];
|
||||
status?: number;
|
||||
}) {
|
||||
if (payload?.contact_phones?.length) {
|
||||
lockState.payload.contact_phones = payload.contact_phones;
|
||||
@@ -290,7 +290,7 @@ watch(
|
||||
<div v-if="overlayOpen" class="pap-lock-mask">
|
||||
<div
|
||||
class="pap-lock-panel"
|
||||
:class="['is-' + alertColor, { 'is-sub': panelMode !== 'hint' }]"
|
||||
:class="[`is-${ alertColor}`, { 'is-sub': panelMode !== 'hint' }]"
|
||||
role="alertdialog"
|
||||
aria-modal="true"
|
||||
>
|
||||
@@ -304,6 +304,7 @@ watch(
|
||||
<LockPayPanel
|
||||
v-if="panelMode === 'pay'"
|
||||
:daily-id="payDailyId"
|
||||
:store-name="String(lockState.payload.store_name || '')"
|
||||
:platform-amount="amountTxt"
|
||||
:bill-date-txt="billDateRangeTxt"
|
||||
:unlock-mode="unlockMode"
|
||||
@@ -337,9 +338,7 @@ watch(
|
||||
<template v-if="softHandle">
|
||||
请尽快处理。完成后可返回继续工作。
|
||||
</template>
|
||||
<template v-else>
|
||||
接诊、开方已暂停,本提示不可关闭。
|
||||
</template>
|
||||
<template v-else> 接诊、开方已暂停,本提示不可关闭。 </template>
|
||||
<template v-if="mergeHint">
|
||||
{{ mergeHint }}
|
||||
</template>
|
||||
@@ -366,9 +365,13 @@ watch(
|
||||
后未处理即锁定;更早的账单全天锁定。
|
||||
</li>
|
||||
<li>诊所管理员在本提示中上传付款凭证(转账截图)。</li>
|
||||
<li v-if="unlockByVoucher">提交成功后立刻解锁,接诊和开方可继续。</li>
|
||||
<li v-if="unlockByVoucher">
|
||||
提交成功后立刻解锁,接诊和开方可继续。
|
||||
</li>
|
||||
<li v-else>提交后仍锁定,等平台管理员确认到账后自动解锁。</li>
|
||||
<li v-if="!softHandle">未达解锁条件前,无法关闭本提示,也无法继续业务。</li>
|
||||
<li v-if="!softHandle">
|
||||
未达解锁条件前,无法关闭本提示,也无法继续业务。
|
||||
</li>
|
||||
</ol>
|
||||
</div>
|
||||
</template>
|
||||
@@ -429,10 +432,10 @@ watch(
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 2100;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: max(10px, env(safe-area-inset-top, 0px))
|
||||
@@ -443,18 +446,10 @@ watch(
|
||||
background: hsl(var(--background) / 62%);
|
||||
backdrop-filter: blur(6px);
|
||||
}
|
||||
|
||||
.pap-lock-panel {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
box-sizing: border-box;
|
||||
width: min(820px, 100%);
|
||||
max-width: 100%;
|
||||
/* 相对遮罩内容盒,保证整框含警戒条都在视口内 */
|
||||
max-height: 100%;
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
--pap-accent: var(--warning);
|
||||
|
||||
/* 顶留白跟警示三角对齐:矮屏大幅压缩 */
|
||||
--pap-pad-top-hint: clamp(52px, 9dvh + 20px, 150px);
|
||||
--pap-pad-x: clamp(14px, 3vw, 40px);
|
||||
@@ -476,84 +471,106 @@ watch(
|
||||
--pap-btn-h: clamp(36px, 4.2dvh, 44px);
|
||||
--pap-stat-cols: 1fr 1fr;
|
||||
--pap-content-w: min(480px, 100%);
|
||||
|
||||
/* 给 canvas 上下警戒条留出露边,避免被底栏背景盖住 */
|
||||
--pap-tape-h: 16px;
|
||||
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: min(820px, 100%);
|
||||
max-width: 100%;
|
||||
min-height: 0;
|
||||
|
||||
/* 相对遮罩内容盒,保证整框含警戒条都在视口内 */
|
||||
max-height: 100%;
|
||||
overflow: hidden;
|
||||
background: hsl(var(--card, var(--background)));
|
||||
border: 1px solid hsl(var(--pap-accent) / 38%);
|
||||
border-radius: clamp(10px, 1.2vw, 16px);
|
||||
box-shadow: 0 0 6px hsl(var(--pap-accent) / 18%);
|
||||
}
|
||||
|
||||
.pap-lock-panel.is-blue {
|
||||
--pap-accent: 217 91% 53%;
|
||||
}
|
||||
|
||||
.pap-lock-panel.is-yellow {
|
||||
--pap-accent: var(--warning);
|
||||
}
|
||||
|
||||
.pap-lock-panel.is-red {
|
||||
--pap-accent: 0 72% 51%;
|
||||
}
|
||||
|
||||
.pap-lock-panel.is-sub {
|
||||
--pap-pad-top-hint: clamp(14px, 2dvh, 28px);
|
||||
}
|
||||
|
||||
/* shell:上滚下钉;上下内边距露出警戒条动画 */
|
||||
.pap-lock-shell {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex: 1 1 auto;
|
||||
flex-direction: column;
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
min-height: 0;
|
||||
max-height: 100%;
|
||||
padding-top: var(--pap-tape-h);
|
||||
padding-bottom: var(--pap-tape-h);
|
||||
}
|
||||
|
||||
.pap-lock-scroll {
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex: 1 1 auto;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
min-height: 0;
|
||||
padding: var(--pap-pad-top-hint) var(--pap-pad-x) var(--pap-gap-sm);
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
overflow: hidden auto;
|
||||
overscroll-behavior: contain;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
text-align: center;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
|
||||
.pap-lock-foot {
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-shrink: 0;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
padding: var(--pap-gap-sm) var(--pap-pad-x) var(--pap-pad-y);
|
||||
border-top: 1px solid hsl(var(--pap-accent) / 22%);
|
||||
|
||||
/* 半透明,滚动区边缘仍能透出粒子;底边警戒条靠 shell padding 露白 */
|
||||
background: hsl(var(--card, var(--background)) / 88%);
|
||||
border-top: 1px solid hsl(var(--pap-accent) / 22%);
|
||||
backdrop-filter: blur(4px);
|
||||
}
|
||||
|
||||
.pap-lock-badge {
|
||||
flex-shrink: 0;
|
||||
padding: 3px 10px;
|
||||
margin-bottom: var(--pap-gap-sm);
|
||||
font-size: var(--pap-fs-badge);
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.08em;
|
||||
color: hsl(var(--pap-accent));
|
||||
letter-spacing: 0.08em;
|
||||
background: hsl(var(--pap-accent) / 12%);
|
||||
border: 1px solid hsl(var(--pap-accent) / 40%);
|
||||
border-radius: 999px;
|
||||
background: hsl(var(--pap-accent) / 12%);
|
||||
box-shadow: 0 0 6px hsl(var(--pap-accent) / 18%);
|
||||
}
|
||||
|
||||
.pap-lock-hello {
|
||||
max-width: min(560px, 100%);
|
||||
margin: 0 0 var(--pap-gap-xs);
|
||||
padding: 0 4px;
|
||||
margin: 0 0 var(--pap-gap-xs);
|
||||
font-size: var(--pap-fs-hello);
|
||||
font-weight: 600;
|
||||
line-height: 1.35;
|
||||
@@ -561,6 +578,7 @@ watch(
|
||||
word-break: break-word;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.pap-lock-title {
|
||||
margin: 0 0 var(--pap-gap-xs);
|
||||
font-size: var(--pap-fs-title);
|
||||
@@ -568,6 +586,7 @@ watch(
|
||||
line-height: 1.25;
|
||||
color: hsl(var(--foreground));
|
||||
}
|
||||
|
||||
.pap-lock-reason {
|
||||
max-width: min(520px, 100%);
|
||||
margin: 0 0 var(--pap-gap-xs);
|
||||
@@ -575,6 +594,7 @@ watch(
|
||||
line-height: 1.4;
|
||||
color: hsl(var(--muted-foreground));
|
||||
}
|
||||
|
||||
.pap-lock-desc {
|
||||
max-width: min(520px, 100%);
|
||||
margin: 0 0 var(--pap-gap-md);
|
||||
@@ -582,6 +602,7 @@ watch(
|
||||
line-height: 1.45;
|
||||
color: hsl(var(--muted-foreground));
|
||||
}
|
||||
|
||||
.pap-lock-stat {
|
||||
display: grid;
|
||||
grid-template-columns: var(--pap-stat-cols);
|
||||
@@ -589,31 +610,38 @@ watch(
|
||||
width: var(--pap-content-w);
|
||||
margin-bottom: var(--pap-gap-md);
|
||||
}
|
||||
|
||||
.pap-lock-stat-item {
|
||||
min-width: 0;
|
||||
padding: var(--pap-block-pad) calc(var(--pap-block-pad) + 2px);
|
||||
text-align: left;
|
||||
background: hsl(var(--card, var(--background)));
|
||||
border: 1px solid hsl(var(--pap-accent) / 30%);
|
||||
border-radius: 10px;
|
||||
background: hsl(var(--card, var(--background)));
|
||||
box-shadow: 0 0 6px hsl(var(--pap-accent) / 18%);
|
||||
}
|
||||
|
||||
.pap-lock-stat-item.is-clickable {
|
||||
cursor: pointer;
|
||||
color: inherit;
|
||||
font: inherit;
|
||||
transition: border-color 0.15s ease, box-shadow 0.15s ease;
|
||||
color: inherit;
|
||||
cursor: pointer;
|
||||
transition:
|
||||
border-color 0.15s ease,
|
||||
box-shadow 0.15s ease;
|
||||
}
|
||||
|
||||
.pap-lock-stat-item.is-clickable:hover {
|
||||
border-color: hsl(var(--pap-accent) / 55%);
|
||||
box-shadow: 0 0 10px hsl(var(--pap-accent) / 28%);
|
||||
}
|
||||
|
||||
.pap-lock-stat-label {
|
||||
display: block;
|
||||
margin-bottom: var(--pap-gap-xs);
|
||||
font-size: var(--pap-fs-label);
|
||||
color: hsl(var(--muted-foreground));
|
||||
}
|
||||
|
||||
.pap-lock-stat-item strong {
|
||||
display: block;
|
||||
font-size: var(--pap-fs-stat);
|
||||
@@ -623,47 +651,55 @@ watch(
|
||||
word-break: break-word;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.pap-lock-stat-item strong.is-amount {
|
||||
color: hsl(var(--pap-accent));
|
||||
}
|
||||
|
||||
.pap-lock-flow {
|
||||
width: var(--pap-content-w);
|
||||
padding: var(--pap-block-pad) calc(var(--pap-block-pad) + 2px);
|
||||
margin-bottom: 0;
|
||||
text-align: left;
|
||||
background: hsl(var(--card, var(--background)));
|
||||
border: 1px solid hsl(var(--pap-accent) / 30%);
|
||||
border-radius: 10px;
|
||||
background: hsl(var(--card, var(--background)));
|
||||
box-shadow: 0 0 6px hsl(var(--pap-accent) / 18%);
|
||||
}
|
||||
|
||||
.pap-lock-flow-title {
|
||||
margin-bottom: var(--pap-gap-xs);
|
||||
font-size: var(--pap-fs-flow);
|
||||
font-weight: 600;
|
||||
color: hsl(var(--pap-accent));
|
||||
}
|
||||
|
||||
.pap-lock-flow-list {
|
||||
margin: 0;
|
||||
padding-left: 16px;
|
||||
margin: 0;
|
||||
font-size: var(--pap-fs-flow);
|
||||
line-height: 1.45;
|
||||
color: hsl(var(--muted-foreground));
|
||||
}
|
||||
|
||||
.pap-lock-actions {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
gap: var(--pap-gap-sm);
|
||||
width: var(--pap-content-w);
|
||||
}
|
||||
|
||||
.pap-lock-btn {
|
||||
width: 100%;
|
||||
height: var(--pap-btn-h);
|
||||
font-size: var(--pap-fs-btn);
|
||||
}
|
||||
|
||||
.pap-lock-btn.is-ghost {
|
||||
color: hsl(var(--foreground));
|
||||
border-color: hsl(var(--pap-accent) / 40%);
|
||||
}
|
||||
|
||||
.pap-lock-hint {
|
||||
max-width: var(--pap-content-w);
|
||||
margin: 0;
|
||||
@@ -671,9 +707,10 @@ watch(
|
||||
line-height: 1.45;
|
||||
color: hsl(var(--pap-accent));
|
||||
}
|
||||
|
||||
.pap-lock-logout {
|
||||
margin-top: var(--pap-gap-sm);
|
||||
padding: 0;
|
||||
margin-top: var(--pap-gap-sm);
|
||||
font-size: var(--pap-fs-desc);
|
||||
line-height: 1.4;
|
||||
color: hsl(var(--muted-foreground));
|
||||
@@ -682,23 +719,28 @@ watch(
|
||||
background: transparent;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.pap-lock-logout:disabled {
|
||||
opacity: 0.6;
|
||||
cursor: not-allowed;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
@media (max-height: 720px) {
|
||||
.pap-lock-mask {
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.pap-lock-panel {
|
||||
/* 矮屏仍双列,省纵向高度;过窄才单列 */
|
||||
--pap-pad-top-hint: clamp(44px, 7dvh + 16px, 96px);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 560px) {
|
||||
.pap-lock-mask {
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.pap-lock-panel {
|
||||
--pap-stat-cols: 1fr;
|
||||
--pap-pad-x: 12px;
|
||||
@@ -712,6 +754,7 @@ body.pap-lock-open .ant-message,
|
||||
body.pap-lock-open .ant-notification {
|
||||
z-index: 5000 !important;
|
||||
}
|
||||
|
||||
/* 退出确认等 Modal 也要压过锁层 */
|
||||
body.pap-lock-open .ant-modal-wrap,
|
||||
body.pap-lock-open .ant-modal-mask {
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
<script lang="ts" setup>
|
||||
import type { DailyUploadRowLike } from '../utils/daily-upload';
|
||||
|
||||
/**
|
||||
* 日汇总上传凭证表单:财务弹窗和锁店二级页共用
|
||||
* 锁店页可无附件直接「我已支付」;财务弹窗默认仍建议传凭证
|
||||
@@ -10,32 +12,43 @@ import { Image, message } from 'ant-design-vue';
|
||||
import UploadDraggerPaste from '#/components/form/components/upload-dragger-paste.vue';
|
||||
import { clearPublicAccountLock } from '#/utils/publicAccountLock';
|
||||
|
||||
import { getPublicAccountDailyBillInfo, uploadPublicAccountDailyBill } from '../api';
|
||||
import {
|
||||
getPublicAccountDailyBillInfo,
|
||||
uploadPublicAccountDailyBill,
|
||||
} from '../api';
|
||||
import {
|
||||
buildDailyUploadFormData,
|
||||
formatDailyPlatformAmount,
|
||||
normalizeDailyVoucherUrls,
|
||||
resolveDailyBillDateTxt,
|
||||
resolveDailyPlatformAmount,
|
||||
resolveDailyStoreName,
|
||||
resolveDailyUploadId,
|
||||
} from '../utils/daily-upload';
|
||||
|
||||
export interface DailyUploadFormData {
|
||||
export interface DailyUploadFormData extends DailyUploadRowLike {
|
||||
id: number;
|
||||
store_name?: string;
|
||||
platform_amount?: string;
|
||||
bill_date_txt?: string;
|
||||
gridApi?: any;
|
||||
}
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
/** 预览层必须高于锁店遮罩 2100,否则图会沉到底下 */
|
||||
previewZIndex?: number;
|
||||
/** 锁店页:允许不传文件直接声明已支付 */
|
||||
allowEmptyVoucher?: boolean;
|
||||
/** 预览层必须高于锁店遮罩 2100,否则图会沉到底下 */
|
||||
previewZIndex?: number;
|
||||
}>(),
|
||||
{ previewZIndex: 2500, allowEmptyVoucher: false },
|
||||
);
|
||||
|
||||
const emit = defineEmits<{
|
||||
success: [payload: {
|
||||
unlocked: boolean;
|
||||
bill_status?: number;
|
||||
contact_phones?: { name?: string; phone: string }[];
|
||||
}];
|
||||
success: [
|
||||
payload: {
|
||||
bill_status?: number;
|
||||
contact_phones?: { name?: string; phone: string }[];
|
||||
unlocked: boolean;
|
||||
},
|
||||
];
|
||||
}>();
|
||||
|
||||
const dailyId = ref(0);
|
||||
@@ -56,37 +69,45 @@ const voucherHint = computed(() =>
|
||||
: '支持转账截图(JPG/PNG/WEBP)与 PDF 回单,请至少上传 1 个文件。',
|
||||
);
|
||||
|
||||
const displayStoreName = computed(() => storeName.value || '本店');
|
||||
const displayBillDateTxt = computed(() => billDateTxt.value || '—');
|
||||
const displayPlatformAmount = computed(() =>
|
||||
formatDailyPlatformAmount(platformAmount.value),
|
||||
);
|
||||
|
||||
function isImageUrl(url: string) {
|
||||
return /\.(jpg|jpeg|png|gif|webp|bmp)(\?|$)/i.test(url || '');
|
||||
}
|
||||
|
||||
/** 打开时灌入日汇总:锁店传入的周期金额/区间优先,详情只补凭证与店名 */
|
||||
/** 打开时灌入日汇总:列表/锁店传入的周期金额/区间优先,详情只补凭证与店名 */
|
||||
async function load(data?: DailyUploadFormData) {
|
||||
dailyId.value = Number(data?.id || 0);
|
||||
storeName.value = data?.store_name || '';
|
||||
// 锁店页已算好周期合计,勿被单日详情盖掉
|
||||
const lockAmount = String(data?.platform_amount || '').trim();
|
||||
const lockDate = String(data?.bill_date_txt || '').trim();
|
||||
platformAmount.value = lockAmount || '0.00';
|
||||
billDateTxt.value = lockDate;
|
||||
const normalized = buildDailyUploadFormData(data || {});
|
||||
dailyId.value = resolveDailyUploadId(normalized);
|
||||
storeName.value = resolveDailyStoreName(normalized);
|
||||
billDateTxt.value = resolveDailyBillDateTxt(normalized);
|
||||
platformAmount.value = resolveDailyPlatformAmount(normalized);
|
||||
gridApi.value = data?.gridApi;
|
||||
voucherUrls.value = [];
|
||||
payRemark.value = '';
|
||||
existVouchers.value = [];
|
||||
|
||||
const presetAmount = resolveDailyPlatformAmount(normalized);
|
||||
const presetDate = resolveDailyBillDateTxt(normalized);
|
||||
|
||||
if (dailyId.value < 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
loading.value = true;
|
||||
try {
|
||||
const detail = await getPublicAccountDailyBillInfo(dailyId.value);
|
||||
existVouchers.value = detail?.vouchers || [];
|
||||
storeName.value = detail?.store_name || storeName.value;
|
||||
// 仅财务弹窗未带金额/日期时,才回落单日字段
|
||||
if (!lockAmount || lockAmount === '0.00') {
|
||||
platformAmount.value = String(detail?.platform_amount || platformAmount.value);
|
||||
existVouchers.value = detail?.vouchers || detail?.payment_vouchers || [];
|
||||
storeName.value = resolveDailyStoreName(detail) || storeName.value;
|
||||
if (!presetAmount || presetAmount === '0.00') {
|
||||
platformAmount.value = resolveDailyPlatformAmount(detail);
|
||||
}
|
||||
if (!lockDate) {
|
||||
billDateTxt.value = detail?.bill_date_txt || billDateTxt.value;
|
||||
if (!presetDate) {
|
||||
billDateTxt.value = resolveDailyBillDateTxt(detail);
|
||||
}
|
||||
} finally {
|
||||
loading.value = false;
|
||||
@@ -97,8 +118,12 @@ async function load(data?: DailyUploadFormData) {
|
||||
* 提交:allowEmpty=true 时可无附件(我已支付)
|
||||
*/
|
||||
async function submit(options?: { allowEmpty?: boolean }): Promise<boolean> {
|
||||
if (dailyId.value < 1) {
|
||||
message.warning('无法识别日汇总,请刷新后重试');
|
||||
return false;
|
||||
}
|
||||
const allowEmpty = Boolean(options?.allowEmpty || props.allowEmptyVoucher);
|
||||
if (!voucherUrls.value.length && !allowEmpty) {
|
||||
if (voucherUrls.value.length === 0 && !allowEmpty) {
|
||||
message.warning('请至少上传 1 张付款凭证(截图或 PDF)');
|
||||
return false;
|
||||
}
|
||||
@@ -106,11 +131,11 @@ async function submit(options?: { allowEmpty?: boolean }): Promise<boolean> {
|
||||
try {
|
||||
const res = await uploadPublicAccountDailyBill({
|
||||
id: dailyId.value,
|
||||
voucher_urls: voucherUrls.value,
|
||||
pay_remark: payRemark.value,
|
||||
declare_paid: allowEmpty && !voucherUrls.value.length ? 1 : 0,
|
||||
voucher_urls: normalizeDailyVoucherUrls(voucherUrls.value),
|
||||
pay_remark: payRemark.value.trim(),
|
||||
declare_paid: allowEmpty && voucherUrls.value.length === 0 ? 1 : 0,
|
||||
});
|
||||
message.success(voucherUrls.value.length ? '已上传' : '已声明支付');
|
||||
message.success(voucherUrls.value.length > 0 ? '已上传' : '已声明支付');
|
||||
if (res?.unlocked) {
|
||||
clearPublicAccountLock();
|
||||
}
|
||||
@@ -132,11 +157,16 @@ defineExpose({ load, submit, submitting, loading, voucherUrls });
|
||||
|
||||
<template>
|
||||
<div class="daily-upload-form">
|
||||
<div v-if="loading" class="mb-3 text-sm text-[hsl(var(--muted-foreground))]">
|
||||
<div
|
||||
v-if="loading"
|
||||
class="mb-3 text-sm text-[hsl(var(--muted-foreground))]"
|
||||
>
|
||||
正在加载日汇总...
|
||||
</div>
|
||||
<div class="mb-3 text-sm text-[hsl(var(--muted-foreground))]">
|
||||
{{ storeName || '本店' }} · {{ billDateTxt || '—' }} · 应付款 ¥{{ platformAmount }}
|
||||
{{ displayStoreName }} · {{ displayBillDateTxt }} · 应付款 ¥{{
|
||||
displayPlatformAmount
|
||||
}}
|
||||
</div>
|
||||
<div v-if="existVouchers.length" class="mb-3">
|
||||
<div class="mb-1 text-sm text-[hsl(var(--foreground))]">已传凭证</div>
|
||||
@@ -177,6 +207,6 @@ defineExpose({ load, submit, submitting, loading, voucherUrls });
|
||||
class="mt-3 w-full rounded border border-[hsl(var(--border))] bg-[hsl(var(--background))] p-2 text-sm text-[hsl(var(--foreground))]"
|
||||
rows="2"
|
||||
placeholder="备注(选填)"
|
||||
/>
|
||||
></textarea>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -6,6 +6,7 @@ import { nextTick, ref } from 'vue';
|
||||
|
||||
import { useVbenModal } from '@vben/common-ui';
|
||||
|
||||
import { buildDailyUploadFormData } from '../utils/daily-upload';
|
||||
import DailyUploadForm from './daily-upload-form.vue';
|
||||
|
||||
const formRef = ref<InstanceType<typeof DailyUploadForm>>();
|
||||
@@ -33,13 +34,7 @@ const [Modal, modalApi] = useVbenModal({
|
||||
if (!isOpen) return;
|
||||
await nextTick();
|
||||
await formRef.value?.load(
|
||||
modalApi.getData<{
|
||||
id: number;
|
||||
store_name?: string;
|
||||
platform_amount?: string;
|
||||
bill_date_txt?: string;
|
||||
gridApi?: any;
|
||||
}>(),
|
||||
buildDailyUploadFormData(modalApi.getData<Record<string, any>>() || {}),
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -8,7 +8,7 @@ import { useRoute } from 'vue-router';
|
||||
|
||||
import { Page, useVbenModal } from '@vben/common-ui';
|
||||
|
||||
import { Image, Tabs, Tag, message } from 'ant-design-vue';
|
||||
import { Image, message, Tabs, Tag } from 'ant-design-vue';
|
||||
|
||||
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||
import { TableAction } from '#/components/table-action';
|
||||
@@ -22,8 +22,8 @@ import {
|
||||
getPublicAccountPayInfo,
|
||||
getPublicAccountPayList,
|
||||
} from './api';
|
||||
import DailyUploadModal from './components/daily-upload-modal.vue';
|
||||
import DailyChangeLogDrawer from './components/daily-change-log-drawer.vue';
|
||||
import DailyUploadModal from './components/daily-upload-modal.vue';
|
||||
import GenerateDailyModal from './components/generate-daily-modal.vue';
|
||||
import MergeOrdersModal from './components/merge-orders-modal.vue';
|
||||
import VoucherThumbs from './components/voucher-thumbs.vue';
|
||||
@@ -35,19 +35,23 @@ import { dailyFormOptions } from './config/daily-search';
|
||||
import { dailyGridOptions } from './config/daily-table';
|
||||
import { formOptions } from './config/search';
|
||||
import { gridOptions } from './config/table';
|
||||
import {
|
||||
buildDailyUploadFormData,
|
||||
resolveDailyUploadId,
|
||||
} from './utils/daily-upload';
|
||||
|
||||
defineOptions({ name: 'FinancePublicAccountPay' });
|
||||
|
||||
const route = useRoute();
|
||||
const TAB_STORAGE_KEY = 'finance_public_account_pay_tab';
|
||||
const activeTab = ref(localStorage.getItem(TAB_STORAGE_KEY) || 'daily');
|
||||
const billMode = ref<'order' | 'daily'>('order');
|
||||
const billMode = ref<'daily' | 'order'>('order');
|
||||
const lockDays = ref(1);
|
||||
const canConfirm = ref(false);
|
||||
const canUpload = ref(false);
|
||||
const canGenerate = ref(false);
|
||||
const detail = ref<Record<string, any> | null>(null);
|
||||
const dailyDetail = ref<Record<string, any> | null>(null);
|
||||
const detail = ref<null | Record<string, any>>(null);
|
||||
const dailyDetail = ref<null | Record<string, any>>(null);
|
||||
const changeLogOpen = ref(false);
|
||||
const changeLogDailyId = ref(0);
|
||||
const changeLogMergeId = ref(0);
|
||||
@@ -100,19 +104,14 @@ function openGenerateDaily() {
|
||||
}
|
||||
|
||||
function openDailyUpload(row: Record<string, any>) {
|
||||
const isPeriod = Number(row.is_period || 0) === 1;
|
||||
const mergeId = Number(row.merge_id || 0);
|
||||
// 上传接口需要真实日账单 id:周期行取 merge_id(=最早日账单 id)
|
||||
const uploadId = isPeriod ? mergeId : Number(row.id || 0);
|
||||
dailyUploadModalApi.setData({
|
||||
id: uploadId,
|
||||
merge_id: mergeId,
|
||||
is_period: isPeriod ? 1 : 0,
|
||||
store_name: row.store_name,
|
||||
platform_amount: row.platform_amount,
|
||||
bill_date_txt: row.bill_date_range_txt || row.bill_date_txt,
|
||||
gridApi: dailyGridApi,
|
||||
});
|
||||
const uploadId = resolveDailyUploadId(row);
|
||||
if (uploadId < 1) {
|
||||
message.warning('无法识别日汇总,请刷新后重试');
|
||||
return;
|
||||
}
|
||||
dailyUploadModalApi.setData(
|
||||
buildDailyUploadFormData(row, { gridApi: dailyGridApi }),
|
||||
);
|
||||
dailyUploadModalApi.open();
|
||||
}
|
||||
|
||||
@@ -164,7 +163,11 @@ function tryOpenFromQuery() {
|
||||
getPublicAccountDailyBillInfo(dailyId)
|
||||
.then((row) => {
|
||||
dailyDetail.value = row;
|
||||
if (Number(row?.status) !== 1 && Number(row?.status) !== 2 && canUpload.value) {
|
||||
if (
|
||||
Number(row?.status) !== 1 &&
|
||||
Number(row?.status) !== 2 &&
|
||||
canUpload.value
|
||||
) {
|
||||
openDailyUpload(row);
|
||||
}
|
||||
})
|
||||
@@ -215,9 +218,11 @@ onMounted(async () => {
|
||||
const cfg = await getPublicAccountPayConfig().catch(() => null);
|
||||
billMode.value = cfg?.bill_mode === 'daily' ? 'daily' : 'order';
|
||||
lockDays.value = Math.max(1, Number(cfg?.lock_days ?? 1));
|
||||
canConfirm.value = Number(cfg?.can_confirm) === 1 || cfg?.can_confirm === true;
|
||||
canConfirm.value =
|
||||
Number(cfg?.can_confirm) === 1 || cfg?.can_confirm === true;
|
||||
canUpload.value = Number(cfg?.can_upload) === 1 || cfg?.can_upload === true;
|
||||
canGenerate.value = Number(cfg?.can_generate) === 1 || cfg?.can_generate === true;
|
||||
canGenerate.value =
|
||||
Number(cfg?.can_generate) === 1 || cfg?.can_generate === true;
|
||||
if (billMode.value === 'order') {
|
||||
persistTab('order');
|
||||
}
|
||||
@@ -233,294 +238,312 @@ watch(
|
||||
<template>
|
||||
<Page auto-content-height title="公账支付账单">
|
||||
<div class="pap-page">
|
||||
<DailyUploadPayModal />
|
||||
<GenerateDailyPayModal />
|
||||
<MergeOrdersPayModal />
|
||||
<DailyChangeLogDrawer
|
||||
v-model:open="changeLogOpen"
|
||||
:daily-bill-id="changeLogDailyId"
|
||||
:merge-id="changeLogMergeId"
|
||||
/>
|
||||
<Tabs
|
||||
v-if="billMode === 'daily'"
|
||||
class="pap-tabs"
|
||||
:active-key="activeTab"
|
||||
@change="persistTab"
|
||||
>
|
||||
<Tabs.TabPane key="daily" tab="日汇总">
|
||||
<DailyGrid>
|
||||
<template #toolbar-actions>
|
||||
<TableAction
|
||||
:actions="[
|
||||
{
|
||||
label: lockDays > 1 ? '生成周期账单' : '生成日账单',
|
||||
type: 'primary',
|
||||
ifShow: canGenerate,
|
||||
onClick: openGenerateDaily,
|
||||
},
|
||||
]"
|
||||
/>
|
||||
</template>
|
||||
<template #status="{ row }">
|
||||
<Tag :color="publicAccountDailyStatusMeta(row.status).color">
|
||||
{{ row.status_txt || publicAccountDailyStatusMeta(row.status).label }}
|
||||
</Tag>
|
||||
</template>
|
||||
<template #vouchers="{ row }">
|
||||
<VoucherThumbs :items="row.vouchers" />
|
||||
</template>
|
||||
<template #arrival_vouchers="{ row }">
|
||||
<VoucherThumbs :items="row.arrival_vouchers" />
|
||||
</template>
|
||||
<template #expand="{ row }">
|
||||
<div class="expand-box">
|
||||
<template v-if="Number(row.is_period || 0) === 1">
|
||||
<div
|
||||
v-for="item in row.items || []"
|
||||
:key="'m-' + item.id"
|
||||
class="expand-row"
|
||||
>
|
||||
日账单 #{{ item.id }} · {{ item.bill_date_txt || item.bill_date }} · ¥{{
|
||||
item.platform_amount
|
||||
}}
|
||||
· {{ item.status_txt }}
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div
|
||||
v-for="item in row.items || []"
|
||||
:key="item.id"
|
||||
class="expand-row"
|
||||
:class="{ 'text-muted-foreground line-through opacity-70': item.status === 2 }"
|
||||
>
|
||||
{{ item.order_no }} · ¥{{ item.platform_amount }} · {{ item.status_txt }}
|
||||
</div>
|
||||
<a
|
||||
v-if="!(row.items || []).length"
|
||||
class="text-sm text-primary"
|
||||
@click="openDailyDetail(row.id)"
|
||||
>
|
||||
查看子单
|
||||
</a>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
<template #action="{ row }">
|
||||
<TableAction
|
||||
:actions="[
|
||||
{
|
||||
label: '上传凭证',
|
||||
type: 'link',
|
||||
ifShow:
|
||||
canUpload &&
|
||||
row.status !== 1 &&
|
||||
row.status !== 2 &&
|
||||
(Number(row.is_period || 0) === 1 ||
|
||||
(Number(row.is_period || 0) !== 1 &&
|
||||
Number(row.status) !== 4)),
|
||||
onClick: openDailyUpload.bind(null, row),
|
||||
},
|
||||
{
|
||||
label: '变动记录',
|
||||
type: 'link',
|
||||
ifShow: Number(row.id || 0) > 0 || Number(row.merge_id || 0) > 0,
|
||||
onClick: openChangeLog.bind(null, row),
|
||||
},
|
||||
{
|
||||
label: '查看合并',
|
||||
type: 'link',
|
||||
ifShow: Number(row.merge_id || 0) > 0,
|
||||
onClick: openMergeDetail.bind(null, Number(row.merge_id)),
|
||||
},
|
||||
{
|
||||
label: '详情',
|
||||
type: 'link',
|
||||
ifShow: Number(row.is_period || 0) !== 1,
|
||||
onClick: openDailyDetail.bind(null, row.id),
|
||||
},
|
||||
{
|
||||
label: '取消',
|
||||
type: 'link',
|
||||
danger: true,
|
||||
ifShow:
|
||||
canConfirm &&
|
||||
Number(row.is_period || 0) !== 1 &&
|
||||
row.status !== 1 &&
|
||||
row.status !== 2 &&
|
||||
row.status !== 4,
|
||||
popConfirm: {
|
||||
title: '确认取消该日汇总?子单将退回待出账',
|
||||
confirm: handleDailyCancel.bind(null, row),
|
||||
<DailyUploadPayModal />
|
||||
<GenerateDailyPayModal />
|
||||
<MergeOrdersPayModal />
|
||||
<DailyChangeLogDrawer
|
||||
v-model:open="changeLogOpen"
|
||||
:daily-bill-id="changeLogDailyId"
|
||||
:merge-id="changeLogMergeId"
|
||||
/>
|
||||
<Tabs
|
||||
v-if="billMode === 'daily'"
|
||||
class="pap-tabs"
|
||||
:active-key="activeTab"
|
||||
@change="persistTab"
|
||||
>
|
||||
<Tabs.TabPane key="daily" tab="日汇总">
|
||||
<DailyGrid>
|
||||
<template #toolbar-actions>
|
||||
<TableAction
|
||||
:actions="[
|
||||
{
|
||||
label: lockDays > 1 ? '生成周期账单' : '生成日账单',
|
||||
type: 'primary',
|
||||
ifShow: canGenerate,
|
||||
onClick: openGenerateDaily,
|
||||
},
|
||||
},
|
||||
]"
|
||||
/>
|
||||
</template>
|
||||
</DailyGrid>
|
||||
</Tabs.TabPane>
|
||||
<Tabs.TabPane key="order" tab="订单明细">
|
||||
<Grid>
|
||||
<template #toolbar-actions>
|
||||
<TableAction
|
||||
:actions="[
|
||||
{
|
||||
label: lockDays > 1 ? '生成周期账单' : '生成日账单',
|
||||
type: 'primary',
|
||||
ifShow: canGenerate,
|
||||
onClick: openGenerateDaily,
|
||||
},
|
||||
]"
|
||||
/>
|
||||
</template>
|
||||
<template #status="{ row }">
|
||||
<Tag :color="publicAccountPayStatusMeta(row.status).color">
|
||||
{{ row.status_txt || publicAccountPayStatusMeta(row.status).label }}
|
||||
</Tag>
|
||||
</template>
|
||||
<template #vouchers="{ row }">
|
||||
<VoucherThumbs :items="row.vouchers" />
|
||||
</template>
|
||||
<template #action="{ row }">
|
||||
<TableAction
|
||||
:actions="[
|
||||
{
|
||||
label: '详情',
|
||||
type: 'link',
|
||||
onClick: openDetail.bind(null, row.id),
|
||||
},
|
||||
]"
|
||||
/>
|
||||
</template>
|
||||
</Grid>
|
||||
</Tabs.TabPane>
|
||||
</Tabs>
|
||||
<Grid v-else>
|
||||
<template #status="{ row }">
|
||||
<Tag :color="publicAccountPayStatusMeta(row.status).color">
|
||||
{{ row.status_txt || publicAccountPayStatusMeta(row.status).label }}
|
||||
</Tag>
|
||||
</template>
|
||||
<template #vouchers="{ row }">
|
||||
<VoucherThumbs :items="row.vouchers" />
|
||||
</template>
|
||||
<template #action="{ row }">
|
||||
<TableAction
|
||||
:actions="[
|
||||
{
|
||||
label: '详情',
|
||||
type: 'link',
|
||||
onClick: openDetail.bind(null, row.id),
|
||||
},
|
||||
{
|
||||
label: '取消',
|
||||
type: 'link',
|
||||
danger: true,
|
||||
ifShow: row.status === 0,
|
||||
popConfirm: {
|
||||
title: '确认取消该待到账单?',
|
||||
confirm: handleCancel.bind(null, row),
|
||||
},
|
||||
},
|
||||
]"
|
||||
/>
|
||||
</template>
|
||||
</Grid>
|
||||
<div v-if="dailyDetail" class="detail-card mt-4">
|
||||
<div class="mb-2 flex items-center justify-between">
|
||||
<b>
|
||||
日汇总 #{{ dailyDetail.id }} ·
|
||||
{{ dailyDetail.bill_date_range_txt || dailyDetail.bill_date_txt }}
|
||||
</b>
|
||||
<a class="text-sm text-primary" @click="dailyDetail = null">关闭</a>
|
||||
<a
|
||||
class="ml-3 text-sm text-primary"
|
||||
@click="openChangeLog(dailyDetail)"
|
||||
>
|
||||
变动记录
|
||||
</a>
|
||||
</div>
|
||||
<div class="text-sm text-[hsl(var(--muted-foreground))]">
|
||||
{{ dailyDetail.store_name }} · {{ dailyDetail.status_txt }} · 应付款
|
||||
¥{{ dailyDetail.platform_amount || '0.00' }} · {{ dailyDetail.order_count }} 单
|
||||
<template v-if="Number(dailyDetail.merge_id || 0) > 0">
|
||||
· 合并ID
|
||||
<a
|
||||
class="text-primary"
|
||||
@click="openMergeDetail(Number(dailyDetail.merge_id))"
|
||||
>
|
||||
#{{ dailyDetail.merge_id }}
|
||||
</a>
|
||||
]"
|
||||
/>
|
||||
</template>
|
||||
<template #status="{ row }">
|
||||
<Tag :color="publicAccountDailyStatusMeta(row.status).color">
|
||||
{{
|
||||
row.status_txt ||
|
||||
publicAccountDailyStatusMeta(row.status).label
|
||||
}}
|
||||
</Tag>
|
||||
</template>
|
||||
<template #vouchers="{ row }">
|
||||
<VoucherThumbs :items="row.vouchers" />
|
||||
</template>
|
||||
<template #arrival_vouchers="{ row }">
|
||||
<VoucherThumbs :items="row.arrival_vouchers" />
|
||||
</template>
|
||||
<template #expand="{ row }">
|
||||
<div class="expand-box">
|
||||
<template v-if="Number(row.is_period || 0) === 1">
|
||||
<div
|
||||
v-for="item in row.items || []"
|
||||
:key="`m-${ item.id}`"
|
||||
class="expand-row"
|
||||
>
|
||||
日账单 #{{ item.id }} ·
|
||||
{{ item.bill_date_txt || item.bill_date }} · ¥{{
|
||||
item.platform_amount
|
||||
}}
|
||||
· {{ item.status_txt }}
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div
|
||||
v-for="item in row.items || []"
|
||||
:key="item.id"
|
||||
class="expand-row"
|
||||
:class="{
|
||||
'text-muted-foreground line-through opacity-70':
|
||||
item.status === 2,
|
||||
}"
|
||||
>
|
||||
{{ item.order_no }} · ¥{{ item.platform_amount }} ·
|
||||
{{ item.status_txt }}
|
||||
</div>
|
||||
<a
|
||||
v-if="!(row.items || []).length"
|
||||
class="text-sm text-primary"
|
||||
@click="openDailyDetail(row.id)"
|
||||
>
|
||||
查看子单
|
||||
</a>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
<template #action="{ row }">
|
||||
<TableAction
|
||||
:actions="[
|
||||
{
|
||||
label: '上传凭证',
|
||||
type: 'link',
|
||||
ifShow:
|
||||
canUpload &&
|
||||
row.status !== 1 &&
|
||||
row.status !== 2 &&
|
||||
(Number(row.is_period || 0) === 1 ||
|
||||
(Number(row.is_period || 0) !== 1 &&
|
||||
Number(row.status) !== 4)),
|
||||
onClick: openDailyUpload.bind(null, row),
|
||||
},
|
||||
{
|
||||
label: '变动记录',
|
||||
type: 'link',
|
||||
ifShow:
|
||||
Number(row.id || 0) > 0 || Number(row.merge_id || 0) > 0,
|
||||
onClick: openChangeLog.bind(null, row),
|
||||
},
|
||||
{
|
||||
label: '查看合并',
|
||||
type: 'link',
|
||||
ifShow: Number(row.merge_id || 0) > 0,
|
||||
onClick: openMergeDetail.bind(null, Number(row.merge_id)),
|
||||
},
|
||||
{
|
||||
label: '详情',
|
||||
type: 'link',
|
||||
ifShow: Number(row.is_period || 0) !== 1,
|
||||
onClick: openDailyDetail.bind(null, row.id),
|
||||
},
|
||||
{
|
||||
label: '取消',
|
||||
type: 'link',
|
||||
danger: true,
|
||||
ifShow:
|
||||
canConfirm &&
|
||||
Number(row.is_period || 0) !== 1 &&
|
||||
row.status !== 1 &&
|
||||
row.status !== 2 &&
|
||||
row.status !== 4,
|
||||
popConfirm: {
|
||||
title: '确认取消该日汇总?子单将退回待出账',
|
||||
confirm: handleDailyCancel.bind(null, row),
|
||||
},
|
||||
},
|
||||
]"
|
||||
/>
|
||||
</template>
|
||||
</DailyGrid>
|
||||
</Tabs.TabPane>
|
||||
<Tabs.TabPane key="order" tab="订单明细">
|
||||
<Grid>
|
||||
<template #toolbar-actions>
|
||||
<TableAction
|
||||
:actions="[
|
||||
{
|
||||
label: lockDays > 1 ? '生成周期账单' : '生成日账单',
|
||||
type: 'primary',
|
||||
ifShow: canGenerate,
|
||||
onClick: openGenerateDaily,
|
||||
},
|
||||
]"
|
||||
/>
|
||||
</template>
|
||||
<template #status="{ row }">
|
||||
<Tag :color="publicAccountPayStatusMeta(row.status).color">
|
||||
{{
|
||||
row.status_txt || publicAccountPayStatusMeta(row.status).label
|
||||
}}
|
||||
</Tag>
|
||||
</template>
|
||||
<template #vouchers="{ row }">
|
||||
<VoucherThumbs :items="row.vouchers" />
|
||||
</template>
|
||||
<template #action="{ row }">
|
||||
<TableAction
|
||||
:actions="[
|
||||
{
|
||||
label: '详情',
|
||||
type: 'link',
|
||||
onClick: openDetail.bind(null, row.id),
|
||||
},
|
||||
]"
|
||||
/>
|
||||
</template>
|
||||
</Grid>
|
||||
</Tabs.TabPane>
|
||||
</Tabs>
|
||||
<Grid v-else>
|
||||
<template #status="{ row }">
|
||||
<Tag :color="publicAccountPayStatusMeta(row.status).color">
|
||||
{{ row.status_txt || publicAccountPayStatusMeta(row.status).label }}
|
||||
</Tag>
|
||||
</template>
|
||||
</div>
|
||||
<div v-if="(dailyDetail.items || []).length" class="mt-3 text-sm">
|
||||
<div
|
||||
v-for="item in dailyDetail.items"
|
||||
:key="item.id"
|
||||
class="expand-row"
|
||||
:class="{ 'text-muted-foreground line-through opacity-70': item.status === 2 }"
|
||||
>
|
||||
{{ item.order_no }} · 平台 ¥{{ item.platform_amount }} · {{ item.status_txt }}
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="(dailyDetail.vouchers || []).length" class="mt-3">
|
||||
<div class="mb-1 text-sm">付款凭证</div>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<Image
|
||||
v-for="item in dailyDetail.vouchers"
|
||||
:key="item.id"
|
||||
:src="item.file_url"
|
||||
:width="80"
|
||||
:height="80"
|
||||
<template #vouchers="{ row }">
|
||||
<VoucherThumbs :items="row.vouchers" />
|
||||
</template>
|
||||
<template #action="{ row }">
|
||||
<TableAction
|
||||
:actions="[
|
||||
{
|
||||
label: '详情',
|
||||
type: 'link',
|
||||
onClick: openDetail.bind(null, row.id),
|
||||
},
|
||||
{
|
||||
label: '取消',
|
||||
type: 'link',
|
||||
danger: true,
|
||||
ifShow: row.status === 0,
|
||||
popConfirm: {
|
||||
title: '确认取消该待到账单?',
|
||||
confirm: handleCancel.bind(null, row),
|
||||
},
|
||||
},
|
||||
]"
|
||||
/>
|
||||
</template>
|
||||
</Grid>
|
||||
<div v-if="dailyDetail" class="detail-card mt-4">
|
||||
<div class="mb-2 flex items-center justify-between">
|
||||
<b>
|
||||
日汇总 #{{ dailyDetail.id }} ·
|
||||
{{ dailyDetail.bill_date_range_txt || dailyDetail.bill_date_txt }}
|
||||
</b>
|
||||
<a class="text-sm text-primary" @click="dailyDetail = null">关闭</a>
|
||||
<a
|
||||
class="ml-3 text-sm text-primary"
|
||||
@click="openChangeLog(dailyDetail)"
|
||||
>
|
||||
变动记录
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="(dailyDetail.arrival_vouchers || []).length" class="mt-3">
|
||||
<div class="mb-1 text-sm">到账凭证</div>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<Image
|
||||
v-for="item in dailyDetail.arrival_vouchers"
|
||||
<div class="text-sm text-[hsl(var(--muted-foreground))]">
|
||||
{{ dailyDetail.store_name }} · {{ dailyDetail.status_txt }} · 应付款
|
||||
¥{{ dailyDetail.platform_amount || '0.00' }} ·
|
||||
{{ dailyDetail.order_count }} 单
|
||||
<template v-if="Number(dailyDetail.merge_id || 0) > 0">
|
||||
· 合并ID
|
||||
<a
|
||||
class="text-primary"
|
||||
@click="openMergeDetail(Number(dailyDetail.merge_id))"
|
||||
>
|
||||
#{{ dailyDetail.merge_id }}
|
||||
</a>
|
||||
</template>
|
||||
</div>
|
||||
<div v-if="(dailyDetail.items || []).length" class="mt-3 text-sm">
|
||||
<div
|
||||
v-for="item in dailyDetail.items"
|
||||
:key="item.id"
|
||||
:src="item.file_url"
|
||||
:width="80"
|
||||
:height="80"
|
||||
/>
|
||||
class="expand-row"
|
||||
:class="{
|
||||
'text-muted-foreground line-through opacity-70':
|
||||
item.status === 2,
|
||||
}"
|
||||
>
|
||||
{{ item.order_no }} · 平台 ¥{{ item.platform_amount }} ·
|
||||
{{ item.status_txt }}
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="(dailyDetail.vouchers || []).length" class="mt-3">
|
||||
<div class="mb-1 text-sm">付款凭证</div>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<Image
|
||||
v-for="item in dailyDetail.vouchers"
|
||||
:key="item.id"
|
||||
:src="item.file_url"
|
||||
:width="80"
|
||||
:height="80"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="(dailyDetail.arrival_vouchers || []).length" class="mt-3">
|
||||
<div class="mb-1 text-sm">到账凭证</div>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<Image
|
||||
v-for="item in dailyDetail.arrival_vouchers"
|
||||
:key="item.id"
|
||||
:src="item.file_url"
|
||||
:width="80"
|
||||
:height="80"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="detail" class="detail-card mt-4">
|
||||
<div class="mb-2 flex items-center justify-between">
|
||||
<b>账单 #{{ detail.id }} · {{ detail.order_no }}</b>
|
||||
<a class="text-sm text-primary" @click="detail = null">关闭</a>
|
||||
</div>
|
||||
<div class="text-sm text-[hsl(var(--muted-foreground))]">
|
||||
状态 {{ detail.status_txt }} · 分账 {{ detail.split_mode_txt || '未选' }} ·
|
||||
入账 ¥{{ detail.paid_amount || '0.00' }}
|
||||
</div>
|
||||
<div v-if="(detail.payment_vouchers || []).length" class="mt-3">
|
||||
<div class="mb-1 text-sm">付款凭证</div>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<Image
|
||||
v-for="item in detail.payment_vouchers"
|
||||
:key="item.id"
|
||||
:src="item.file_url"
|
||||
:width="80"
|
||||
:height="80"
|
||||
/>
|
||||
<div v-if="detail" class="detail-card mt-4">
|
||||
<div class="mb-2 flex items-center justify-between">
|
||||
<b>账单 #{{ detail.id }} · {{ detail.order_no }}</b>
|
||||
<a class="text-sm text-primary" @click="detail = null">关闭</a>
|
||||
</div>
|
||||
<div class="text-sm text-[hsl(var(--muted-foreground))]">
|
||||
状态 {{ detail.status_txt }} · 分账
|
||||
{{ detail.split_mode_txt || '未选' }} · 入账 ¥{{
|
||||
detail.paid_amount || '0.00'
|
||||
}}
|
||||
</div>
|
||||
<div v-if="(detail.payment_vouchers || []).length" class="mt-3">
|
||||
<div class="mb-1 text-sm">付款凭证</div>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<Image
|
||||
v-for="item in detail.payment_vouchers"
|
||||
:key="item.id"
|
||||
:src="item.file_url"
|
||||
:width="80"
|
||||
:height="80"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="(detail.arrival_vouchers || []).length" class="mt-3">
|
||||
<div class="mb-1 text-sm">到账凭证</div>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<Image
|
||||
v-for="item in detail.arrival_vouchers"
|
||||
:key="item.id"
|
||||
:src="item.file_url"
|
||||
:width="80"
|
||||
:height="80"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="(detail.arrival_vouchers || []).length" class="mt-3">
|
||||
<div class="mb-1 text-sm">到账凭证</div>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<Image
|
||||
v-for="item in detail.arrival_vouchers"
|
||||
:key="item.id"
|
||||
:src="item.file_url"
|
||||
:width="80"
|
||||
:height="80"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Page>
|
||||
</template>
|
||||
@@ -532,40 +555,48 @@ watch(
|
||||
height: 100%;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.pap-page > :deep(.h-full) {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.pap-tabs {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.pap-tabs :deep(.ant-tabs-nav) {
|
||||
flex-shrink: 0;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.pap-tabs :deep(.ant-tabs-content-holder) {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.pap-tabs :deep(.ant-tabs-content) {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.pap-tabs :deep(.ant-tabs-tabpane),
|
||||
.pap-tabs :deep(.ant-tabs-tabpane-active) {
|
||||
flex: 1;
|
||||
height: 100%;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.pap-page :deep(.vxe-grid) {
|
||||
height: 100% !important;
|
||||
}
|
||||
|
||||
.detail-card {
|
||||
padding: 14px 16px;
|
||||
background: hsl(var(--card, var(--background)));
|
||||
@@ -573,12 +604,14 @@ watch(
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 0 6px hsl(var(--primary) / 18%);
|
||||
}
|
||||
|
||||
.expand-box {
|
||||
padding: 8px 12px;
|
||||
}
|
||||
|
||||
.expand-row {
|
||||
padding: 4px 0;
|
||||
color: hsl(var(--muted-foreground));
|
||||
font-size: 13px;
|
||||
color: hsl(var(--muted-foreground));
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -0,0 +1,141 @@
|
||||
/**
|
||||
* 日汇总上传凭证:从列表/锁店/站内信等不同入口解析展示与提交字段
|
||||
*/
|
||||
export interface DailyUploadRowLike {
|
||||
id?: number | string;
|
||||
merge_id?: number | string;
|
||||
is_period?: number | string;
|
||||
store_id?: number | string;
|
||||
store_name?: string;
|
||||
store?: { name?: string };
|
||||
platform_amount?: number | string;
|
||||
platform_amount_total?: number | string;
|
||||
bill_date_txt?: string;
|
||||
bill_date_range_txt?: string;
|
||||
items?: Array<{ id?: number | string; platform_amount?: number | string }>;
|
||||
}
|
||||
|
||||
/** 上传接口需要的真实日账单 id(周期行 id 常为 0) */
|
||||
export function resolveDailyUploadId(row?: DailyUploadRowLike | null): number {
|
||||
if (!row) {
|
||||
return 0;
|
||||
}
|
||||
const directId = Number(row.id || 0);
|
||||
if (directId > 0) {
|
||||
return directId;
|
||||
}
|
||||
const mergeId = Number(row.merge_id || 0);
|
||||
if (mergeId > 0) {
|
||||
return mergeId;
|
||||
}
|
||||
const firstItem = row.items?.[0];
|
||||
const itemId = Number(firstItem?.id || 0);
|
||||
if (itemId > 0) {
|
||||
return itemId;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/** 列表/锁店展示的应付款(平台成本) */
|
||||
export function resolveDailyPlatformAmount(
|
||||
row?: DailyUploadRowLike | null,
|
||||
): string {
|
||||
if (!row) {
|
||||
return '0.00';
|
||||
}
|
||||
const isPeriod = Number(row.is_period || 0) === 1;
|
||||
const total = trimAmount(row.platform_amount_total);
|
||||
const amount = trimAmount(row.platform_amount);
|
||||
if (isPeriod) {
|
||||
if (total) {
|
||||
return total;
|
||||
}
|
||||
if (amount) {
|
||||
return amount;
|
||||
}
|
||||
return sumItemPlatformAmounts(row.items);
|
||||
}
|
||||
if (amount) {
|
||||
return amount;
|
||||
}
|
||||
if (total) {
|
||||
return total;
|
||||
}
|
||||
return sumItemPlatformAmounts(row.items);
|
||||
}
|
||||
|
||||
export function resolveDailyStoreName(row?: DailyUploadRowLike | null): string {
|
||||
if (!row) {
|
||||
return '';
|
||||
}
|
||||
return String(row.store_name || row.store?.name || '').trim();
|
||||
}
|
||||
|
||||
export function resolveDailyBillDateTxt(
|
||||
row?: DailyUploadRowLike | null,
|
||||
): string {
|
||||
if (!row) {
|
||||
return '';
|
||||
}
|
||||
return String(row.bill_date_range_txt || row.bill_date_txt || '').trim();
|
||||
}
|
||||
|
||||
/** 打开上传弹窗/表单时灌入的标准数据结构 */
|
||||
export function buildDailyUploadFormData(
|
||||
row: DailyUploadRowLike,
|
||||
extras?: Record<string, any>,
|
||||
) {
|
||||
const uploadId = resolveDailyUploadId(row);
|
||||
return {
|
||||
...row,
|
||||
...extras,
|
||||
id: uploadId,
|
||||
store_name: resolveDailyStoreName(row),
|
||||
platform_amount: resolveDailyPlatformAmount(row),
|
||||
bill_date_txt: resolveDailyBillDateTxt(row),
|
||||
};
|
||||
}
|
||||
|
||||
/** 金额展示:千分位 + 两位小数 */
|
||||
export function formatDailyPlatformAmount(value: unknown): string {
|
||||
const raw = String(value ?? '').trim();
|
||||
if (!raw) {
|
||||
return '0.00';
|
||||
}
|
||||
const n = Number(raw.replaceAll(/,/g, ''));
|
||||
if (!Number.isFinite(n)) {
|
||||
return raw;
|
||||
}
|
||||
return n.toLocaleString(undefined, {
|
||||
minimumFractionDigits: 2,
|
||||
maximumFractionDigits: 2,
|
||||
});
|
||||
}
|
||||
|
||||
/** 提交给后端的凭证结构 */
|
||||
export function normalizeDailyVoucherUrls(urls: string[]) {
|
||||
return urls
|
||||
.map((url) => String(url || '').trim())
|
||||
.filter(Boolean)
|
||||
.map((file_url) => ({ file_url, file_id: 0 }));
|
||||
}
|
||||
|
||||
function trimAmount(value: unknown): string {
|
||||
return String(value ?? '').trim();
|
||||
}
|
||||
|
||||
function sumItemPlatformAmounts(
|
||||
items?: Array<{ platform_amount?: number | string }>,
|
||||
): string {
|
||||
if (!items?.length) {
|
||||
return '0.00';
|
||||
}
|
||||
let total = 0;
|
||||
for (const item of items) {
|
||||
const n = Number(item.platform_amount ?? 0);
|
||||
if (Number.isFinite(n)) {
|
||||
total += n;
|
||||
}
|
||||
}
|
||||
return total.toFixed(2);
|
||||
}
|
||||
Reference in New Issue
Block a user