| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448 |
- <template>
- <view class="module-container">
- <uv-empty v-if="projectStore.fetchFundingLoading" mode="list" text="加载中..."></uv-empty>
- <view v-else class="content-wrapper">
-
- <!-- 经费汇总概览 -->
- <view class="summary-card">
- <view class="sum-row">
- <view class="sum-item">
- <text class="sum-label">到款合计({{ projectStore.fundsData?.fundAllotCount || 0 }}笔)</text>
- <text class="sum-value text-blue">{{ amountUnitFormatter(projectStore.fundsData?.fundAllotSum) }}<text class="unit">元</text></text>
- </view>
- <view class="sum-item">
- <text class="sum-label">结余</text>
- <text class="sum-value text-green">{{ amountUnitFormatter(projectStore.fundsData?.surplus) }}<text class="unit">元</text></text>
- </view>
- </view>
- <view class="sum-row">
- <view class="sum-item">
- <text class="sum-label">支出合计({{ projectStore.fundsData?.expenseCount || 0 }}笔)</text>
- <text class="sum-value text-red">{{ amountUnitFormatter(projectStore.fundsData?.expenseSum) }}<text class="unit">元</text></text>
- </view>
- <view class="sum-item">
- <text class="sum-label">外拨合计({{ projectStore.fundsData?.externalAllotCount || 0 }}笔)</text>
- <text class="sum-value text-orange">{{ amountUnitFormatter(projectStore.fundsData?.externalAllotSum) }}<text class="unit">元</text></text>
- </view>
- </view>
- </view>
- <!-- 标签页切换 -->
- <view class="tabs-wrapper">
- <uv-tabs :list="fundingTabs" :current="currentTab" @click="handleTabClick" lineColor="#1c9bfd" activeColor="#1c9bfd" inactiveColor="#666"></uv-tabs>
- </view>
- <!-- 入账列表 -->
- <view class="section" v-show="currentTab === 0">
- <uv-empty v-if="!projectStore.claimdData || projectStore.claimdData.length === 0" mode="data" text="暂无入账记录"></uv-empty>
- <template v-else>
- <CommonListCard
- v-for="(item, index) in projectStore.claimdData"
- :key="'in'+index"
- :title="item.allotNo || '入账记录'"
- statusType="primary"
- @click="handleItemClick(item, 0)"
- >
- <CommonInfoRow label="认领金额" :value="amountUnitFormatter(item.amount)" isAmount />
- <CommonInfoRow label="认领时间" :value="item.applyTime ? formatDate(item.applyTime) : '--'" />
- <CommonInfoRow label="负责人" :value="item.projectIncharge" noBorder />
- </CommonListCard>
- </template>
- </view>
- <!-- 外拨列表 -->
- <view class="section" v-show="currentTab === 1">
- <uv-empty v-if="!projectStore.outBoundData || projectStore.outBoundData.length === 0" mode="data" text="暂无外拨记录"></uv-empty>
- <template v-else>
- <CommonListCard
- v-for="(item, index) in projectStore.outBoundData"
- :key="'out'+index"
- :title="item.externalAllotNo || '外拨记录'"
- statusType="warning"
- @click="handleItemClick(item, 1)"
- >
- <CommonInfoRow label="外拨总额" :value="amountUnitFormatter(item.amount)" isAmount />
- <CommonInfoRow label="申请日期" :value="item.applyTime ? formatDate(item.applyTime) : '--'" />
- <CommonInfoRow label="负责人" :value="item.projectIncharge" noBorder />
- </CommonListCard>
- </template>
- </view>
- <!-- 支出列表 -->
- <view class="section" v-show="currentTab === 2">
- <uv-empty v-if="!projectStore.rebateData || projectStore.rebateData.length === 0" mode="data" text="暂无支出记录"></uv-empty>
- <template v-else>
- <CommonListCard
- v-for="(item, index) in projectStore.rebateData"
- :key="'exp'+index"
- :title="item.expenseNo || '支出记录'"
- :statusLabel="getExpenseStatusLabel(item.status)"
- :statusType="getExpenseStatusType(item.status)"
- @click="handleItemClick(item, 2)"
- >
- <CommonInfoRow label="支出金额" :value="amountUnitFormatter(item.amount)" isAmount />
- <CommonInfoRow label="支出科目" :value="item.subject" />
- <CommonInfoRow label="支出日期" :value="item.expendTime ? formatDate(item.expendTime) : '--'" noBorder />
- </CommonListCard>
- </template>
- </view>
- </view>
- <!-- 详情弹窗 -->
- <uv-popup ref="detailPopup" mode="bottom" round="20" bgColor="#f8f9fc">
- <view class="detail-popup-content">
- <view class="popup-header">
- <text class="title">详情记录</text>
- <uv-icon name="close" @click="closePopup" color="#999" size="20"></uv-icon>
- </view>
- <scroll-view scroll-y class="detail-scroll">
- <view class="detail-list">
- <view class="detail-item" v-for="(field, idx) in currentFields" :key="idx">
- <text class="label">{{ field.label }}</text>
- <view class="value-wrap">
- <text class="value" :class="{ 'amount': field.isAmount }">
- {{ formatFieldValue(field) }}
- </text>
- <text class="unit" v-if="field.isAmount">元</text>
- </view>
- </view>
-
- <view v-if="activeItem?.fileName" class="attachment-section">
- <text class="label">附件信息</text>
- <CommonFileList :files="[{ fileName: activeItem.fileName, fileUrl: activeItem.fileUrl }]" />
- </view>
- </view>
- </scroll-view>
- </view>
- </uv-popup>
- </view>
- </template>
- <script setup lang="ts">
- import { ref, watch, computed } from 'vue';
- import { useProjectStore } from '@/store/modules/project';
- import { formatDate } from '@/utils/date';
- import { formatWithComma } from '@/utils/format';
- import { previewFile } from '@/utils/file';
- import CommonListCard from '@/components/ui/CommonListCard.vue';
- import CommonInfoRow from '@/components/ui/CommonInfoRow.vue';
- import CommonFileList from '@/components/ui/CommonFileList.vue';
- /**
- * 接收来自父组件的属性
- * projectId: 项目内码
- * projectType: 项目分类标识
- */
- const props = defineProps<{
- projectId: number;
- projectType: string;
- }>();
- const projectStore = useProjectStore();
- // 当前激活的子 Tab 索引
- const currentTab = ref(0);
- const detailPopup = ref();
- const activeItem = ref<any>(null);
- const activeTabType = ref(0);
- // 详情字段配置
- const fieldConfigs = {
- inbound: [
- { label: '认领编号', key: 'allotNo' },
- { label: '项目负责人', key: 'projectIncharge' },
- { label: '项目类型', key: 'projectType', isType: true },
- { label: '认领金额', key: 'amount', isAmount: true },
- { label: '外拨金额', key: 'externalAmount', isAmount: true },
- { label: '留校金额', key: 'internalAmount', isAmount: true },
- { label: '认领时间', key: 'applyTime', isDate: true },
- { label: '摘要', key: 'remark' },
- { label: '创建人', key: 'createdName' },
- { label: '创建时间', key: 'createdTime', isDate: true },
- ],
- outbound: [
- { label: '外拨编号', key: 'externalAllotNo' },
- { label: '项目编号', key: 'projectNo' },
- { label: '项目负责人', key: 'projectIncharge' },
- { label: '外拨总额', key: 'amount', isAmount: true },
- { label: '已拨金额', key: 'allottedAmount', isAmount: true },
- { label: '未拨金额', key: 'notAllottedAmount', isAmount: true },
- { label: '申请日期', key: 'applyTime', isDate: true },
- { label: '摘要', key: 'remark' },
- { label: '创建人', key: 'createdName' },
- { label: '创建时间', key: 'createdTime', isDate: true },
- ],
- expense: [
- { label: '支出编号', key: 'expenseNo' },
- { label: '项目类型', key: 'projectType', isType: true },
- { label: '项目名称', key: 'projectName' },
- { label: '支出金额', key: 'amount', isAmount: true },
- { label: '支出科目', key: 'subject' },
- { label: '支出状态', key: 'status', isStatus: true },
- { label: '合同金额', key: 'contractAmount', isAmount: true },
- { label: '间接/配套经费支出', key: 'otherAmount', isAmount: true },
- { label: '财政拨款支出', key: 'projectAmount', isAmount: true },
- { label: '自筹经费支出', key: 'raiseAmount', isAmount: true },
- { label: '收款人', key: 'receiver' },
- { label: '支出日期', key: 'expendTime', isDate: true },
- { label: '摘要', key: 'remark' },
- { label: '创建人', key: 'createdName' },
- { label: '创建时间', key: 'createdTime', isDate: true },
- ]
- };
- const currentFields = computed(() => {
- if (activeTabType.value === 0) return fieldConfigs.inbound;
- if (activeTabType.value === 1) return fieldConfigs.outbound;
- return fieldConfigs.expense;
- });
- const formatFieldValue = (field: any) => {
- if (!activeItem.value) return '--';
- const val = activeItem.value[field.key];
- if (field.isAmount) return amountUnitFormatter(val);
- if (field.isDate) return val ? formatDate(val) : '--';
- if (field.isType) return getProjectType(val);
- if (field.isStatus) return getExpenseStatusLabel(val);
- return val || '--';
- };
- /**
- * 详情点击显示
- */
- const handleItemClick = (item: any, type: number) => {
- activeItem.value = item;
- activeTabType.value = type;
- detailPopup.value.open();
- };
- const closePopup = () => {
- detailPopup.value.close();
- };
- // 经费模块内部的子 Tab 配置
- const fundingTabs = ref([
- { name: '入账记录' },
- { name: '外拨记录' },
- { name: '支出记录' }
- ]);
- /**
- * 切换子 Tab 处理
- */
- const handleTabClick = (item: any) => {
- currentTab.value = item.index;
- };
- /**
- * 监听项目 ID 变化,触发经费数据加载
- */
- watch(() => props.projectId, (id) => {
- if (id) {
- projectStore.fetchFunding(id, props.projectType);
- }
- }, { immediate: true });
- /**
- * 金额格式化处理
- * 使用全局统一的千分位格式化工具
- */
- const amountUnitFormatter = (num: any) => {
- return formatWithComma(num);
- };
- /**
- * 获取项目类型名称 (内部逻辑)
- */
- const getProjectType = (code: string | number) => {
- if (code == 10) return '纵向项目';
- if (code == 20) return '横向项目';
- if (code == 30) return '内部项目';
- if (code == 40) return '重点学科';
- return '未知';
- };
- /**
- * 获取支出记录审批状态说明
- */
- const getExpenseStatusLabel = (status: string | number) => {
- if (status == 10) return '待审核';
- if (status == 20) return '已通过';
- if (status == 30) return '已拒绝';
- return '未知';
- };
- /**
- * 获取支出记录审批状态对应的 UI 类型
- */
- const getExpenseStatusType = (status: string | number) => {
- if (status == 10) return 'info';
- if (status == 20) return 'success';
- if (status == 30) return 'error';
- return 'default';
- };
- /**
- * 统一附件打开/预览处理
- */
- const handleDownload = (file: any) => {
- if (!file.fileUrl) return;
- previewFile(file.fileUrl, file.fileName);
- };
- </script>
- <style lang="scss" scoped>
- .module-container {
- min-height: 400rpx;
- position: relative;
- }
- .content-wrapper {
- display: flex;
- flex-direction: column;
- gap: 30rpx;
- }
- .tabs-wrapper {
- background-color: #fff;
- border-radius: 16rpx;
- overflow: hidden;
- box-shadow: 0 4rpx 16rpx rgba(0,0,0,0.03);
- }
- .summary-card {
- background: linear-gradient(135deg, #ffffff 0%, #f6faff 100%);
- border-radius: 20rpx;
- padding: 30rpx;
- box-shadow: 0 8rpx 24rpx rgba(28, 155, 253, 0.08);
- display: flex;
- flex-direction: column;
- gap: 30rpx;
- .sum-row {
- display: flex;
- justify-content: space-between;
- .sum-item {
- flex: 1;
- display: flex;
- flex-direction: column;
- align-items: center;
- .sum-label {
- font-size: 24rpx;
- color: #909399;
- margin-bottom: 12rpx;
- }
- .sum-value {
- font-size: 38rpx;
- font-weight: 700;
- font-family: din;
- letter-spacing: 0.5px;
-
- .unit {
- font-size: 22rpx;
- margin-left: 6rpx;
- font-weight: 400;
- color: #999;
- }
- }
- .text-blue { color: #1c9bfd; }
- .text-green { color: #52c41a; }
- .text-red { color: #ff4d4f; }
- .text-orange { color: #faad14; }
- }
- }
- }
- .detail-popup-content {
- padding: 40rpx 30rpx;
- max-height: 85vh;
- display: flex;
- flex-direction: column;
- .popup-header {
- display: flex;
- justify-content: space-between;
- align-items: center;
- margin-bottom: 40rpx;
- padding: 0 10rpx;
- .title {
- font-size: 34rpx;
- font-weight: bold;
- color: #333;
- }
- }
- .detail-scroll {
- flex: 1;
- overflow: hidden;
- }
- .detail-list {
- display: flex;
- flex-direction: column;
- gap: 30rpx;
- .detail-item {
- display: flex;
- justify-content: space-between;
- align-items: flex-start;
- padding-bottom: 24rpx;
- border-bottom: 1rpx solid #efefef;
- .label {
- font-size: 28rpx;
- color: #909399;
- flex-shrink: 0;
- margin-right: 30rpx;
- }
- .value-wrap {
- display: flex;
- align-items: baseline;
- justify-content: flex-end;
- flex: 1;
- .value {
- font-size: 28rpx;
- color: #333;
- text-align: right;
- word-break: break-all;
- &.amount {
- color: #1c9bfd;
- font-weight: bold;
- font-family: din;
- font-size: 32rpx;
- }
- }
-
- .unit {
- font-size: 20rpx;
- color: #999;
- margin-left: 4rpx;
- }
- }
- }
- .attachment-section {
- margin-top: 20rpx;
- .label {
- font-size: 28rpx;
- color: #909399;
- display: block;
- margin-bottom: 16rpx;
- }
- }
- }
- }
- .section {
- width: 100%;
- }
- </style>
|