|
|
<template>
|
|
|
<div class="budget-container">
|
|
|
<!-- 页面头部 -->
|
|
|
<div class="page-header">
|
|
|
<h1 class="page-title">
|
|
|
<el-icon :size="24"><DataAnalysis /></el-icon>
|
|
|
预算查询
|
|
|
</h1>
|
|
|
</div>
|
|
|
|
|
|
<!-- 筛选区域 -->
|
|
|
<el-card class="filter-section" shadow="never">
|
|
|
<el-form :model="filterForm" inline>
|
|
|
<el-form-item label="预算年度">
|
|
|
<el-select v-model="filterForm.year_id" style="width: 180px" placeholder="请选择预算年度" @change="handleYearChange">
|
|
|
<el-option
|
|
|
v-for="item in yearOptions"
|
|
|
:key="item.value"
|
|
|
:label="item.label"
|
|
|
:value="item.value"
|
|
|
/>
|
|
|
</el-select>
|
|
|
</el-form-item>
|
|
|
</el-form>
|
|
|
</el-card>
|
|
|
|
|
|
<!-- 汇总统计 -->
|
|
|
<div class="summary-section">
|
|
|
<!-- 总预算(部门预算 + 专项资金) -->
|
|
|
<el-card class="summary-row-card summary-row-card-total" shadow="never">
|
|
|
<div class="summary-row">
|
|
|
<div class="summary-row-items">
|
|
|
<div class="summary-row-item">
|
|
|
<div class="summary-row-label">总预算</div>
|
|
|
<div class="summary-row-value">¥{{ formatNumber(summary.totalBudget) }}</div>
|
|
|
</div>
|
|
|
<div class="summary-row-item">
|
|
|
<div class="summary-row-label">已使用</div>
|
|
|
<div class="summary-row-value">¥{{ formatNumber(summary.departmentExecuted + summary.specialFundUsed) }}</div>
|
|
|
</div>
|
|
|
<div class="summary-row-item">
|
|
|
<div class="summary-row-label">待使用</div>
|
|
|
<div class="summary-row-value">¥{{ formatNumber(summary.totalBudget - (summary.departmentExecuted + summary.specialFundUsed)) }}</div>
|
|
|
</div>
|
|
|
<div class="summary-row-item">
|
|
|
<div class="summary-row-label">执行率</div>
|
|
|
<div class="summary-row-value" :style="{ color: getSummaryRateColor(totalExecutionRate) }">
|
|
|
{{ totalExecutionRate }}%
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</el-card>
|
|
|
|
|
|
<!-- 部门预算 -->
|
|
|
<el-card class="summary-row-card" shadow="never">
|
|
|
<div class="summary-row">
|
|
|
<div class="summary-row-items">
|
|
|
<div class="summary-row-item">
|
|
|
<div class="summary-row-label">部门预算</div>
|
|
|
<div class="summary-row-value">¥{{ formatNumber(summary.departmentBudget) }}</div>
|
|
|
</div>
|
|
|
<div class="summary-row-item">
|
|
|
<div class="summary-row-label">已使用</div>
|
|
|
<div class="summary-row-value">¥{{ formatNumber(summary.departmentExecuted) }}</div>
|
|
|
</div>
|
|
|
<div class="summary-row-item">
|
|
|
<div class="summary-row-label">待使用</div>
|
|
|
<div class="summary-row-value">¥{{ formatNumber(summary.departmentBudget - summary.departmentExecuted) }}</div>
|
|
|
</div>
|
|
|
<div class="summary-row-item">
|
|
|
<div class="summary-row-label">执行率</div>
|
|
|
<div class="summary-row-value" :style="{ color: getSummaryRateColor(departmentExecutionRate) }">
|
|
|
{{ departmentExecutionRate }}%
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</el-card>
|
|
|
|
|
|
<!-- 专项资金 -->
|
|
|
<el-card class="summary-row-card" shadow="never">
|
|
|
<div class="summary-row">
|
|
|
<div class="summary-row-items">
|
|
|
<div class="summary-row-item">
|
|
|
<div class="summary-row-label">专项资金</div>
|
|
|
<div class="summary-row-value">¥{{ formatNumber(summary.specialFundBudget) }}</div>
|
|
|
</div>
|
|
|
<div class="summary-row-item">
|
|
|
<div class="summary-row-label">已使用</div>
|
|
|
<div class="summary-row-value">¥{{ formatNumber(summary.specialFundUsed) }}</div>
|
|
|
</div>
|
|
|
<div class="summary-row-item">
|
|
|
<div class="summary-row-label">待使用</div>
|
|
|
<div class="summary-row-value">¥{{ formatNumber(summary.specialFundBudget - summary.specialFundUsed) }}</div>
|
|
|
</div>
|
|
|
<div class="summary-row-item">
|
|
|
<div class="summary-row-label">执行率</div>
|
|
|
<div class="summary-row-value" :style="{ color: getSummaryRateColor(specialFundExecutionRate) }">
|
|
|
{{ specialFundExecutionRate }}%
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</el-card>
|
|
|
</div>
|
|
|
|
|
|
<!-- 主要内容 -->
|
|
|
<div class="main-content">
|
|
|
<div class="budget-tree">
|
|
|
<template v-for="section in budgetSections" :key="section.key">
|
|
|
<!-- 上一年结转资金:统计通栏(放在自有账户之前) -->
|
|
|
<el-card v-if="section.key === 'last_year_carryover'" class="summary-row-card summary-row-card-carryover" shadow="never">
|
|
|
<div class="summary-row">
|
|
|
<div class="summary-row-items">
|
|
|
<div class="summary-row-item">
|
|
|
<div class="summary-row-label">上一年结转资金</div>
|
|
|
<div class="summary-row-value">¥{{ formatNumber(summary.lastYearCarryoverBudget) }}</div>
|
|
|
</div>
|
|
|
<div class="summary-row-item">
|
|
|
<div class="summary-row-label">已使用</div>
|
|
|
<div class="summary-row-value">¥{{ formatNumber(summary.lastYearCarryoverUsed) }}</div>
|
|
|
</div>
|
|
|
<div class="summary-row-item">
|
|
|
<div class="summary-row-label">待使用</div>
|
|
|
<div class="summary-row-value">¥{{ formatNumber(summary.lastYearCarryoverBudget - summary.lastYearCarryoverUsed) }}</div>
|
|
|
</div>
|
|
|
<div class="summary-row-item">
|
|
|
<div class="summary-row-label">执行率</div>
|
|
|
<div class="summary-row-value" :style="{ color: getSummaryRateColor(lastYearCarryoverExecutionRate) }">
|
|
|
{{ lastYearCarryoverExecutionRate }}%
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</el-card>
|
|
|
|
|
|
<!-- 自有账户统计信息 -->
|
|
|
<el-card v-if="section.key === 'project'" class="project-summary-card project-summary-card-green" shadow="never">
|
|
|
<div class="project-summary">
|
|
|
<div class="project-summary-item">
|
|
|
<div class="project-summary-label">自有账户</div>
|
|
|
<div class="project-summary-value">¥{{ formatNumber(summary.projectBudget) }}</div>
|
|
|
</div>
|
|
|
<div class="project-summary-item">
|
|
|
<div class="project-summary-label">已使用</div>
|
|
|
<div class="project-summary-value">¥{{ formatNumber(summary.projectUsed) }}</div>
|
|
|
</div>
|
|
|
<div class="project-summary-item">
|
|
|
<div class="project-summary-label">剩余</div>
|
|
|
<div class="project-summary-value" style="color: #10b981;">
|
|
|
¥{{ formatNumber(summary.projectBudget - summary.projectUsed) }}
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</el-card>
|
|
|
|
|
|
<!-- 抵消预付账款:统计通栏 -->
|
|
|
<el-card v-if="section.key === 'offset_prepaid'" class="summary-row-card summary-row-card-offset-prepaid" shadow="never">
|
|
|
<div class="summary-row">
|
|
|
<div class="summary-row-items">
|
|
|
<div class="summary-row-item">
|
|
|
<div class="summary-row-label">抵消预付账款</div>
|
|
|
<div class="summary-row-value">¥{{ formatNumber(summary.offsetPrepaidBudget) }}</div>
|
|
|
</div>
|
|
|
<div class="summary-row-item">
|
|
|
<div class="summary-row-label">已使用</div>
|
|
|
<div class="summary-row-value">¥{{ formatNumber(summary.offsetPrepaidUsed) }}</div>
|
|
|
</div>
|
|
|
<div class="summary-row-item">
|
|
|
<div class="summary-row-label">待使用</div>
|
|
|
<div class="summary-row-value">¥{{ formatNumber(summary.offsetPrepaidBudget - summary.offsetPrepaidUsed) }}</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</el-card>
|
|
|
|
|
|
<!-- 工会:统计通栏(排在现有大类最后) -->
|
|
|
<el-card v-if="section.key === 'trade_union'" class="summary-row-card summary-row-card-trade-union" shadow="never">
|
|
|
<div class="summary-row">
|
|
|
<div class="summary-row-items">
|
|
|
<div class="summary-row-item">
|
|
|
<div class="summary-row-label">工会</div>
|
|
|
<div class="summary-row-value">¥{{ formatNumber(summary.tradeUnionBudget) }}</div>
|
|
|
</div>
|
|
|
<div class="summary-row-item">
|
|
|
<div class="summary-row-label">已使用</div>
|
|
|
<div class="summary-row-value">¥{{ formatNumber(summary.tradeUnionUsed) }}</div>
|
|
|
</div>
|
|
|
<div class="summary-row-item">
|
|
|
<div class="summary-row-label">待使用</div>
|
|
|
<div class="summary-row-value">¥{{ formatNumber(summary.tradeUnionBudget - summary.tradeUnionUsed) }}</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</el-card>
|
|
|
|
|
|
<el-card
|
|
|
:class="['type-section-card', `type-section-card-${section.key}`]"
|
|
|
shadow="never"
|
|
|
>
|
|
|
<template #header>
|
|
|
<div class="type-header">
|
|
|
<div class="type-title">
|
|
|
<el-tag :type="section.tagType" size="small" effect="dark">{{ section.label }}</el-tag>
|
|
|
<span class="type-count">根节点:{{ section.data.length }}</span>
|
|
|
</div>
|
|
|
<div>
|
|
|
<el-button size="small" text @click="expandAll(section.key)">展开</el-button>
|
|
|
<el-button size="small" text @click="collapseAll(section.key)">收起</el-button>
|
|
|
</div>
|
|
|
</div>
|
|
|
</template>
|
|
|
<el-empty v-if="!section.data.length" description="暂无数据" />
|
|
|
<template v-else>
|
|
|
<div
|
|
|
v-for="level1 in section.data"
|
|
|
:key="level1.id"
|
|
|
class="budget-level-1"
|
|
|
>
|
|
|
<div
|
|
|
:class="['budget-level-1-header', `budget-level-1-header-${level1.budget_type || 'department'}`, { collapsed: !level1.expanded }]"
|
|
|
@click="toggleLevel1(level1)"
|
|
|
>
|
|
|
<div class="budget-level-1-title">
|
|
|
<el-icon><Folder /></el-icon>
|
|
|
<span>{{ level1.name }}</span>
|
|
|
<el-tag size="small" type="info" effect="plain">{{ budgetTypeLabel(level1.budget_type) }}</el-tag>
|
|
|
</div>
|
|
|
<div class="budget-level-1-info">
|
|
|
<div class="budget-info-item">
|
|
|
<span class="budget-info-label">总预算</span>
|
|
|
<span class="budget-info-value">¥{{ formatNumber(level1.budget_amount) }}</span>
|
|
|
</div>
|
|
|
<div class="budget-info-item">
|
|
|
<span class="budget-info-label">已使用</span>
|
|
|
<span class="budget-info-value">¥{{ formatNumber(level1.used_amount || 0) }}</span>
|
|
|
</div>
|
|
|
<div class="budget-info-item">
|
|
|
<span class="budget-info-label">使用率</span>
|
|
|
<span class="budget-info-value" :style="{ color: getUsageRateColor(level1) }">
|
|
|
{{ getUsageRate(level1) }}%
|
|
|
</span>
|
|
|
</div>
|
|
|
<div class="budget-info-item">
|
|
|
<span class="budget-info-label">剩余</span>
|
|
|
<span class="budget-info-value" style="color: #10b981;">
|
|
|
¥{{ formatNumber((level1.budget_amount || 0) - (level1.used_amount || 0)) }}
|
|
|
</span>
|
|
|
</div>
|
|
|
<el-button
|
|
|
v-if="level1.is_leaf"
|
|
|
size="small"
|
|
|
type="primary"
|
|
|
link
|
|
|
class="level-1-detail-button"
|
|
|
@click.stop="viewDetail(level1.id)"
|
|
|
>
|
|
|
<el-icon><View /></el-icon>
|
|
|
查看
|
|
|
</el-button>
|
|
|
<el-icon v-else :size="20" style="margin-left: 15px;">
|
|
|
<ArrowDown v-if="level1.expanded" /><ArrowRight v-else />
|
|
|
</el-icon>
|
|
|
</div>
|
|
|
</div>
|
|
|
<el-collapse-transition>
|
|
|
<div v-show="level1.expanded" class="budget-level-1-content">
|
|
|
<div
|
|
|
v-for="level2 in level1.children"
|
|
|
:key="level2.id"
|
|
|
class="budget-level-2"
|
|
|
>
|
|
|
<div class="budget-level-2-name">
|
|
|
<el-icon><Document /></el-icon>
|
|
|
{{ level2.name }}
|
|
|
</div>
|
|
|
<div class="budget-amount">¥{{ formatNumber(level2.budget_amount) }}</div>
|
|
|
<div class="budget-used">¥{{ formatNumber(level2.used_amount || 0) }}</div>
|
|
|
<div class="budget-remaining positive">
|
|
|
¥{{ formatNumber((level2.budget_amount || 0) - (level2.used_amount || 0)) }}
|
|
|
</div>
|
|
|
<div class="budget-progress">
|
|
|
<el-progress
|
|
|
:percentage="level2.execution_rate || 0"
|
|
|
:stroke-width="8"
|
|
|
:color="progressColor(level2.execution_rate || 0)"
|
|
|
:show-text="false"
|
|
|
/>
|
|
|
<span class="progress-text">{{ level2.execution_rate || 0 }}%</span>
|
|
|
</div>
|
|
|
<div>
|
|
|
<el-button size="small" type="primary" link @click="viewDetail(level2.id)">
|
|
|
<el-icon><View /></el-icon>
|
|
|
查看
|
|
|
</el-button>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</el-collapse-transition>
|
|
|
</div>
|
|
|
</template>
|
|
|
</el-card>
|
|
|
</template>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<!-- 执行明细抽屉 -->
|
|
|
<el-drawer
|
|
|
v-model="drawerVisible"
|
|
|
title="执行明细"
|
|
|
:size="600"
|
|
|
direction="rtl"
|
|
|
>
|
|
|
<template #header>
|
|
|
<div class="drawer-header">
|
|
|
<div class="drawer-title-row">
|
|
|
<h3>{{ currentBudgetName }}</h3>
|
|
|
<el-button
|
|
|
type="primary"
|
|
|
size="small"
|
|
|
:icon="Download"
|
|
|
:disabled="!executionDetails.executed.length && !executionDetails.pending.length"
|
|
|
@click="exportExecutionDetails"
|
|
|
>
|
|
|
导出明细
|
|
|
</el-button>
|
|
|
</div>
|
|
|
<div class="drawer-summary">
|
|
|
<div class="summary-item">
|
|
|
<span class="label">已执行:</span>
|
|
|
<span class="value executed">¥{{ formatNumber(executionDetails.executed_total || 0) }}</span>
|
|
|
</div>
|
|
|
<div class="summary-item">
|
|
|
<span class="label">待执行:</span>
|
|
|
<span class="value pending">¥{{ formatNumber(executionDetails.pending_total || 0) }}</span>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</template>
|
|
|
|
|
|
<el-tabs v-model="activeTab" class="execution-tabs">
|
|
|
<el-tab-pane label="已执行" name="executed">
|
|
|
<el-table :data="executionDetails.executed" style="width: 100%">
|
|
|
<el-table-column prop="payment_serial_number" label="支付单号" width="180" />
|
|
|
<el-table-column prop="payment_date" label="支付日期" width="120" />
|
|
|
<el-table-column prop="amount" label="金额" width="120" align="right">
|
|
|
<template #default="scope">
|
|
|
<span class="amount executed">¥{{ formatNumber(scope.row.amount) }}</span>
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
<el-table-column label="操作" width="100" fixed="right">
|
|
|
<template #default="scope">
|
|
|
<el-button type="primary" link size="small" @click="viewPaymentDetail(scope.row)">
|
|
|
详情
|
|
|
</el-button>
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
</el-table>
|
|
|
</el-tab-pane>
|
|
|
<el-tab-pane label="待执行" name="pending">
|
|
|
<el-table :data="executionDetails.pending" style="width: 100%">
|
|
|
<el-table-column prop="payment_serial_number" label="支付单号" width="180" />
|
|
|
<el-table-column prop="payment_date" label="支付日期" width="120" />
|
|
|
<el-table-column label="状态" width="100">
|
|
|
<template #default="scope">
|
|
|
<el-tag :type="getStatusTagType(scope.row.payment_status)" size="small">
|
|
|
{{ scope.row.payment_status_text }}
|
|
|
</el-tag>
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
<el-table-column prop="payee" label="收款单位" min-width="150" />
|
|
|
<el-table-column prop="payment_item" label="支付事项" min-width="200" />
|
|
|
<el-table-column prop="amount" label="金额" width="120" align="right">
|
|
|
<template #default="scope">
|
|
|
<span class="amount pending">¥{{ formatNumber(scope.row.amount) }}</span>
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
<el-table-column label="操作" width="100" fixed="right">
|
|
|
<template #default="scope">
|
|
|
<el-button type="primary" link size="small" @click="viewPaymentDetail(scope.row)">
|
|
|
详情
|
|
|
</el-button>
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
</el-table>
|
|
|
</el-tab-pane>
|
|
|
</el-tabs>
|
|
|
</el-drawer>
|
|
|
|
|
|
<!-- 支付明细详情弹窗(A4格式) -->
|
|
|
<el-dialog
|
|
|
v-model="detailDialogVisible"
|
|
|
title="支付明细详情"
|
|
|
width="210mm"
|
|
|
class="payment-detail-dialog"
|
|
|
:close-on-click-modal="false"
|
|
|
>
|
|
|
<div v-if="paymentDetailInfo" class="a4-container">
|
|
|
<div class="a4-page">
|
|
|
<div class="detail-header-section">
|
|
|
<h2 class="main-title">支付明细信息链</h2>
|
|
|
<div class="detail-basic-info">
|
|
|
<div class="info-row">
|
|
|
<span class="label">支付明细ID:</span>
|
|
|
<span class="value">{{ paymentDetailInfo.id }}</span>
|
|
|
</div>
|
|
|
<div class="info-row">
|
|
|
<span class="label">支付金额:</span>
|
|
|
<span class="value amount">¥{{ formatNumber(paymentDetailInfo.amount) }}</span>
|
|
|
</div>
|
|
|
<div v-if="paymentDetailInfo.description" class="info-row">
|
|
|
<span class="label">说明:</span>
|
|
|
<span class="value">{{ paymentDetailInfo.description }}</span>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<!-- 支付流程信息 -->
|
|
|
<div v-if="paymentDetailInfo.payment_info" class="info-section">
|
|
|
<h3 class="section-title">支付流程信息</h3>
|
|
|
<div class="info-grid">
|
|
|
<div class="info-item">
|
|
|
<span class="label">支付单号:</span>
|
|
|
<span class="value">{{ paymentDetailInfo.payment_info.serial_number }}</span>
|
|
|
</div>
|
|
|
<div class="info-item">
|
|
|
<span class="label">状态:</span>
|
|
|
<el-tag :type="paymentDetailInfo.payment_info.status_color" size="small">
|
|
|
{{ paymentDetailInfo.payment_info.status_text }}
|
|
|
</el-tag>
|
|
|
</div>
|
|
|
<div class="info-item">
|
|
|
<span class="label">支付日期:</span>
|
|
|
<span class="value">{{ paymentDetailInfo.payment_info.payment_date || '-' }}</span>
|
|
|
</div>
|
|
|
<div class="info-item">
|
|
|
<span class="label">总金额:</span>
|
|
|
<span class="value">¥{{ formatNumber(paymentDetailInfo.payment_info.total_amount) }}</span>
|
|
|
</div>
|
|
|
<div v-if="paymentDetailInfo.payment_info.payment_category" class="info-item full-width">
|
|
|
<span class="label">支付分类:</span>
|
|
|
<span class="value">{{ paymentDetailInfo.payment_info.payment_category.breadcrumb.join(' > ') }}</span>
|
|
|
</div>
|
|
|
<div v-if="paymentDetailInfo.payment_info.oa_flow" class="info-item">
|
|
|
<span class="label">OA流程:</span>
|
|
|
<span class="value">{{ paymentDetailInfo.payment_info.oa_flow.title }}({{ paymentDetailInfo.payment_info.oa_flow.no }})</span>
|
|
|
</div>
|
|
|
<div v-if="paymentDetailInfo.payment_info.description" class="info-item full-width">
|
|
|
<span class="label">描述:</span>
|
|
|
<span class="value">{{ paymentDetailInfo.payment_info.description }}</span>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<!-- 支付流程自定义字段 -->
|
|
|
<div v-if="paymentDetailInfo.payment_info.element_values && paymentDetailInfo.payment_info.element_values.length > 0" class="custom-fields">
|
|
|
<h4 class="subsection-title">自定义字段</h4>
|
|
|
<div class="info-grid">
|
|
|
<div v-for="field in paymentDetailInfo.payment_info.element_values" :key="field.element_id" class="info-item">
|
|
|
<span class="label">{{ field.display_name || field.field_key }}:</span>
|
|
|
<span class="value">{{ formatFieldValue(field) }}</span>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<!-- 事前支出信息(非直接支付) -->
|
|
|
<div v-if="paymentDetailInfo.expenditure_info" class="info-section">
|
|
|
<h3 class="section-title">事前支出信息</h3>
|
|
|
<div class="info-grid">
|
|
|
<div class="info-item">
|
|
|
<span class="label">账单编号:</span>
|
|
|
<span class="value">{{ paymentDetailInfo.expenditure_info.serial_number }}</span>
|
|
|
</div>
|
|
|
<div class="info-item">
|
|
|
<span class="label">账单标题:</span>
|
|
|
<span class="value">{{ paymentDetailInfo.expenditure_info.title }}</span>
|
|
|
</div>
|
|
|
<div class="info-item">
|
|
|
<span class="label">账单金额:</span>
|
|
|
<span class="value">¥{{ formatNumber(paymentDetailInfo.expenditure_info.amount) }}</span>
|
|
|
</div>
|
|
|
<div v-if="paymentDetailInfo.expenditure_info.description" class="info-item full-width">
|
|
|
<span class="label">描述:</span>
|
|
|
<span class="value">{{ paymentDetailInfo.expenditure_info.description }}</span>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<!-- 事前支出自定义字段 -->
|
|
|
<div v-if="paymentDetailInfo.expenditure_info.element_values && paymentDetailInfo.expenditure_info.element_values.length > 0" class="custom-fields">
|
|
|
<h4 class="subsection-title">自定义字段</h4>
|
|
|
<div class="info-grid">
|
|
|
<div v-for="field in paymentDetailInfo.expenditure_info.element_values" :key="field.element_id" class="info-item">
|
|
|
<span class="label">{{ field.display_name || field.field_key }}:</span>
|
|
|
<span class="value">{{ formatFieldValue(field) }}</span>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<!-- 预算来源信息 -->
|
|
|
<div v-if="paymentDetailInfo.budget_info" class="info-section">
|
|
|
<h3 class="section-title">预算来源信息</h3>
|
|
|
<div class="info-grid">
|
|
|
<div class="info-item">
|
|
|
<span class="label">预算年度:</span>
|
|
|
<span class="value">{{ paymentDetailInfo.budget_info.year }}</span>
|
|
|
</div>
|
|
|
<div class="info-item">
|
|
|
<span class="label">预算类型:</span>
|
|
|
<span class="value">{{ paymentDetailInfo.budget_info.budget_type_text }}</span>
|
|
|
</div>
|
|
|
<div class="info-item">
|
|
|
<span class="label">预算金额:</span>
|
|
|
<span class="value">¥{{ formatNumber(paymentDetailInfo.budget_info.budget_amount) }}</span>
|
|
|
</div>
|
|
|
<div class="info-item full-width">
|
|
|
<span class="label">预算路径:</span>
|
|
|
<span class="value">{{ paymentDetailInfo.budget_info.path }}</span>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<!-- 时间信息 -->
|
|
|
<div class="info-section">
|
|
|
<h3 class="section-title">时间信息</h3>
|
|
|
<div class="info-grid">
|
|
|
<div class="info-item">
|
|
|
<span class="label">创建时间:</span>
|
|
|
<span class="value">{{ paymentDetailInfo.created_at || '-' }}</span>
|
|
|
</div>
|
|
|
<div class="info-item">
|
|
|
<span class="label">更新时间:</span>
|
|
|
<span class="value">{{ paymentDetailInfo.updated_at || '-' }}</span>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
<template #footer>
|
|
|
<el-button @click="detailDialogVisible = false">关闭</el-button>
|
|
|
<el-button type="primary" @click="printDetail">打印</el-button>
|
|
|
</template>
|
|
|
</el-dialog>
|
|
|
</div>
|
|
|
</template>
|
|
|
|
|
|
<script setup>
|
|
|
import { ref, computed, onMounted } from 'vue'
|
|
|
import { useRouter } from 'vue-router'
|
|
|
import { ElMessage } from 'element-plus'
|
|
|
import * as XLSX from 'xlsx'
|
|
|
import {
|
|
|
DataAnalysis,
|
|
|
Search,
|
|
|
Refresh,
|
|
|
List,
|
|
|
FullScreen,
|
|
|
Minus,
|
|
|
Folder,
|
|
|
Document,
|
|
|
View,
|
|
|
Download,
|
|
|
ArrowDown,
|
|
|
ArrowRight
|
|
|
} from '@element-plus/icons-vue'
|
|
|
import { budgetDataAPI, paymentDetailAPI } from '@/utils/api'
|
|
|
|
|
|
const router = useRouter()
|
|
|
|
|
|
const filterForm = ref({
|
|
|
year_id: ''
|
|
|
})
|
|
|
|
|
|
const yearOptions = ref([])
|
|
|
const summary = ref({
|
|
|
totalBudget: 0, // 总预算(部门预算 + 专项资金)
|
|
|
departmentBudget: 0,
|
|
|
specialFundBudget: 0,
|
|
|
lastYearCarryoverBudget: 0, // 上一年结转资金
|
|
|
projectBudget: 0, // 自有账户
|
|
|
offsetPrepaidBudget: 0, // 抵消预付账款
|
|
|
tradeUnionBudget: 0, // 工会
|
|
|
departmentExecuted: 0,
|
|
|
specialFundUsed: 0,
|
|
|
lastYearCarryoverUsed: 0,
|
|
|
projectUsed: 0,
|
|
|
offsetPrepaidUsed: 0,
|
|
|
tradeUnionUsed: 0
|
|
|
})
|
|
|
const budgetTree = ref([])
|
|
|
const loading = ref(false)
|
|
|
|
|
|
// 执行明细抽屉相关
|
|
|
const drawerVisible = ref(false)
|
|
|
const activeTab = ref('executed')
|
|
|
const currentBudgetName = ref('')
|
|
|
const currentBudgetId = ref(null)
|
|
|
const executionDetails = ref({
|
|
|
executed: [],
|
|
|
pending: [],
|
|
|
executed_total: 0,
|
|
|
pending_total: 0
|
|
|
})
|
|
|
|
|
|
// 计算总预算执行率(部门预算 + 专项资金)
|
|
|
const totalExecutionRate = computed(() => {
|
|
|
if (summary.value.totalBudget === 0) return '0.00'
|
|
|
const totalUsed = summary.value.departmentExecuted + summary.value.specialFundUsed
|
|
|
return ((totalUsed / summary.value.totalBudget) * 100).toFixed(2)
|
|
|
})
|
|
|
|
|
|
// 计算部门预算执行率
|
|
|
const departmentExecutionRate = computed(() => {
|
|
|
if (summary.value.departmentBudget === 0) return '0.00'
|
|
|
return ((summary.value.departmentExecuted / summary.value.departmentBudget) * 100).toFixed(2)
|
|
|
})
|
|
|
|
|
|
// 计算专项资金执行率
|
|
|
const specialFundExecutionRate = computed(() => {
|
|
|
if (summary.value.specialFundBudget === 0) return '0.00'
|
|
|
return ((summary.value.specialFundUsed / summary.value.specialFundBudget) * 100).toFixed(2)
|
|
|
})
|
|
|
|
|
|
// 计算上一年结转资金执行率
|
|
|
const lastYearCarryoverExecutionRate = computed(() => {
|
|
|
if (summary.value.lastYearCarryoverBudget === 0) return '0.00'
|
|
|
return ((summary.value.lastYearCarryoverUsed / summary.value.lastYearCarryoverBudget) * 100).toFixed(2)
|
|
|
})
|
|
|
|
|
|
// 计算自有账户使用率
|
|
|
const projectUsageRate = computed(() => {
|
|
|
if (summary.value.projectBudget === 0) return '0.00'
|
|
|
return ((summary.value.projectUsed / summary.value.projectBudget) * 100).toFixed(2)
|
|
|
})
|
|
|
|
|
|
// 计算抵消预付账款执行率
|
|
|
const offsetPrepaidExecutionRate = computed(() => {
|
|
|
if (summary.value.offsetPrepaidBudget === 0) return '0.00'
|
|
|
return ((summary.value.offsetPrepaidUsed / summary.value.offsetPrepaidBudget) * 100).toFixed(2)
|
|
|
})
|
|
|
|
|
|
// 计算工会执行率
|
|
|
const tradeUnionExecutionRate = computed(() => {
|
|
|
if (summary.value.tradeUnionBudget === 0) return '0.00'
|
|
|
return ((summary.value.tradeUnionUsed / summary.value.tradeUnionBudget) * 100).toFixed(2)
|
|
|
})
|
|
|
|
|
|
// 根据执行率返回颜色
|
|
|
const getSummaryRateColor = (rate) => {
|
|
|
const numRate = Number(rate)
|
|
|
if (numRate >= 80) return '#67c23a'
|
|
|
if (numRate >= 60) return '#409eff'
|
|
|
if (numRate >= 40) return '#e6a23c'
|
|
|
return '#f56c6c'
|
|
|
}
|
|
|
|
|
|
const formatNumber = (num) => {
|
|
|
const n = Number(num || 0)
|
|
|
return n.toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })
|
|
|
}
|
|
|
|
|
|
// 数字转中文大写金额
|
|
|
const numberToChinese = (num) => {
|
|
|
const n = Number(num || 0)
|
|
|
if (n === 0) return '零元整'
|
|
|
|
|
|
const digits = ['零', '壹', '贰', '叁', '肆', '伍', '陆', '柒', '捌', '玖']
|
|
|
const units = ['', '拾', '佰', '仟']
|
|
|
|
|
|
// 分离整数和小数部分
|
|
|
const parts = n.toFixed(2).split('.')
|
|
|
const integerPart = parseInt(parts[0])
|
|
|
const decimalPart = parts[1]
|
|
|
|
|
|
let result = ''
|
|
|
|
|
|
// 处理整数部分
|
|
|
if (integerPart > 0) {
|
|
|
// 处理亿位
|
|
|
if (integerPart >= 100000000) {
|
|
|
const yi = Math.floor(integerPart / 100000000)
|
|
|
result += convertGroup(yi, digits, units) + '亿'
|
|
|
const remaining = integerPart % 100000000
|
|
|
if (remaining > 0) {
|
|
|
if (remaining < 10000000) result += '零'
|
|
|
result += convertInteger(remaining, digits, units)
|
|
|
}
|
|
|
} else {
|
|
|
result += convertInteger(integerPart, digits, units)
|
|
|
}
|
|
|
result += '元'
|
|
|
}
|
|
|
|
|
|
// 处理小数部分
|
|
|
const jiao = parseInt(decimalPart[0])
|
|
|
const fen = parseInt(decimalPart[1])
|
|
|
|
|
|
if (jiao === 0 && fen === 0) {
|
|
|
result += '整'
|
|
|
} else {
|
|
|
if (jiao > 0) {
|
|
|
result += digits[jiao] + '角'
|
|
|
}
|
|
|
if (fen > 0) {
|
|
|
result += digits[fen] + '分'
|
|
|
} else if (jiao > 0) {
|
|
|
result += '整'
|
|
|
}
|
|
|
}
|
|
|
|
|
|
return result
|
|
|
}
|
|
|
|
|
|
// 转换整数部分(处理万位和个位)
|
|
|
const convertInteger = (num, digits, units) => {
|
|
|
if (num === 0) return ''
|
|
|
|
|
|
let result = ''
|
|
|
|
|
|
if (num >= 10000) {
|
|
|
const wan = Math.floor(num / 10000)
|
|
|
result += convertGroup(wan, digits, units) + '万'
|
|
|
const ge = num % 10000
|
|
|
if (ge > 0) {
|
|
|
if (ge < 1000) result += '零'
|
|
|
result += convertGroup(ge, digits, units)
|
|
|
}
|
|
|
} else {
|
|
|
result += convertGroup(num, digits, units)
|
|
|
}
|
|
|
|
|
|
return result
|
|
|
}
|
|
|
|
|
|
// 转换四位数为一组(个、十、百、千)
|
|
|
const convertGroup = (num, digits, units) => {
|
|
|
if (num === 0) return ''
|
|
|
|
|
|
let result = ''
|
|
|
const numStr = num.toString()
|
|
|
const len = numStr.length
|
|
|
|
|
|
for (let i = 0; i < len; i++) {
|
|
|
const digit = parseInt(numStr[i])
|
|
|
const pos = len - i - 1
|
|
|
|
|
|
if (digit !== 0) {
|
|
|
// 处理"壹拾"的情况,通常简化为"拾"
|
|
|
if (digit === 1 && pos === 1 && len === 2) {
|
|
|
result += units[pos]
|
|
|
} else {
|
|
|
result += digits[digit] + units[pos]
|
|
|
}
|
|
|
} else if (result && result[result.length - 1] !== '零' && i < len - 1) {
|
|
|
// 添加零,但要检查后面是否有非零数字
|
|
|
const nextNonZero = numStr.substring(i + 1).split('').some(d => parseInt(d) !== 0)
|
|
|
if (nextNonZero) {
|
|
|
result += '零'
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
return result
|
|
|
}
|
|
|
|
|
|
// 进度色彩映射
|
|
|
const progressColor = (rate) => {
|
|
|
if (rate >= 80) return '#67c23a'
|
|
|
if (rate >= 60) return '#409eff'
|
|
|
if (rate >= 40) return '#e6a23c'
|
|
|
return '#f56c6c'
|
|
|
}
|
|
|
|
|
|
// 计算使用率
|
|
|
const getUsageRate = (item) => {
|
|
|
const budgetAmount = Number(item.budget_amount || 0)
|
|
|
const usedAmount = Number(item.used_amount || 0)
|
|
|
if (budgetAmount === 0) return '0.00'
|
|
|
const rate = (usedAmount / budgetAmount) * 100
|
|
|
return rate.toFixed(2)
|
|
|
}
|
|
|
|
|
|
// 根据使用率返回颜色
|
|
|
const getUsageRateColor = (item) => {
|
|
|
const rate = Number(getUsageRate(item))
|
|
|
if (rate >= 80) return '#67c23a'
|
|
|
if (rate >= 60) return '#409eff'
|
|
|
if (rate >= 40) return '#e6a23c'
|
|
|
return '#f56c6c'
|
|
|
}
|
|
|
|
|
|
const budgetTypeLabel = (type) => {
|
|
|
const map = {
|
|
|
department: '部门预算',
|
|
|
project: '自有账户',
|
|
|
special_fund: '专项资金',
|
|
|
last_year_carryover: '上一年结转资金',
|
|
|
offset_prepaid: '抵消预付账款',
|
|
|
trade_union: '工会'
|
|
|
}
|
|
|
return map[type] || '未指定'
|
|
|
}
|
|
|
|
|
|
const toggleLevel1 = (item) => {
|
|
|
item.expanded = !item.expanded
|
|
|
}
|
|
|
|
|
|
const departmentBudget = computed(() =>
|
|
|
(budgetTree.value || []).filter(item => (item.budget_type || 'department') === 'department')
|
|
|
)
|
|
|
const projectBudget = computed(() =>
|
|
|
(budgetTree.value || []).filter(item => (item.budget_type || 'department') === 'project')
|
|
|
)
|
|
|
const specialFundBudget = computed(() =>
|
|
|
(budgetTree.value || []).filter(item => (item.budget_type || 'department') === 'special_fund')
|
|
|
)
|
|
|
const lastYearCarryoverBudget = computed(() =>
|
|
|
(budgetTree.value || []).filter(item => (item.budget_type || 'department') === 'last_year_carryover')
|
|
|
)
|
|
|
const offsetPrepaidBudget = computed(() =>
|
|
|
(budgetTree.value || []).filter(item => (item.budget_type || 'department') === 'offset_prepaid')
|
|
|
)
|
|
|
const tradeUnionBudget = computed(() =>
|
|
|
(budgetTree.value || []).filter(item => (item.budget_type || 'department') === 'trade_union')
|
|
|
)
|
|
|
|
|
|
const budgetSections = computed(() => [
|
|
|
{ key: 'department', label: '部门预算', data: departmentBudget.value, tagType: 'success' },
|
|
|
{ key: 'special_fund', label: '专项资金', data: specialFundBudget.value, tagType: 'warning' },
|
|
|
{ key: 'last_year_carryover', label: '上一年结转资金', data: lastYearCarryoverBudget.value, tagType: 'danger' },
|
|
|
{ key: 'project', label: '自有账户', data: projectBudget.value, tagType: 'info' },
|
|
|
{ key: 'offset_prepaid', label: '抵消预付账款', data: offsetPrepaidBudget.value, tagType: 'primary' },
|
|
|
{ key: 'trade_union', label: '工会', data: tradeUnionBudget.value, tagType: 'info' }
|
|
|
])
|
|
|
|
|
|
const expandAll = (typeKey = null) => {
|
|
|
let list
|
|
|
if (typeKey) {
|
|
|
if (typeKey === 'department') {
|
|
|
list = departmentBudget.value
|
|
|
} else if (typeKey === 'project') {
|
|
|
list = projectBudget.value
|
|
|
} else if (typeKey === 'special_fund') {
|
|
|
list = specialFundBudget.value
|
|
|
} else if (typeKey === 'last_year_carryover') {
|
|
|
list = lastYearCarryoverBudget.value
|
|
|
} else if (typeKey === 'offset_prepaid') {
|
|
|
list = offsetPrepaidBudget.value
|
|
|
} else if (typeKey === 'trade_union') {
|
|
|
list = tradeUnionBudget.value
|
|
|
} else {
|
|
|
list = budgetTree.value
|
|
|
}
|
|
|
} else {
|
|
|
list = budgetTree.value
|
|
|
}
|
|
|
list.forEach(item => {
|
|
|
item.expanded = true
|
|
|
})
|
|
|
}
|
|
|
|
|
|
const collapseAll = (typeKey = null) => {
|
|
|
let list
|
|
|
if (typeKey) {
|
|
|
if (typeKey === 'department') {
|
|
|
list = departmentBudget.value
|
|
|
} else if (typeKey === 'project') {
|
|
|
list = projectBudget.value
|
|
|
} else if (typeKey === 'special_fund') {
|
|
|
list = specialFundBudget.value
|
|
|
} else if (typeKey === 'last_year_carryover') {
|
|
|
list = lastYearCarryoverBudget.value
|
|
|
} else if (typeKey === 'offset_prepaid') {
|
|
|
list = offsetPrepaidBudget.value
|
|
|
} else if (typeKey === 'trade_union') {
|
|
|
list = tradeUnionBudget.value
|
|
|
} else {
|
|
|
list = budgetTree.value
|
|
|
}
|
|
|
} else {
|
|
|
list = budgetTree.value
|
|
|
}
|
|
|
list.forEach(item => {
|
|
|
item.expanded = false
|
|
|
})
|
|
|
}
|
|
|
|
|
|
const handleYearChange = () => {
|
|
|
loadTreeData()
|
|
|
}
|
|
|
|
|
|
// 获取状态标签类型
|
|
|
const getStatusTagType = (status) => {
|
|
|
const typeMap = {
|
|
|
'draft': 'info',
|
|
|
'submitted': '',
|
|
|
'approved': 'success',
|
|
|
'rejected': 'danger',
|
|
|
'completed': 'success'
|
|
|
}
|
|
|
return typeMap[status] || ''
|
|
|
}
|
|
|
|
|
|
// 查看执行明细
|
|
|
const viewDetail = async (budgetId) => {
|
|
|
// 找到对应的预算节点获取名称
|
|
|
const findBudgetName = (nodes, id) => {
|
|
|
for (const node of nodes) {
|
|
|
if (node.id === id) {
|
|
|
return node.name
|
|
|
}
|
|
|
if (node.children && node.children.length > 0) {
|
|
|
const found = findBudgetName(node.children, id)
|
|
|
if (found) return found
|
|
|
}
|
|
|
}
|
|
|
return ''
|
|
|
}
|
|
|
|
|
|
currentBudgetId.value = budgetId
|
|
|
currentBudgetName.value = findBudgetName(budgetTree.value, budgetId) || '预算明细'
|
|
|
drawerVisible.value = true
|
|
|
activeTab.value = 'executed'
|
|
|
|
|
|
// 加载执行明细
|
|
|
await loadExecutionDetails(budgetId)
|
|
|
}
|
|
|
|
|
|
// 加载执行明细
|
|
|
const loadExecutionDetails = async (budgetId) => {
|
|
|
try {
|
|
|
const res = await budgetDataAPI.getExecutionDetails(budgetId)
|
|
|
if (res.code === 0) {
|
|
|
executionDetails.value = {
|
|
|
executed: res.data.executed || [],
|
|
|
pending: res.data.pending || [],
|
|
|
executed_total: res.data.executed_total || 0,
|
|
|
pending_total: res.data.pending_total || 0
|
|
|
}
|
|
|
} else {
|
|
|
ElMessage.error(res.msg || '加载执行明细失败')
|
|
|
executionDetails.value = {
|
|
|
executed: [],
|
|
|
pending: [],
|
|
|
executed_total: 0,
|
|
|
pending_total: 0
|
|
|
}
|
|
|
}
|
|
|
} catch (e) {
|
|
|
ElMessage.error('加载执行明细失败:' + e.message)
|
|
|
executionDetails.value = {
|
|
|
executed: [],
|
|
|
pending: [],
|
|
|
executed_total: 0,
|
|
|
pending_total: 0
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
const sanitizeFileName = (name) => {
|
|
|
return String(name || '预算明细')
|
|
|
.replace(/[\\/:*?"<>|]/g, '_')
|
|
|
.slice(0, 80)
|
|
|
}
|
|
|
|
|
|
const formatExportDateTime = (value) => {
|
|
|
if (!value) return ''
|
|
|
if (typeof value === 'string' && /^\d{4}-\d{2}-\d{2}( \d{2}:\d{2}:\d{2})?$/.test(value)) {
|
|
|
return value
|
|
|
}
|
|
|
|
|
|
const date = new Date(value)
|
|
|
if (Number.isNaN(date.getTime())) return String(value)
|
|
|
|
|
|
const pad = (num) => String(num).padStart(2, '0')
|
|
|
const year = date.getFullYear()
|
|
|
const month = pad(date.getMonth() + 1)
|
|
|
const day = pad(date.getDate())
|
|
|
const hours = pad(date.getHours())
|
|
|
const minutes = pad(date.getMinutes())
|
|
|
const seconds = pad(date.getSeconds())
|
|
|
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
|
|
|
}
|
|
|
|
|
|
const mapExecutionRows = (rows, statusText) => {
|
|
|
return (rows || []).map((row, index) => ({
|
|
|
序号: index + 1,
|
|
|
状态: statusText || row.payment_status_text || '',
|
|
|
支付单号: row.payment_serial_number || '',
|
|
|
支付日期: formatExportDateTime(row.payment_date),
|
|
|
收款单位: row.payee || '',
|
|
|
支付事项: row.payment_item || row.description || '',
|
|
|
金额: Number(row.amount || 0),
|
|
|
付款ID: row.payment_id || '',
|
|
|
分摊ID: row.allocation_id || '',
|
|
|
创建时间: formatExportDateTime(row.created_at)
|
|
|
}))
|
|
|
}
|
|
|
|
|
|
const exportExecutionDetails = () => {
|
|
|
const executedRows = executionDetails.value.executed || []
|
|
|
const pendingRows = executionDetails.value.pending || []
|
|
|
|
|
|
if (!executedRows.length && !pendingRows.length) {
|
|
|
ElMessage.warning('暂无可导出的明细')
|
|
|
return
|
|
|
}
|
|
|
|
|
|
const workbook = XLSX.utils.book_new()
|
|
|
const summaryRows = [
|
|
|
{ 项目: '预算节点', 数值: currentBudgetName.value || '' },
|
|
|
{ 项目: '已执行合计', 数值: Number(executionDetails.value.executed_total || 0) },
|
|
|
{ 项目: '待执行合计', 数值: Number(executionDetails.value.pending_total || 0) },
|
|
|
{ 项目: '导出时间', 数值: new Date().toLocaleString('zh-CN') }
|
|
|
]
|
|
|
|
|
|
XLSX.utils.book_append_sheet(workbook, XLSX.utils.json_to_sheet(summaryRows), '汇总')
|
|
|
XLSX.utils.book_append_sheet(workbook, XLSX.utils.json_to_sheet(mapExecutionRows(executedRows, '已执行')), '已执行')
|
|
|
XLSX.utils.book_append_sheet(workbook, XLSX.utils.json_to_sheet(mapExecutionRows(pendingRows, '待执行')), '待执行')
|
|
|
|
|
|
const date = new Date().toISOString().slice(0, 10)
|
|
|
XLSX.writeFile(workbook, `${sanitizeFileName(currentBudgetName.value)}_执行明细_${date}.xlsx`)
|
|
|
ElMessage.success('导出成功')
|
|
|
}
|
|
|
|
|
|
const buildDisplayTree = (nodes) => {
|
|
|
return nodes.map(node => ({
|
|
|
...node,
|
|
|
expanded: true,
|
|
|
children: node.children ? buildDisplayTree(node.children) : []
|
|
|
}))
|
|
|
}
|
|
|
|
|
|
const updateSummary = async () => {
|
|
|
// 只汇总第一层节点,因为第一层的数据已经包含了子孙层的汇总
|
|
|
// 计算部门预算(budget_type === 'department' 的第一层节点)
|
|
|
const departmentTotal = departmentBudget.value.reduce((sum, n) => sum + Number(n.budget_amount || 0), 0)
|
|
|
|
|
|
// 计算专项资金(budget_type === 'special_fund' 的第一层节点)
|
|
|
const specialFundTotal = specialFundBudget.value.reduce((sum, n) => sum + Number(n.budget_amount || 0), 0)
|
|
|
|
|
|
// 计算上一年结转资金(budget_type === 'last_year_carryover' 的第一层节点)
|
|
|
const lastYearCarryoverTotal = lastYearCarryoverBudget.value.reduce((sum, n) => sum + Number(n.budget_amount || 0), 0)
|
|
|
|
|
|
// 计算总预算(部门预算 + 专项资金,不包含自有账户)
|
|
|
const total = departmentTotal + specialFundTotal
|
|
|
|
|
|
// 计算自有账户(budget_type === 'project' 的第一层节点)
|
|
|
const projectTotal = projectBudget.value.reduce((sum, n) => sum + Number(n.budget_amount || 0), 0)
|
|
|
|
|
|
// 计算抵消预付账款(budget_type === 'offset_prepaid' 的第一层节点)
|
|
|
const offsetPrepaidTotal = offsetPrepaidBudget.value.reduce((sum, n) => sum + Number(n.budget_amount || 0), 0)
|
|
|
|
|
|
// 计算工会(budget_type === 'trade_union' 的第一层节点)
|
|
|
const tradeUnionTotal = tradeUnionBudget.value.reduce((sum, n) => sum + Number(n.budget_amount || 0), 0)
|
|
|
|
|
|
summary.value.totalBudget = total
|
|
|
summary.value.departmentBudget = departmentTotal
|
|
|
summary.value.specialFundBudget = specialFundTotal
|
|
|
summary.value.lastYearCarryoverBudget = lastYearCarryoverTotal
|
|
|
summary.value.projectBudget = projectTotal
|
|
|
summary.value.offsetPrepaidBudget = offsetPrepaidTotal
|
|
|
summary.value.tradeUnionBudget = tradeUnionTotal
|
|
|
|
|
|
// 获取执行统计
|
|
|
await loadExecutionStatistics()
|
|
|
}
|
|
|
|
|
|
// 加载执行统计
|
|
|
const loadExecutionStatistics = async () => {
|
|
|
if (!filterForm.value.year_id) {
|
|
|
return
|
|
|
}
|
|
|
try {
|
|
|
const params = {
|
|
|
year_id: filterForm.value.year_id
|
|
|
}
|
|
|
const res = await budgetDataAPI.getExecutionStatistics(params)
|
|
|
if (res.code === 0) {
|
|
|
summary.value.departmentExecuted = res.data.department_executed || 0
|
|
|
summary.value.specialFundUsed = res.data.special_fund_used || 0
|
|
|
summary.value.lastYearCarryoverUsed = res.data.last_year_carryover_used || 0
|
|
|
summary.value.projectUsed = res.data.project_used || 0
|
|
|
summary.value.offsetPrepaidUsed = res.data.offset_prepaid_used || 0
|
|
|
summary.value.tradeUnionUsed = res.data.trade_union_used || 0
|
|
|
}
|
|
|
} catch (e) {
|
|
|
console.error('加载执行统计失败:', e)
|
|
|
// 失败时不显示错误,保持默认值0
|
|
|
}
|
|
|
}
|
|
|
|
|
|
const loadYearOptions = async () => {
|
|
|
try {
|
|
|
const res = await budgetDataAPI.getYearOptions()
|
|
|
if (res.code === 0) {
|
|
|
yearOptions.value = res.data || []
|
|
|
if (!filterForm.value.year_id && yearOptions.value.length > 0) {
|
|
|
filterForm.value.year_id = yearOptions.value[0].value
|
|
|
}
|
|
|
} else {
|
|
|
ElMessage.error(res.msg || '加载年度失败')
|
|
|
}
|
|
|
} catch (e) {
|
|
|
ElMessage.error('加载年度失败:' + e.message)
|
|
|
}
|
|
|
}
|
|
|
|
|
|
const loadTreeData = async () => {
|
|
|
if (!filterForm.value.year_id) {
|
|
|
return
|
|
|
}
|
|
|
loading.value = true
|
|
|
try {
|
|
|
const params = {
|
|
|
year_id: filterForm.value.year_id
|
|
|
}
|
|
|
const res = await budgetDataAPI.getTree(params)
|
|
|
if (res.code === 0) {
|
|
|
const data = res.data || []
|
|
|
budgetTree.value = buildDisplayTree(data)
|
|
|
updateSummary()
|
|
|
} else {
|
|
|
ElMessage.error(res.msg || '加载预算数据失败')
|
|
|
}
|
|
|
} catch (e) {
|
|
|
ElMessage.error('加载预算数据失败:' + e.message)
|
|
|
} finally {
|
|
|
loading.value = false
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 支付明细详情相关
|
|
|
const detailDialogVisible = ref(false)
|
|
|
const paymentDetailInfo = ref(null)
|
|
|
|
|
|
// 查看支付明细详情
|
|
|
const viewPaymentDetail = async (row) => {
|
|
|
try {
|
|
|
// Step2:执行明细可能来自 Payment(row.id 为空)或 legacy PaymentDetail(row.id 有值)
|
|
|
if (row?.id) {
|
|
|
const res = await paymentDetailAPI.getDetailInfo(row.id)
|
|
|
if (res.code === 0) {
|
|
|
paymentDetailInfo.value = res.data
|
|
|
detailDialogVisible.value = true
|
|
|
} else {
|
|
|
ElMessage.error(res.msg || '加载详情失败')
|
|
|
}
|
|
|
return
|
|
|
}
|
|
|
|
|
|
// Payment 级记录:直接跳转到付款打印页
|
|
|
if (row?.payment_id) {
|
|
|
router.push({ path: `/payment/payment-detail-print/${row.payment_id}` })
|
|
|
return
|
|
|
}
|
|
|
|
|
|
ElMessage.warning('无法打开详情:缺少ID')
|
|
|
} catch (e) {
|
|
|
ElMessage.error('加载详情失败:' + e.message)
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 格式化字段值
|
|
|
const formatFieldValue = (field) => {
|
|
|
if (!field.value) return '-'
|
|
|
if (typeof field.value === 'object') {
|
|
|
if (field.value.title) return field.value.title
|
|
|
if (field.value.name) return field.value.name
|
|
|
return JSON.stringify(field.value)
|
|
|
}
|
|
|
return field.value
|
|
|
}
|
|
|
|
|
|
// 打印详情
|
|
|
const printDetail = () => {
|
|
|
window.print()
|
|
|
}
|
|
|
|
|
|
onMounted(async () => {
|
|
|
await loadYearOptions()
|
|
|
await loadTreeData()
|
|
|
})
|
|
|
</script>
|
|
|
|
|
|
<style scoped>
|
|
|
.budget-container {
|
|
|
padding: 20px;
|
|
|
background: #f5f7fa;
|
|
|
min-height: 100%;
|
|
|
}
|
|
|
|
|
|
.page-header {
|
|
|
background: white;
|
|
|
padding: 25px 30px;
|
|
|
border-radius: 10px;
|
|
|
margin-bottom: 20px;
|
|
|
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05);
|
|
|
display: flex;
|
|
|
justify-content: space-between;
|
|
|
align-items: center;
|
|
|
}
|
|
|
|
|
|
.page-title {
|
|
|
font-size: 24px;
|
|
|
font-weight: 600;
|
|
|
color: #333;
|
|
|
margin: 0;
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
gap: 10px;
|
|
|
}
|
|
|
|
|
|
.filter-section {
|
|
|
margin-bottom: 20px;
|
|
|
}
|
|
|
|
|
|
.summary-section {
|
|
|
margin-bottom: 20px;
|
|
|
}
|
|
|
|
|
|
.summary-row-card {
|
|
|
margin-bottom: 16px;
|
|
|
}
|
|
|
|
|
|
.summary-row-card:last-child {
|
|
|
margin-bottom: 0;
|
|
|
}
|
|
|
|
|
|
.summary-row-card-total {
|
|
|
background: linear-gradient(135deg, #409fff 0%, #93c5fd 100%);
|
|
|
border: none;
|
|
|
box-shadow: 0 4px 12px rgba(64, 159, 255, 0.25);
|
|
|
}
|
|
|
|
|
|
.summary-row-card-total :deep(.el-card__body) {
|
|
|
padding: 24px 20px;
|
|
|
}
|
|
|
|
|
|
.summary-row-card-total .summary-row-label {
|
|
|
color: rgba(255, 255, 255, 0.9);
|
|
|
font-size: 15px;
|
|
|
font-weight: 500;
|
|
|
}
|
|
|
|
|
|
.summary-row-card-total .summary-row-value {
|
|
|
color: #ffffff;
|
|
|
font-size: 24px;
|
|
|
font-weight: 700;
|
|
|
}
|
|
|
|
|
|
/* 上一年结转资金通栏 - 靛蓝渐变风格(调淡) */
|
|
|
.summary-row-card-carryover {
|
|
|
background: linear-gradient(135deg, #818cf8 0%, #c7d2fe 100%);
|
|
|
border: none;
|
|
|
box-shadow: 0 4px 12px rgba(129, 140, 248, 0.2);
|
|
|
}
|
|
|
|
|
|
.summary-row-card-carryover :deep(.el-card__body) {
|
|
|
padding: 24px 20px;
|
|
|
}
|
|
|
|
|
|
.summary-row-card-carryover .summary-row-label {
|
|
|
color: #4338ca;
|
|
|
font-size: 15px;
|
|
|
font-weight: 500;
|
|
|
}
|
|
|
|
|
|
.summary-row-card-carryover .summary-row-value {
|
|
|
color: #3730a3;
|
|
|
font-size: 24px;
|
|
|
font-weight: 700;
|
|
|
}
|
|
|
|
|
|
/* 抵消预付账款通栏 - 紫色渐变风格(调淡) */
|
|
|
.summary-row-card-offset-prepaid {
|
|
|
background: linear-gradient(135deg, #8b5cf6 0%, #c4b5fd 100%);
|
|
|
border: none;
|
|
|
box-shadow: 0 4px 12px rgba(139, 92, 246, 0.2);
|
|
|
}
|
|
|
|
|
|
.summary-row-card-offset-prepaid :deep(.el-card__body) {
|
|
|
padding: 24px 20px;
|
|
|
}
|
|
|
|
|
|
.summary-row-card-offset-prepaid .summary-row-label {
|
|
|
color: #5b21b6;
|
|
|
font-size: 15px;
|
|
|
font-weight: 500;
|
|
|
}
|
|
|
|
|
|
.summary-row-card-offset-prepaid .summary-row-value {
|
|
|
color: #4c1d95;
|
|
|
font-size: 24px;
|
|
|
font-weight: 700;
|
|
|
}
|
|
|
|
|
|
/* 工会通栏 - 青色渐变风格 */
|
|
|
.summary-row-card-trade-union {
|
|
|
background: linear-gradient(135deg, #0d9488 0%, #99f6e4 100%);
|
|
|
border: none;
|
|
|
box-shadow: 0 4px 12px rgba(13, 148, 136, 0.2);
|
|
|
}
|
|
|
|
|
|
.summary-row-card-trade-union :deep(.el-card__body) {
|
|
|
padding: 24px 20px;
|
|
|
}
|
|
|
|
|
|
.summary-row-card-trade-union .summary-row-label {
|
|
|
color: #0f766e;
|
|
|
font-size: 15px;
|
|
|
font-weight: 500;
|
|
|
}
|
|
|
|
|
|
.summary-row-card-trade-union .summary-row-value {
|
|
|
color: #115e59;
|
|
|
font-size: 24px;
|
|
|
font-weight: 700;
|
|
|
}
|
|
|
|
|
|
/* 抵消预付账款明细项表头 - 紫色渐变风格(调淡) */
|
|
|
.budget-level-1-header-trade_union {
|
|
|
background: linear-gradient(135deg, #0d9488 0%, #99f6e4 100%) !important;
|
|
|
color: #115e59 !important;
|
|
|
}
|
|
|
|
|
|
.budget-level-1-header-trade_union.collapsed {
|
|
|
background: #99f6e4 !important;
|
|
|
color: #115e59 !important;
|
|
|
}
|
|
|
|
|
|
.summary-row {
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
}
|
|
|
|
|
|
.summary-row-items {
|
|
|
display: flex;
|
|
|
gap: 40px;
|
|
|
width: 100%;
|
|
|
justify-content: space-around;
|
|
|
}
|
|
|
|
|
|
.summary-row-item {
|
|
|
flex: 1;
|
|
|
text-align: center;
|
|
|
}
|
|
|
|
|
|
.summary-row-label {
|
|
|
font-size: 14px;
|
|
|
color: #666;
|
|
|
margin-bottom: 8px;
|
|
|
}
|
|
|
|
|
|
.summary-row-value {
|
|
|
font-size: 20px;
|
|
|
font-weight: 600;
|
|
|
color: #333;
|
|
|
}
|
|
|
|
|
|
.project-summary-card {
|
|
|
margin-bottom: 16px;
|
|
|
}
|
|
|
|
|
|
.project-summary {
|
|
|
display: flex;
|
|
|
gap: 40px;
|
|
|
width: 100%;
|
|
|
justify-content: space-around;
|
|
|
}
|
|
|
|
|
|
.project-summary-item {
|
|
|
flex: 1;
|
|
|
text-align: center;
|
|
|
}
|
|
|
|
|
|
.project-summary-label {
|
|
|
font-size: 14px;
|
|
|
color: #666;
|
|
|
margin-bottom: 8px;
|
|
|
}
|
|
|
|
|
|
.project-summary-value {
|
|
|
font-size: 20px;
|
|
|
font-weight: 600;
|
|
|
color: #333;
|
|
|
}
|
|
|
|
|
|
/* 自有账户通栏 - 绿色渐变风格(调淡) */
|
|
|
.project-summary-card-green {
|
|
|
background: linear-gradient(135deg, #34d399 0%, #a7f3d0 100%);
|
|
|
border: none;
|
|
|
box-shadow: 0 4px 12px rgba(52, 211, 153, 0.2);
|
|
|
}
|
|
|
|
|
|
.project-summary-card-green :deep(.el-card__body) {
|
|
|
padding: 24px 20px;
|
|
|
}
|
|
|
|
|
|
.project-summary-card-green .project-summary-label {
|
|
|
color: #065f46;
|
|
|
font-size: 15px;
|
|
|
font-weight: 500;
|
|
|
}
|
|
|
|
|
|
.project-summary-card-green .project-summary-value {
|
|
|
color: #047857;
|
|
|
font-size: 24px;
|
|
|
font-weight: 700;
|
|
|
}
|
|
|
|
|
|
.summary-item {
|
|
|
text-align: center;
|
|
|
}
|
|
|
|
|
|
.summary-label {
|
|
|
font-size: 14px;
|
|
|
color: #666;
|
|
|
margin-bottom: 8px;
|
|
|
}
|
|
|
|
|
|
.summary-value {
|
|
|
font-size: 28px;
|
|
|
font-weight: 700;
|
|
|
color: #333;
|
|
|
margin-bottom: 4px;
|
|
|
}
|
|
|
|
|
|
.summary-value.positive {
|
|
|
color: #10b981;
|
|
|
}
|
|
|
|
|
|
.summary-value-chinese {
|
|
|
font-size: 12px;
|
|
|
color: #909399;
|
|
|
line-height: 1.4;
|
|
|
word-break: break-all;
|
|
|
margin-top: 4px;
|
|
|
}
|
|
|
|
|
|
.execution-percentage {
|
|
|
font-size: 16px;
|
|
|
font-weight: 500;
|
|
|
color: #409eff;
|
|
|
margin-left: 8px;
|
|
|
}
|
|
|
|
|
|
.content-header {
|
|
|
display: flex;
|
|
|
justify-content: space-between;
|
|
|
align-items: center;
|
|
|
}
|
|
|
|
|
|
.content-title {
|
|
|
font-size: 18px;
|
|
|
font-weight: 600;
|
|
|
color: #333;
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
gap: 8px;
|
|
|
}
|
|
|
|
|
|
.budget-tree {
|
|
|
padding: 0;
|
|
|
}
|
|
|
|
|
|
.type-section-card {
|
|
|
margin-bottom: 20px;
|
|
|
}
|
|
|
|
|
|
.type-section-card:last-child {
|
|
|
margin-bottom: 0;
|
|
|
}
|
|
|
|
|
|
/* 明细表头保持白色背景,不修改 */
|
|
|
|
|
|
/* 标题标签背景色随大块主色调 */
|
|
|
.type-section-card-department :deep(.type-title .el-tag--dark),
|
|
|
.type-section-card-special_fund :deep(.type-title .el-tag--dark) {
|
|
|
background-color: #409fff !important;
|
|
|
border-color: #409fff !important;
|
|
|
}
|
|
|
|
|
|
.type-section-card-last_year_carryover :deep(.type-title .el-tag--dark) {
|
|
|
background-color: #6366f1 !important;
|
|
|
border-color: #6366f1 !important;
|
|
|
}
|
|
|
|
|
|
.type-section-card-project :deep(.type-title .el-tag--dark) {
|
|
|
background-color: #10b981 !important;
|
|
|
border-color: #10b981 !important;
|
|
|
}
|
|
|
|
|
|
.type-section-card-offset_prepaid :deep(.type-title .el-tag--dark) {
|
|
|
background-color: #8b5cf6 !important;
|
|
|
border-color: #8b5cf6 !important;
|
|
|
}
|
|
|
|
|
|
.type-section-card-trade_union :deep(.type-title .el-tag--dark) {
|
|
|
background-color: #0d9488 !important;
|
|
|
border-color: #0d9488 !important;
|
|
|
}
|
|
|
|
|
|
.type-header {
|
|
|
display: flex;
|
|
|
justify-content: space-between;
|
|
|
align-items: center;
|
|
|
margin-bottom: 10px;
|
|
|
}
|
|
|
|
|
|
.type-title {
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
gap: 8px;
|
|
|
font-weight: 600;
|
|
|
}
|
|
|
|
|
|
.type-count {
|
|
|
color: #6b7280;
|
|
|
font-size: 12px;
|
|
|
}
|
|
|
|
|
|
.budget-level-1 {
|
|
|
margin-bottom: 15px;
|
|
|
border: 1px solid #e5e7eb;
|
|
|
border-radius: 8px;
|
|
|
overflow: hidden;
|
|
|
background: #fafafa;
|
|
|
}
|
|
|
|
|
|
.budget-level-1-header {
|
|
|
padding: 15px 20px;
|
|
|
background: linear-gradient(135deg, #409fff 0%, #93c5fd 100%);
|
|
|
color: white;
|
|
|
display: flex;
|
|
|
justify-content: space-between;
|
|
|
align-items: center;
|
|
|
cursor: pointer;
|
|
|
transition: all 0.3s;
|
|
|
}
|
|
|
|
|
|
.budget-level-1-header:hover {
|
|
|
opacity: 0.9;
|
|
|
}
|
|
|
|
|
|
.budget-level-1-header.collapsed {
|
|
|
background: #f3f4f6;
|
|
|
color: #333;
|
|
|
}
|
|
|
|
|
|
/* 上一年结转资金明细项表头 - 靛蓝渐变风格(调淡) */
|
|
|
.budget-level-1-header-last_year_carryover {
|
|
|
background: linear-gradient(135deg, #818cf8 0%, #c7d2fe 100%);
|
|
|
color: #312e81;
|
|
|
}
|
|
|
|
|
|
.budget-level-1-header-last_year_carryover.collapsed {
|
|
|
background: #eef2ff;
|
|
|
color: #4338ca;
|
|
|
}
|
|
|
|
|
|
/* 自有账户明细项表头 - 绿色渐变风格(调淡) */
|
|
|
.budget-level-1-header-project {
|
|
|
background: linear-gradient(135deg, #34d399 0%, #a7f3d0 100%);
|
|
|
color: #064e3b;
|
|
|
}
|
|
|
|
|
|
.budget-level-1-header-project.collapsed {
|
|
|
background: #f0fdf4;
|
|
|
color: #059669;
|
|
|
}
|
|
|
|
|
|
/* 抵消预付账款明细项表头 - 紫色渐变风格(调淡) */
|
|
|
.budget-level-1-header-offset_prepaid {
|
|
|
background: linear-gradient(135deg, #8b5cf6 0%, #c4b5fd 100%);
|
|
|
color: #4c1d95;
|
|
|
}
|
|
|
|
|
|
.budget-level-1-header-offset_prepaid.collapsed {
|
|
|
background: #f3e8ff;
|
|
|
color: #7c3aed;
|
|
|
}
|
|
|
|
|
|
.budget-level-1-title {
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
gap: 10px;
|
|
|
font-weight: 600;
|
|
|
font-size: 16px;
|
|
|
}
|
|
|
|
|
|
.budget-level-1-info {
|
|
|
display: flex;
|
|
|
gap: 30px;
|
|
|
align-items: center;
|
|
|
}
|
|
|
|
|
|
.budget-info-item {
|
|
|
display: flex;
|
|
|
flex-direction: column;
|
|
|
align-items: flex-end;
|
|
|
}
|
|
|
|
|
|
.level-1-detail-button {
|
|
|
margin-left: 12px;
|
|
|
font-weight: 600;
|
|
|
}
|
|
|
|
|
|
.budget-info-label {
|
|
|
font-size: 12px;
|
|
|
opacity: 0.8;
|
|
|
margin-bottom: 3px;
|
|
|
}
|
|
|
|
|
|
.budget-info-value {
|
|
|
font-size: 18px;
|
|
|
font-weight: 700;
|
|
|
}
|
|
|
|
|
|
.budget-level-1-content {
|
|
|
padding: 0;
|
|
|
}
|
|
|
|
|
|
.budget-level-2 {
|
|
|
padding: 15px 20px;
|
|
|
border-bottom: 1px solid #e5e7eb;
|
|
|
background: white;
|
|
|
display: grid;
|
|
|
grid-template-columns: 2fr 1fr 1fr 1fr 1fr 150px;
|
|
|
gap: 15px;
|
|
|
align-items: center;
|
|
|
transition: background-color 0.3s;
|
|
|
}
|
|
|
|
|
|
.budget-level-2:hover {
|
|
|
background-color: #f8f9fa;
|
|
|
}
|
|
|
|
|
|
.budget-level-2:last-child {
|
|
|
border-bottom: none;
|
|
|
}
|
|
|
|
|
|
.budget-level-2-name {
|
|
|
font-weight: 500;
|
|
|
color: #333;
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
gap: 8px;
|
|
|
}
|
|
|
|
|
|
.budget-amount {
|
|
|
text-align: right;
|
|
|
font-weight: 600;
|
|
|
color: #333;
|
|
|
}
|
|
|
|
|
|
.budget-used {
|
|
|
text-align: right;
|
|
|
color: #666;
|
|
|
}
|
|
|
|
|
|
.budget-remaining {
|
|
|
text-align: right;
|
|
|
font-weight: 600;
|
|
|
}
|
|
|
|
|
|
.budget-remaining.positive {
|
|
|
color: #10b981;
|
|
|
}
|
|
|
|
|
|
.budget-progress {
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
gap: 10px;
|
|
|
}
|
|
|
|
|
|
.progress-text {
|
|
|
font-size: 12px;
|
|
|
color: #666;
|
|
|
min-width: 45px;
|
|
|
text-align: right;
|
|
|
}
|
|
|
|
|
|
@media (max-width: 1200px) {
|
|
|
.budget-level-2 {
|
|
|
grid-template-columns: 2fr 1fr 1fr 1fr;
|
|
|
gap: 10px;
|
|
|
}
|
|
|
|
|
|
.budget-progress {
|
|
|
grid-column: 1 / -1;
|
|
|
margin-top: 10px;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/* 执行明细抽屉样式 */
|
|
|
.drawer-header {
|
|
|
display: flex;
|
|
|
flex-direction: column;
|
|
|
gap: 15px;
|
|
|
}
|
|
|
|
|
|
.drawer-title-row {
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
justify-content: space-between;
|
|
|
gap: 16px;
|
|
|
}
|
|
|
|
|
|
.drawer-header h3 {
|
|
|
margin: 0;
|
|
|
font-size: 18px;
|
|
|
font-weight: 600;
|
|
|
color: #333;
|
|
|
}
|
|
|
|
|
|
.drawer-summary {
|
|
|
display: flex;
|
|
|
gap: 30px;
|
|
|
padding: 15px;
|
|
|
background: #f5f7fa;
|
|
|
border-radius: 6px;
|
|
|
}
|
|
|
|
|
|
.drawer-summary .summary-item {
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
gap: 8px;
|
|
|
}
|
|
|
|
|
|
.drawer-summary .label {
|
|
|
font-size: 14px;
|
|
|
color: #666;
|
|
|
}
|
|
|
|
|
|
.drawer-summary .value {
|
|
|
font-size: 18px;
|
|
|
font-weight: 700;
|
|
|
}
|
|
|
|
|
|
.drawer-summary .value.executed {
|
|
|
color: #10b981;
|
|
|
}
|
|
|
|
|
|
.drawer-summary .value.pending {
|
|
|
color: #409eff;
|
|
|
}
|
|
|
|
|
|
.execution-tabs {
|
|
|
margin-top: 20px;
|
|
|
}
|
|
|
|
|
|
.detail-list {
|
|
|
display: flex;
|
|
|
flex-direction: column;
|
|
|
gap: 15px;
|
|
|
}
|
|
|
|
|
|
.empty-state {
|
|
|
padding: 40px 0;
|
|
|
text-align: center;
|
|
|
}
|
|
|
|
|
|
.detail-item {
|
|
|
padding: 20px;
|
|
|
background: #fff;
|
|
|
border: 1px solid #e5e7eb;
|
|
|
border-radius: 8px;
|
|
|
transition: all 0.3s;
|
|
|
}
|
|
|
|
|
|
.detail-item:hover {
|
|
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
|
|
}
|
|
|
|
|
|
.detail-header {
|
|
|
display: flex;
|
|
|
justify-content: space-between;
|
|
|
align-items: center;
|
|
|
padding-bottom: 15px;
|
|
|
border-bottom: 1px solid #e5e7eb;
|
|
|
margin-bottom: 15px;
|
|
|
}
|
|
|
|
|
|
.detail-header .serial-number {
|
|
|
font-size: 16px;
|
|
|
font-weight: 600;
|
|
|
color: #333;
|
|
|
}
|
|
|
|
|
|
.detail-header .header-right {
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
gap: 15px;
|
|
|
}
|
|
|
|
|
|
.detail-header .amount {
|
|
|
font-size: 18px;
|
|
|
font-weight: 700;
|
|
|
}
|
|
|
|
|
|
.detail-header .amount.executed {
|
|
|
color: #10b981;
|
|
|
}
|
|
|
|
|
|
.detail-header .amount.pending {
|
|
|
color: #409eff;
|
|
|
}
|
|
|
|
|
|
.detail-info {
|
|
|
display: flex;
|
|
|
flex-direction: column;
|
|
|
gap: 10px;
|
|
|
}
|
|
|
|
|
|
.info-row {
|
|
|
display: flex;
|
|
|
font-size: 14px;
|
|
|
line-height: 1.6;
|
|
|
}
|
|
|
|
|
|
.info-label {
|
|
|
color: #666;
|
|
|
min-width: 80px;
|
|
|
flex-shrink: 0;
|
|
|
}
|
|
|
|
|
|
.info-value {
|
|
|
color: #333;
|
|
|
flex: 1;
|
|
|
}
|
|
|
|
|
|
/* A4格式详情弹窗样式 */
|
|
|
:deep(.payment-detail-dialog) {
|
|
|
.el-dialog__body {
|
|
|
padding: 0;
|
|
|
background: #f5f5f5;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
.a4-container {
|
|
|
display: flex;
|
|
|
justify-content: center;
|
|
|
padding: 20px;
|
|
|
}
|
|
|
|
|
|
.a4-page {
|
|
|
width: 210mm;
|
|
|
min-height: 297mm;
|
|
|
background: white;
|
|
|
padding: 20mm;
|
|
|
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
|
|
|
}
|
|
|
|
|
|
.detail-header-section {
|
|
|
margin-bottom: 30px;
|
|
|
padding-bottom: 20px;
|
|
|
border-bottom: 2px solid #333;
|
|
|
}
|
|
|
|
|
|
.main-title {
|
|
|
font-size: 24px;
|
|
|
font-weight: 700;
|
|
|
color: #333;
|
|
|
margin: 0 0 20px 0;
|
|
|
text-align: center;
|
|
|
}
|
|
|
|
|
|
.detail-basic-info {
|
|
|
display: grid;
|
|
|
grid-template-columns: repeat(2, 1fr);
|
|
|
gap: 15px;
|
|
|
}
|
|
|
|
|
|
.detail-basic-info .info-row {
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
font-size: 14px;
|
|
|
}
|
|
|
|
|
|
.detail-basic-info .label {
|
|
|
font-weight: 600;
|
|
|
color: #666;
|
|
|
min-width: 100px;
|
|
|
}
|
|
|
|
|
|
.detail-basic-info .value {
|
|
|
color: #333;
|
|
|
}
|
|
|
|
|
|
.detail-basic-info .value.amount {
|
|
|
font-size: 18px;
|
|
|
font-weight: 700;
|
|
|
color: #10b981;
|
|
|
}
|
|
|
|
|
|
.info-section {
|
|
|
margin-bottom: 30px;
|
|
|
padding: 20px;
|
|
|
border: 1px solid #e5e7eb;
|
|
|
border-radius: 6px;
|
|
|
background: #fafafa;
|
|
|
}
|
|
|
|
|
|
.section-title {
|
|
|
font-size: 18px;
|
|
|
font-weight: 600;
|
|
|
color: #333;
|
|
|
margin: 0 0 20px 0;
|
|
|
padding-bottom: 10px;
|
|
|
border-bottom: 1px solid #e5e7eb;
|
|
|
}
|
|
|
|
|
|
.subsection-title {
|
|
|
font-size: 16px;
|
|
|
font-weight: 600;
|
|
|
color: #666;
|
|
|
margin: 20px 0 15px 0;
|
|
|
}
|
|
|
|
|
|
.info-grid {
|
|
|
display: grid;
|
|
|
grid-template-columns: repeat(2, 1fr);
|
|
|
gap: 15px;
|
|
|
}
|
|
|
|
|
|
.info-item {
|
|
|
display: flex;
|
|
|
align-items: flex-start;
|
|
|
font-size: 14px;
|
|
|
line-height: 1.6;
|
|
|
}
|
|
|
|
|
|
.info-item.full-width {
|
|
|
grid-column: 1 / -1;
|
|
|
}
|
|
|
|
|
|
.info-item .label {
|
|
|
font-weight: 600;
|
|
|
color: #666;
|
|
|
min-width: 120px;
|
|
|
flex-shrink: 0;
|
|
|
}
|
|
|
|
|
|
.info-item .value {
|
|
|
color: #333;
|
|
|
word-break: break-word;
|
|
|
}
|
|
|
|
|
|
.custom-fields {
|
|
|
margin-top: 20px;
|
|
|
padding-top: 20px;
|
|
|
border-top: 1px solid #e5e7eb;
|
|
|
}
|
|
|
|
|
|
/* 打印样式 */
|
|
|
@media print {
|
|
|
.a4-container {
|
|
|
padding: 0;
|
|
|
}
|
|
|
|
|
|
.a4-page {
|
|
|
box-shadow: none;
|
|
|
page-break-after: always;
|
|
|
}
|
|
|
|
|
|
:deep(.el-dialog__header),
|
|
|
:deep(.el-dialog__footer) {
|
|
|
display: none;
|
|
|
}
|
|
|
|
|
|
:deep(.el-dialog__body) {
|
|
|
padding: 0;
|
|
|
}
|
|
|
}
|
|
|
</style>
|