ProjectFunding.vue 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271
  1. <template>
  2. <view class="module-container">
  3. <uv-empty v-if="projectStore.fetchFundingLoading" mode="list" text="加载中..."></uv-empty>
  4. <view v-else class="content-wrapper">
  5. <!-- 经费汇总概览 -->
  6. <view class="summary-card">
  7. <view class="sum-row">
  8. <view class="sum-item">
  9. <text class="sum-label">到款合计({{ projectStore.fundsData?.fundAllotCount || 0 }}笔)</text>
  10. <text class="sum-value text-blue">{{ amountUnitFormatter(projectStore.fundsData?.fundAllotSum) }}<text class="unit">元</text></text>
  11. </view>
  12. <view class="sum-item">
  13. <text class="sum-label">结余</text>
  14. <text class="sum-value text-green">{{ amountUnitFormatter(projectStore.fundsData?.surplus) }}<text class="unit">元</text></text>
  15. </view>
  16. </view>
  17. <view class="sum-row">
  18. <view class="sum-item">
  19. <text class="sum-label">支出合计({{ projectStore.fundsData?.expenseCount || 0 }}笔)</text>
  20. <text class="sum-value text-red">{{ amountUnitFormatter(projectStore.fundsData?.expenseSum) }}<text class="unit">元</text></text>
  21. </view>
  22. <view class="sum-item">
  23. <text class="sum-label">外拨合计({{ projectStore.fundsData?.externalAllotCount || 0 }}笔)</text>
  24. <text class="sum-value text-orange">{{ amountUnitFormatter(projectStore.fundsData?.externalAllotSum) }}<text class="unit">元</text></text>
  25. </view>
  26. </view>
  27. </view>
  28. <!-- 标签页切换 -->
  29. <view class="tabs-wrapper">
  30. <uv-tabs :list="fundingTabs" :current="currentTab" @click="handleTabClick" lineColor="#1c9bfd" activeColor="#1c9bfd" inactiveColor="#666"></uv-tabs>
  31. </view>
  32. <!-- 入账列表 -->
  33. <view class="section" v-show="currentTab === 0">
  34. <uv-empty v-if="!projectStore.claimdData || projectStore.claimdData.length === 0" mode="data" text="暂无入账记录"></uv-empty>
  35. <template v-else>
  36. <CommonListCard
  37. v-for="(item, index) in projectStore.claimdData"
  38. :key="'in'+index"
  39. :title="item.allotNo || '入账记录'"
  40. statusType="primary"
  41. >
  42. <CommonInfoRow label="认领金额(元)" :value="amountUnitFormatter(item.amount)" isAmount />
  43. <CommonInfoRow label="外拨金额(元)" :value="amountUnitFormatter(item.externalAmount)" />
  44. <CommonInfoRow label="留校金额(元)" :value="amountUnitFormatter(item.internalAmount)" />
  45. <CommonInfoRow label="认领时间" :value="item.applyTime ? formatDate(item.applyTime) : '--'" />
  46. <CommonInfoRow label="摘要" :value="item.remark" />
  47. <CommonInfoRow label="创建人" :value="item.createBy || item.createName" />
  48. <CommonInfoRow label="创建时间" :value="item.createTime ? formatDate(item.createTime) : '--'" noBorder />
  49. </CommonListCard>
  50. </template>
  51. </view>
  52. <!-- 外拨列表 -->
  53. <view class="section" v-show="currentTab === 1">
  54. <uv-empty v-if="!projectStore.outBoundData || projectStore.outBoundData.length === 0" mode="data" text="暂无外拨记录"></uv-empty>
  55. <template v-else>
  56. <CommonListCard
  57. v-for="(item, index) in projectStore.outBoundData"
  58. :key="'out'+index"
  59. :title="item.externalAllotNo || '外拨记录'"
  60. statusType="warning"
  61. >
  62. <CommonInfoRow label="项目编号" :value="item.projectNo" />
  63. <CommonInfoRow label="外拨总额(元)" :value="amountUnitFormatter(item.amount)" isAmount />
  64. <CommonInfoRow label="已拨金额(元)" :value="amountUnitFormatter(item.allottedAmount)" />
  65. <CommonInfoRow label="未拨金额(元)" :value="amountUnitFormatter(item.notAllottedAmount)" />
  66. <CommonInfoRow label="申请日期" :value="item.applyTime ? formatDate(item.applyTime) : '--'" />
  67. <CommonInfoRow label="摘要" :value="item.remark" />
  68. <CommonInfoRow label="创建人" :value="item.createBy || item.createName" />
  69. <CommonInfoRow label="创建时间" :value="item.createTime ? formatDate(item.createTime) : '--'" noBorder />
  70. </CommonListCard>
  71. </template>
  72. </view>
  73. <!-- 支出列表 -->
  74. <view class="section" v-show="currentTab === 2">
  75. <uv-empty v-if="!projectStore.rebateData || projectStore.rebateData.length === 0" mode="data" text="暂无支出记录"></uv-empty>
  76. <template v-else>
  77. <CommonListCard
  78. v-for="(item, index) in projectStore.rebateData"
  79. :key="'exp'+index"
  80. :title="item.expenseNo || '支出记录'"
  81. :statusLabel="getExpenseStatusLabel(item.status)"
  82. :statusType="getExpenseStatusType(item.status)"
  83. >
  84. <CommonInfoRow label="支出金额(元)" :value="amountUnitFormatter(item.amount)" isAmount />
  85. <CommonInfoRow label="支出科目" :value="item.subject" />
  86. <CommonInfoRow label="收款人" :value="item.receiver" />
  87. <CommonInfoRow label="支出日期" :value="item.expendTime ? formatDate(item.expendTime) : '--'" />
  88. <CommonInfoRow label="摘要" :value="item.remark" />
  89. <CommonInfoRow label="创建人" :value="item.createBy || item.createName" />
  90. <CommonInfoRow label="创建时间" :value="item.createTime ? formatDate(item.createTime) : '--'" :noBorder="!(item.fileName && item.fileUrl)" />
  91. <view v-if="item.fileName && item.fileUrl" style="margin-top: 20rpx;">
  92. <CommonFileList :files="[{ fileName: item.fileName, fileUrl: item.fileUrl }]" />
  93. </view>
  94. </CommonListCard>
  95. </template>
  96. </view>
  97. </view>
  98. </view>
  99. </template>
  100. <script setup lang="ts">
  101. import { ref, watch } from 'vue';
  102. import { useProjectStore } from '@/store/modules/project';
  103. import { formatDate } from '@/utils/date';
  104. import { formatWithComma } from '@/utils/format';
  105. import { previewFile } from '@/utils/file';
  106. import CommonListCard from '@/components/ui/CommonListCard.vue';
  107. import CommonInfoRow from '@/components/ui/CommonInfoRow.vue';
  108. import CommonFileList from '@/components/ui/CommonFileList.vue';
  109. /**
  110. * 接收来自父组件的属性
  111. * projectId: 项目内码
  112. * projectType: 项目分类标识
  113. */
  114. const props = defineProps<{
  115. projectId: number;
  116. projectType: string;
  117. }>();
  118. const projectStore = useProjectStore();
  119. // 当前激活的子 Tab 索引
  120. const currentTab = ref(0);
  121. // 经费模块内部的子 Tab 配置
  122. const fundingTabs = ref([
  123. { name: '入账记录' },
  124. { name: '外拨记录' },
  125. { name: '支出记录' }
  126. ]);
  127. /**
  128. * 切换子 Tab 处理
  129. */
  130. const handleTabClick = (item: any) => {
  131. currentTab.value = item.index;
  132. };
  133. /**
  134. * 监听项目 ID 变化,触发经费数据加载
  135. */
  136. watch(() => props.projectId, (id) => {
  137. if (id) {
  138. projectStore.fetchFunding(id, props.projectType);
  139. }
  140. }, { immediate: true });
  141. /**
  142. * 金额格式化处理
  143. * 使用全局统一的千分位格式化工具
  144. */
  145. const amountUnitFormatter = (num: any) => {
  146. return formatWithComma(num);
  147. };
  148. /**
  149. * 获取项目类型名称 (内部逻辑)
  150. */
  151. const getProjectType = (code: string | number) => {
  152. if (code == 10) return '纵向项目';
  153. if (code == 20) return '横向项目';
  154. if (code == 30) return '内部项目';
  155. if (code == 40) return '重点学科';
  156. return '未知';
  157. };
  158. /**
  159. * 获取支出记录审批状态说明
  160. */
  161. const getExpenseStatusLabel = (status: string | number) => {
  162. if (status == 10) return '待审核';
  163. if (status == 20) return '已通过';
  164. if (status == 30) return '已拒绝';
  165. return '未知';
  166. };
  167. /**
  168. * 获取支出记录审批状态对应的 UI 类型
  169. */
  170. const getExpenseStatusType = (status: string | number) => {
  171. if (status == 10) return 'info';
  172. if (status == 20) return 'success';
  173. if (status == 30) return 'error';
  174. return 'default';
  175. };
  176. /**
  177. * 统一附件打开/预览处理
  178. */
  179. const handleDownload = (file: any) => {
  180. if (!file.fileUrl) return;
  181. previewFile(file.fileUrl, file.fileName);
  182. };
  183. </script>
  184. <style lang="scss" scoped>
  185. .module-container {
  186. min-height: 400rpx;
  187. position: relative;
  188. }
  189. .content-wrapper {
  190. display: flex;
  191. flex-direction: column;
  192. gap: 30rpx;
  193. }
  194. .tabs-wrapper {
  195. background-color: #fff;
  196. border-radius: 16rpx;
  197. overflow: hidden;
  198. box-shadow: 0 4rpx 16rpx rgba(0,0,0,0.03);
  199. }
  200. .summary-card {
  201. background: #fff;
  202. border-radius: 16rpx;
  203. padding: 30rpx;
  204. box-shadow: 0 4rpx 16rpx rgba(0,0,0,0.03);
  205. display: flex;
  206. flex-direction: column;
  207. gap: 20rpx;
  208. .sum-row {
  209. display: flex;
  210. justify-content: space-between;
  211. .sum-item {
  212. flex: 1;
  213. display: flex;
  214. flex-direction: column;
  215. align-items: center;
  216. .sum-label {
  217. font-size: 26rpx;
  218. color: #888;
  219. margin-bottom: 12rpx;
  220. }
  221. .sum-value {
  222. font-size: 36rpx;
  223. font-weight: bold;
  224. font-family: din;
  225. .unit {
  226. font-size: 24rpx;
  227. margin-left: 4rpx;
  228. font-weight: normal;
  229. }
  230. }
  231. .text-blue { color: #1c9bfd; }
  232. .text-green { color: #52c41a; }
  233. .text-red { color: #ff4d4f; }
  234. .text-orange { color: #faad14; }
  235. }
  236. }
  237. }
  238. .section {
  239. width: 100%;
  240. }
  241. </style>