ReimbursementForm.vue 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255
  1. <template>
  2. <view class="document-form">
  3. <uv-loading-icon v-if="loading" mode="circle" text="正在加载详情..."></uv-loading-icon>
  4. <template v-else-if="form">
  5. <!-- 1. 项目信息 -->
  6. <view class="common-section-card">
  7. <view class="section-title">项目信息</view>
  8. <view class="info-row">
  9. <text class="label">项目类型</text>
  10. <text class="value">{{ getProjectTypeName(form.projectType) }}</text>
  11. </view>
  12. <view class="info-row">
  13. <text class="label">项目名称</text>
  14. <text class="value">{{ form.projectName || '-' }}</text>
  15. </view>
  16. <view class="info-row">
  17. <text class="label">项目编号</text>
  18. <text class="value">{{ form.projectNo || '-' }}</text>
  19. </view>
  20. <view class="info-row">
  21. <text class="label">项目来源</text>
  22. <text class="value">{{ form.projectSource || '-' }}</text>
  23. </view>
  24. <view class="info-row">
  25. <text class="label">负责人</text>
  26. <text class="value">{{ form.projectIncharge || '-' }}</text>
  27. </view>
  28. <view class="info-row">
  29. <text class="label">所属科室</text>
  30. <text class="value">{{ form.projectDeptName || '-' }}</text>
  31. </view>
  32. <view class="info-row">
  33. <text class="label">经费卡</text>
  34. <text class="value">{{ cardLabel || '-' }}</text>
  35. </view>
  36. <view class="info-row">
  37. <text class="label">费用科目</text>
  38. <text class="value">{{ form.subjName || '-' }}</text>
  39. </view>
  40. <view class="info-row">
  41. <text class="label">入款/可用</text>
  42. <text class="value red-color">¥{{ formatAmount(showAmount) }} / ¥{{ formatAmount(showBalanceAmount) }}</text>
  43. </view>
  44. <view class="info-row">
  45. <text class="label">费用类型</text>
  46. <text class="value">{{ form.subSubjName || '-' }}</text>
  47. </view>
  48. <view class="info-row">
  49. <text class="label">事项</text>
  50. <text class="value">{{ form.purpose || '-' }}</text>
  51. </view>
  52. </view>
  53. <!-- 2. 报销经费 -->
  54. <view class="common-section-card mt20">
  55. <view class="section-title">报销经费</view>
  56. <view class="info-row">
  57. <text class="label">经办人</text>
  58. <text class="value">{{ form.handle || '-' }}</text>
  59. </view>
  60. <view class="info-row">
  61. <text class="label">支出金额</text>
  62. <text class="value red-color font-bold">¥{{ formatAmount(form.amount) }} 元</text>
  63. </view>
  64. </view>
  65. <!-- 3. 发票信息 -->
  66. <view class="common-section-card mt20" v-if="form.invoice?.length">
  67. <view class="section-title">发票信息</view>
  68. <view class="invoice-list">
  69. <view class="invoice-item" v-for="(inv, idx) in form.invoice" :key="idx">
  70. <view class="i-main">
  71. <text class="i-no">No.{{ inv.invoiceNo }}</text>
  72. <text class="i-amount red-color">¥{{ formatAmount(inv.amount) }}</text>
  73. </view>
  74. <view class="i-date">{{ formatDate(inv.invoiceBy) }}</view>
  75. <view class="i-files" v-if="inv.fileUrl">
  76. <text class="link-text" @click="previewFile(inv.fileUrl, inv.fileName)">查看发票文件</text>
  77. </view>
  78. </view>
  79. </view>
  80. </view>
  81. <!-- 4. 支付信息 -->
  82. <view class="common-section-card mt20" v-if="form.payment?.length">
  83. <view class="section-title">支付信息</view>
  84. <view class="payment-list">
  85. <view class="pay-item" v-for="(pay, pIdx) in form.payment" :key="pIdx">
  86. <view class="p-header">
  87. <text class="p-type">{{ getPayTypeLabel(pay.payType) }}</text>
  88. <text class="p-amount red-color">¥{{ formatAmount(pay.amount) }}</text>
  89. </view>
  90. <view class="p-row">
  91. <text class="pl">收款人/单位</text>
  92. <text class="pv">{{ pay.receiver }}</text>
  93. </view>
  94. <view class="p-row">
  95. <text class="pl">类型</text>
  96. <text class="pv">{{ getDictLabel('sci_receiver_type', pay.receiverType) }}</text>
  97. </view>
  98. </view>
  99. </view>
  100. </view>
  101. <!-- 5. 附件 -->
  102. <view class="common-section-card mt20" v-if="form.fileList?.length">
  103. <view class="section-title">附件信息</view>
  104. <view class="common-file-list">
  105. <view class="file-item" v-for="(file, fIdx) in form.fileList" :key="fIdx"
  106. v-show="file.fileUrl"
  107. @click="previewFile(file.fileUrl, file.fileName)">
  108. <view class="file-info">
  109. <text class="f-name">{{ file.fileName }}</text>
  110. <text class="f-meta">{{ file.fileType }}</text>
  111. </view>
  112. </view>
  113. </view>
  114. </view>
  115. </template>
  116. <uv-empty v-else mode="data" text="暂无数据"></uv-empty>
  117. </view>
  118. </template>
  119. <script setup lang="ts">
  120. import { ref, onMounted, watch, computed } from 'vue';
  121. import { useDocumentApi } from '@/api/document';
  122. import { useDict } from '@/hooks/useDict';
  123. import { formatDate } from '@/utils/date';
  124. import { previewFile } from '@/utils/file';
  125. import to from 'await-to-js';
  126. const props = defineProps<{
  127. code: string;
  128. }>();
  129. const { getDictLabel } = useDict('sci_receiver_type');
  130. const documentApi = useDocumentApi();
  131. const form = ref<any>(null);
  132. const loading = ref(false);
  133. const projectTypeOptions = [
  134. { dictValue: '10', dictLabel: '纵向项目' },
  135. { dictValue: '20', dictLabel: '横向项目' },
  136. { dictValue: '30', dictLabel: '内部项目' },
  137. { dictValue: '50', dictLabel: '人才类项目' },
  138. { dictValue: '60', dictLabel: '科研平台' },
  139. ];
  140. const getProjectTypeName = (val: string) => {
  141. const item = projectTypeOptions.find(opt => opt.dictValue === val);
  142. return item ? item.dictLabel : val;
  143. };
  144. const showAmount = ref(0);
  145. const showBalanceAmount = ref(0);
  146. const cardLabel = computed(() => {
  147. if (!form.value) return '';
  148. const typeMap: Record<string, string> = { '10': '上级拨款', '20': '匹配经费', '30': '横向经费' };
  149. return `${form.value.cardNo || ''}_${typeMap[form.value.type] || ''}`;
  150. });
  151. const formatAmount = (num: number | string) => {
  152. if (!num && num !== 0) return '0.00';
  153. return Number(num).toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 });
  154. };
  155. const getPayTypeLabel = (val: string) => {
  156. const map: Record<string, string> = { '10': '银行转账', '20': '现金', '30': '国库集中支付' };
  157. return map[val] || val;
  158. };
  159. const fetchData = async () => {
  160. if (!props.code) return;
  161. loading.value = true;
  162. // 1. 获取报销单详情
  163. const [err, res] = await to(documentApi.getFundExpenseById(props.code));
  164. if (!err && res?.data) {
  165. form.value = res.data;
  166. // 2. 获取经费余额情况 (对齐PC预览逻辑)
  167. if (form.value.projectId && form.value.subjCode) {
  168. const balanceParams = {
  169. projectId: form.value.projectId,
  170. projectType: form.value.projectType,
  171. subjCode: form.value.subjCode,
  172. type: form.value.type,
  173. };
  174. const [bErr, bRes] = await to(documentApi.getSubjAmount(balanceParams));
  175. if (!bErr && bRes?.data) {
  176. showAmount.value = bRes.data.amount || 0;
  177. showBalanceAmount.value = bRes.data.balanceAmount || 0;
  178. }
  179. }
  180. }
  181. loading.value = false;
  182. };
  183. onMounted(() => {
  184. fetchData();
  185. });
  186. watch(() => props.code, () => {
  187. fetchData();
  188. });
  189. </script>
  190. <style lang="scss" scoped>
  191. @import "./common.scss";
  192. .invoice-list {
  193. .invoice-item {
  194. padding: 20rpx 0;
  195. border-bottom: 1rpx solid #f5f5f5;
  196. &:last-child { border-bottom: none; }
  197. .i-main {
  198. display: flex;
  199. justify-content: space-between;
  200. .i-no { font-size: 28rpx; color: #333; font-weight: 500; }
  201. .i-amount { font-size: 28rpx; color: #ff4d4f; font-weight: bold; }
  202. }
  203. .i-date { font-size: 24rpx; color: #999; margin-top: 8rpx; }
  204. .i-files { margin-top: 10rpx; }
  205. }
  206. }
  207. .payment-list {
  208. .pay-item {
  209. background: #fcfcfc;
  210. padding: 20rpx;
  211. border-radius: 8rpx;
  212. margin-bottom: 15rpx;
  213. .p-header {
  214. display: flex;
  215. justify-content: space-between;
  216. margin-bottom: 12rpx;
  217. .p-type { font-size: 26rpx; background: #e6f7ff; color: #1890ff; padding: 2rpx 12rpx; border-radius: 4rpx; }
  218. .p-amount { font-size: 28rpx; color: #333; font-weight: bold; }
  219. }
  220. .p-row {
  221. display: flex;
  222. justify-content: space-between;
  223. margin-top: 6rpx;
  224. font-size: 24rpx;
  225. .pl { color: #999; }
  226. .pv { color: #555; }
  227. }
  228. }
  229. }
  230. .link-text { color: #007aff; font-size: 24rpx; }
  231. .font-bold { font-weight: bold; }
  232. </style>