Some checks failed
CI / Test (ubuntu-latest) (push) Has been cancelled
CI / Test (windows-latest) (push) Has been cancelled
CI / Lint (ubuntu-latest) (push) Has been cancelled
CI / Lint (windows-latest) (push) Has been cancelled
CI / Check (ubuntu-latest) (push) Has been cancelled
CI / Check (windows-latest) (push) Has been cancelled
CI / CI OK (push) Has been cancelled
CodeQL / Analyze (javascript-typescript) (push) Has been cancelled
Deploy Website on push / Deploy Push Playground Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Docs Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Antd Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Element Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Naive Ftp (push) Has been cancelled
Deploy Website on push / Rerun on failure (push) Has been cancelled
Release Drafter / update_release_draft (push) Has been cancelled
周期账单生成与变动记录抽屉;查看合并按账单日 Tabs;退款原因必填;开方成功页操作按钮自动换行。
551 lines
14 KiB
Vue
551 lines
14 KiB
Vue
<script lang="ts" setup>
|
||
/**
|
||
* 锁店 / 查看合并:日账单 + 下属商品订单(只读)
|
||
* 按账单日用 Tabs 切换,避免多日堆叠难扫
|
||
*/
|
||
import { computed, onMounted, ref, watch } from 'vue';
|
||
|
||
import { Button, Spin, Tabs, Tag } from 'ant-design-vue';
|
||
|
||
import DailyChangeLogDrawer from '#/views/finance/public-account-pay/components/daily-change-log-drawer.vue';
|
||
import { getPublicAccountDailyLockOrders } from '#/views/finance/public-account-pay/api';
|
||
|
||
const props = defineProps<{
|
||
mergeId?: number;
|
||
dailyId?: number;
|
||
overdueDailyIds?: number[];
|
||
/** 锁店内页要返回;弹窗内隐藏 */
|
||
showBack?: boolean;
|
||
/** 弹窗内铺满宽度 */
|
||
embedded?: boolean;
|
||
}>();
|
||
|
||
const emit = defineEmits<{
|
||
back: [];
|
||
}>();
|
||
|
||
const showBackBtn = computed(() => props.showBack !== false);
|
||
|
||
const loading = ref(false);
|
||
const loadError = ref('');
|
||
const bundle = ref<Record<string, any> | null>(null);
|
||
/** 当前选中的日账单 id(Tabs) */
|
||
const activeDayId = ref<string>('');
|
||
const changeLogOpen = ref(false);
|
||
|
||
const days = computed(() => {
|
||
const list = bundle.value?.items;
|
||
return Array.isArray(list) ? list : [];
|
||
});
|
||
|
||
const activeDay = computed(() => {
|
||
const id = Number(activeDayId.value || 0);
|
||
if (id < 1) {
|
||
return days.value[0] || null;
|
||
}
|
||
return days.value.find((d) => Number(d.id) === id) || days.value[0] || null;
|
||
});
|
||
|
||
const summary = computed(() => {
|
||
const b = bundle.value;
|
||
if (!b) {
|
||
return null;
|
||
}
|
||
return {
|
||
range: b.bill_date_range_txt || '',
|
||
billCount: Number(b.bill_count || 0),
|
||
orderCount: Number(b.order_count || 0),
|
||
amount: b.platform_amount_total || '0.00',
|
||
periodEnd: b.period_end_at_txt || '',
|
||
};
|
||
});
|
||
|
||
function dayDateTxt(day: Record<string, any>) {
|
||
return day.bill_date_txt || String(day.bill_date || '—');
|
||
}
|
||
|
||
/** Tab 标题:月-日,短一点好扫 */
|
||
function dayTabLabel(day: Record<string, any>) {
|
||
const txt = dayDateTxt(day);
|
||
if (/^\d{4}-\d{2}-\d{2}$/.test(txt)) {
|
||
return txt.slice(5);
|
||
}
|
||
return txt;
|
||
}
|
||
|
||
function dayOrderCount(day: Record<string, any>) {
|
||
return (day.items || []).length;
|
||
}
|
||
|
||
async function loadOrders() {
|
||
loading.value = true;
|
||
loadError.value = '';
|
||
try {
|
||
const data = await getPublicAccountDailyLockOrders({
|
||
merge_id: Number(props.mergeId || 0) || undefined,
|
||
daily_id: Number(props.dailyId || 0) || undefined,
|
||
overdue_daily_ids: (props.overdueDailyIds || []).join(',') || undefined,
|
||
});
|
||
bundle.value = data && typeof data === 'object' ? data : null;
|
||
const first = days.value[0];
|
||
activeDayId.value = first ? String(Number(first.id || 0)) : '';
|
||
} catch (err: any) {
|
||
loadError.value = err?.message || '加载失败';
|
||
bundle.value = null;
|
||
activeDayId.value = '';
|
||
} finally {
|
||
loading.value = false;
|
||
}
|
||
}
|
||
|
||
onMounted(() => {
|
||
void loadOrders();
|
||
});
|
||
|
||
watch(
|
||
() => [
|
||
Number(props.mergeId || 0),
|
||
Number(props.dailyId || 0),
|
||
(props.overdueDailyIds || []).join(','),
|
||
],
|
||
() => {
|
||
void loadOrders();
|
||
},
|
||
);
|
||
</script>
|
||
|
||
<template>
|
||
<div class="pap-orders" :class="{ 'is-embedded': embedded }">
|
||
<div class="pap-orders-head">
|
||
<div class="pap-orders-head-main">
|
||
<div class="pap-orders-badge">只读明细</div>
|
||
<h3 class="pap-orders-title">合并账单明细</h3>
|
||
<div v-if="summary" class="pap-orders-stats">
|
||
<div class="pap-stat">
|
||
<span class="pap-stat-label">账单区间</span>
|
||
<strong class="pap-stat-value">{{ summary.range || '—' }}</strong>
|
||
</div>
|
||
<div class="pap-stat">
|
||
<span class="pap-stat-label">日账单</span>
|
||
<strong class="pap-stat-value">{{ summary.billCount }} 张</strong>
|
||
</div>
|
||
<div class="pap-stat">
|
||
<span class="pap-stat-label">商品订单</span>
|
||
<strong class="pap-stat-value">{{ summary.orderCount }} 笔</strong>
|
||
</div>
|
||
<div class="pap-stat pap-stat-amount">
|
||
<span class="pap-stat-label">合计应付款</span>
|
||
<strong class="pap-stat-value">¥{{ summary.amount }}</strong>
|
||
</div>
|
||
</div>
|
||
<p v-if="summary?.periodEnd" class="pap-orders-deadline">
|
||
周期截止 {{ summary.periodEnd }}
|
||
</p>
|
||
</div>
|
||
<div class="pap-orders-actions">
|
||
<Button
|
||
v-if="showBackBtn"
|
||
size="middle"
|
||
@click="emit('back')"
|
||
>
|
||
返回
|
||
</Button>
|
||
<Button
|
||
v-if="Number(mergeId || 0) > 0 || Number(dailyId || 0) > 0"
|
||
size="middle"
|
||
@click="changeLogOpen = true"
|
||
>
|
||
变动记录
|
||
</Button>
|
||
</div>
|
||
</div>
|
||
<DailyChangeLogDrawer
|
||
v-model:open="changeLogOpen"
|
||
:daily-bill-id="Number(dailyId || 0)"
|
||
:merge-id="Number(mergeId || 0)"
|
||
/>
|
||
<Spin :spinning="loading">
|
||
<p v-if="loadError" class="pap-orders-empty">{{ loadError }}</p>
|
||
<p v-else-if="!loading && !days.length" class="pap-orders-empty">
|
||
暂无日账单明细
|
||
</p>
|
||
<div v-else class="pap-orders-body">
|
||
<Tabs
|
||
v-model:active-key="activeDayId"
|
||
class="pap-day-tabs"
|
||
type="card"
|
||
size="small"
|
||
>
|
||
<Tabs.TabPane
|
||
v-for="day in days"
|
||
:key="String(Number(day.id || 0))"
|
||
>
|
||
<template #tab>
|
||
<span class="pap-day-tab">
|
||
<span class="pap-day-tab-date">{{ dayTabLabel(day) }}</span>
|
||
<span class="pap-day-tab-amt">¥{{ day.platform_amount || '0.00' }}</span>
|
||
</span>
|
||
</template>
|
||
</Tabs.TabPane>
|
||
</Tabs>
|
||
<div v-if="activeDay" class="pap-day-panel">
|
||
<div class="pap-day-panel-head">
|
||
<div class="pap-day-panel-title">
|
||
<strong>{{ dayDateTxt(activeDay) }}</strong>
|
||
<Tag>{{ activeDay.status_txt || '—' }}</Tag>
|
||
</div>
|
||
<div class="pap-day-panel-meta">
|
||
<span>{{ dayOrderCount(activeDay) }} 笔订单</span>
|
||
<span>应付款 ¥{{ activeDay.platform_amount || '0.00' }}</span>
|
||
<span v-if="activeDay.order_amount">
|
||
订单合计 ¥{{ activeDay.order_amount }}
|
||
</span>
|
||
</div>
|
||
</div>
|
||
<div
|
||
v-if="!(activeDay.items || []).length"
|
||
class="pap-order-empty"
|
||
>
|
||
该日暂无商品订单
|
||
</div>
|
||
<div v-else class="pap-order-list">
|
||
<div
|
||
v-for="order in activeDay.items || []"
|
||
:key="order.id"
|
||
class="pap-order-row"
|
||
>
|
||
<div class="pap-order-top">
|
||
<div class="pap-order-main">
|
||
<span class="pap-order-tag">商品订单</span>
|
||
<strong>{{ order.order_no || `#${order.order_id}` }}</strong>
|
||
</div>
|
||
<div class="pap-order-side">
|
||
<span>订单 ¥{{ order.order_amount || '0.00' }}</span>
|
||
<strong>平台 ¥{{ order.platform_amount || '0.00' }}</strong>
|
||
<span>{{ order.status_txt || '' }}</span>
|
||
</div>
|
||
</div>
|
||
<div
|
||
v-if="(order.products || []).length"
|
||
class="pap-order-products"
|
||
>
|
||
<div
|
||
v-for="product in order.products"
|
||
:key="product.id"
|
||
class="pap-product"
|
||
:class="{ 'is-chinese': Number(product.is_chinese) === 1 }"
|
||
>
|
||
<template v-if="Number(product.is_chinese) === 1">
|
||
<span class="pap-product-chip">
|
||
{{ product.drug_name || '—' }}×{{ product.number ?? 0 }}
|
||
</span>
|
||
</template>
|
||
<template v-else>
|
||
<img
|
||
v-if="product.drug_image"
|
||
class="pap-product-img"
|
||
:src="product.drug_image"
|
||
:alt="product.drug_name || '药品'"
|
||
/>
|
||
<div v-else class="pap-product-img is-empty">药</div>
|
||
<div class="pap-product-meta">
|
||
<strong>{{ product.drug_name || '—' }}</strong>
|
||
<span>×{{ product.number ?? 0 }}</span>
|
||
</div>
|
||
</template>
|
||
</div>
|
||
</div>
|
||
<div v-else class="pap-order-empty-products">暂无商品明细</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</Spin>
|
||
</div>
|
||
</template>
|
||
|
||
<style scoped>
|
||
.pap-orders {
|
||
width: min(720px, 100%);
|
||
max-width: 100%;
|
||
text-align: left;
|
||
}
|
||
.pap-orders.is-embedded {
|
||
width: 100%;
|
||
}
|
||
.pap-orders-head {
|
||
display: flex;
|
||
align-items: flex-start;
|
||
justify-content: space-between;
|
||
gap: 16px;
|
||
margin-bottom: 14px;
|
||
}
|
||
.pap-orders-head-main {
|
||
min-width: 0;
|
||
flex: 1;
|
||
}
|
||
.pap-orders-badge {
|
||
display: inline-block;
|
||
padding: 2px 10px;
|
||
margin-bottom: 8px;
|
||
font-size: 12px;
|
||
font-weight: 600;
|
||
color: hsl(var(--pap-accent, var(--primary)));
|
||
border: 1px solid hsl(var(--pap-accent, var(--primary)) / 40%);
|
||
border-radius: 999px;
|
||
background: hsl(var(--pap-accent, var(--primary)) / 12%);
|
||
}
|
||
.pap-orders-title {
|
||
margin: 0 0 10px;
|
||
font-size: var(--pap-fs-title, 20px);
|
||
font-weight: 700;
|
||
color: hsl(var(--foreground));
|
||
}
|
||
.pap-orders-stats {
|
||
display: grid;
|
||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||
gap: 8px;
|
||
}
|
||
.pap-stat {
|
||
padding: 10px 12px;
|
||
border: 1px solid hsl(var(--border));
|
||
border-radius: 8px;
|
||
background: hsl(var(--muted) / 0.22);
|
||
}
|
||
.pap-stat-amount {
|
||
border-color: hsl(var(--pap-accent, var(--primary)) / 30%);
|
||
box-shadow: 0 0 6px hsl(var(--pap-accent, var(--primary)) / 14%);
|
||
}
|
||
.pap-stat-label {
|
||
display: block;
|
||
margin-bottom: 4px;
|
||
font-size: 12px;
|
||
color: hsl(var(--muted-foreground));
|
||
}
|
||
.pap-stat-value {
|
||
font-size: 14px;
|
||
font-weight: 600;
|
||
color: hsl(var(--foreground));
|
||
word-break: break-all;
|
||
}
|
||
.pap-stat-amount .pap-stat-value {
|
||
color: hsl(var(--pap-accent, var(--primary)));
|
||
}
|
||
.pap-orders-deadline {
|
||
margin: 8px 0 0;
|
||
font-size: 12px;
|
||
color: hsl(var(--muted-foreground));
|
||
}
|
||
.pap-orders-actions {
|
||
display: flex;
|
||
flex-shrink: 0;
|
||
flex-wrap: wrap;
|
||
gap: 8px;
|
||
}
|
||
.pap-orders-empty {
|
||
margin: 24px 0;
|
||
font-size: 14px;
|
||
color: hsl(var(--muted-foreground));
|
||
text-align: center;
|
||
}
|
||
.pap-orders-body {
|
||
border: 1px solid hsl(var(--border));
|
||
border-radius: 10px;
|
||
background: hsl(var(--card, var(--background)));
|
||
overflow: hidden;
|
||
}
|
||
.pap-day-tabs {
|
||
padding: 8px 8px 0;
|
||
background: hsl(var(--muted) / 0.18);
|
||
}
|
||
.pap-day-tabs :deep(.ant-tabs-nav) {
|
||
margin-bottom: 0;
|
||
}
|
||
.pap-day-tabs :deep(.ant-tabs-tab) {
|
||
padding: 6px 10px !important;
|
||
}
|
||
.pap-day-tab {
|
||
display: inline-flex;
|
||
flex-direction: column;
|
||
align-items: flex-start;
|
||
gap: 2px;
|
||
line-height: 1.2;
|
||
}
|
||
.pap-day-tab-date {
|
||
font-size: 13px;
|
||
font-weight: 600;
|
||
color: hsl(var(--foreground));
|
||
}
|
||
.pap-day-tab-amt {
|
||
font-size: 11px;
|
||
color: hsl(var(--muted-foreground));
|
||
}
|
||
.pap-day-panel {
|
||
padding: 12px 14px 14px;
|
||
}
|
||
.pap-day-panel-head {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
gap: 8px;
|
||
margin-bottom: 12px;
|
||
padding-bottom: 10px;
|
||
border-bottom: 1px solid hsl(var(--border));
|
||
}
|
||
.pap-day-panel-title {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
}
|
||
.pap-day-panel-title strong {
|
||
font-size: 15px;
|
||
color: hsl(var(--foreground));
|
||
}
|
||
.pap-day-panel-meta {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 12px;
|
||
font-size: 12px;
|
||
color: hsl(var(--muted-foreground));
|
||
}
|
||
.pap-order-empty {
|
||
padding: 16px 4px;
|
||
font-size: 13px;
|
||
color: hsl(var(--muted-foreground));
|
||
text-align: center;
|
||
}
|
||
.pap-order-list {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 10px;
|
||
}
|
||
.pap-order-row {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 8px;
|
||
padding: 12px;
|
||
border: 1px solid hsl(var(--border));
|
||
border-radius: 8px;
|
||
background: hsl(var(--muted) / 0.18);
|
||
}
|
||
.pap-order-top {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
gap: 8px;
|
||
}
|
||
.pap-order-main {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
}
|
||
.pap-order-tag {
|
||
padding: 1px 8px;
|
||
font-size: 11px;
|
||
color: hsl(var(--pap-accent, var(--primary)));
|
||
border: 1px solid hsl(var(--pap-accent, var(--primary)) / 30%);
|
||
border-radius: 999px;
|
||
background: hsl(var(--pap-accent, var(--primary)) / 10%);
|
||
}
|
||
.pap-order-main strong {
|
||
font-size: 13px;
|
||
color: hsl(var(--foreground));
|
||
}
|
||
.pap-order-side {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 10px;
|
||
font-size: 12px;
|
||
color: hsl(var(--muted-foreground));
|
||
}
|
||
.pap-order-side strong {
|
||
color: hsl(var(--pap-accent, var(--primary)));
|
||
}
|
||
.pap-order-products {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 6px;
|
||
}
|
||
.pap-product {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 6px;
|
||
width: calc(33.333% - 4px);
|
||
min-width: 0;
|
||
padding: 4px 6px;
|
||
border-radius: 6px;
|
||
background: hsl(var(--background) / 0.55);
|
||
border: 1px solid hsl(var(--border) / 70%);
|
||
}
|
||
.pap-product.is-chinese {
|
||
width: auto;
|
||
max-width: 100%;
|
||
padding: 2px 8px;
|
||
background: hsl(var(--muted) / 0.28);
|
||
}
|
||
.pap-product-chip {
|
||
font-size: 12px;
|
||
line-height: 1.3;
|
||
color: hsl(var(--foreground));
|
||
white-space: nowrap;
|
||
}
|
||
.pap-product-img {
|
||
width: 28px;
|
||
height: 28px;
|
||
object-fit: cover;
|
||
border-radius: 6px;
|
||
border: 1px solid hsl(var(--border));
|
||
background: hsl(var(--muted) / 0.35);
|
||
flex-shrink: 0;
|
||
}
|
||
.pap-product-img.is-empty {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-size: 10px;
|
||
color: hsl(var(--muted-foreground));
|
||
}
|
||
.pap-product-meta {
|
||
display: flex;
|
||
flex: 1;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
gap: 4px;
|
||
min-width: 0;
|
||
}
|
||
.pap-product-meta strong {
|
||
font-size: 12px;
|
||
font-weight: 600;
|
||
line-height: 1.2;
|
||
color: hsl(var(--foreground));
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
.pap-product-meta span {
|
||
flex-shrink: 0;
|
||
font-size: 11px;
|
||
color: hsl(var(--muted-foreground));
|
||
}
|
||
.pap-order-empty-products {
|
||
font-size: 12px;
|
||
color: hsl(var(--muted-foreground));
|
||
}
|
||
@media (max-width: 720px) {
|
||
.pap-orders-stats {
|
||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||
}
|
||
.pap-orders-head {
|
||
flex-direction: column;
|
||
}
|
||
.pap-orders-actions {
|
||
width: 100%;
|
||
}
|
||
.pap-product:not(.is-chinese) {
|
||
width: calc(50% - 3px);
|
||
}
|
||
}
|
||
</style>
|