PlanListForm.vue 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186
  1. <template>
  2. <view class="document-form">
  3. <uv-loading-icon v-if="state.loading" mode="circle" text="正在加载项目计划详情..."></uv-loading-icon>
  4. <template v-else-if="state.form">
  5. <!-- 基本信息 -->
  6. <view class="common-section-card">
  7. <view class="section-title">基本信息</view>
  8. <view class="info-row"><text class="label">项目名称</text><text class="value">{{ state.form.projectName }}</text></view>
  9. <view class="info-row">
  10. <text class="label">项目级别</text>
  11. <text class="value">{{ getDictLabel('sci_pjt_level', state.form.projectLevel) }}</text>
  12. </view>
  13. <view class="info-row"><text class="label">项目来源</text><text class="value">{{ state.form.projectSource || '-' }}</text></view>
  14. <view class="info-row">
  15. <text class="label">计划日期</text>
  16. <text class="value">{{ formatDate(state.form.planStartDate) }} 至 {{ formatDate(state.form.planEndDate) }}</text>
  17. </view>
  18. <view class="info-row">
  19. <text class="label">研究类型</text>
  20. <text class="value">{{ getDictLabel('sci_pjt_type', state.form.studyType) }}</text>
  21. </view>
  22. <view class="info-row"><text class="label">所属科室</text><text class="value">{{ state.form.deptName }}</text></view>
  23. <view class="info-row"><text class="label">项目负责人</text><text class="value">{{ state.form.projectLeaderName }}</text></view>
  24. <view class="info-row"><text class="label">负责人电话</text><text class="value">{{ state.form.projectLeaderPhone || '-' }}</text></view>
  25. <view class="info-row"><text class="label">负责人邮箱</text><text class="value">{{ state.form.projectLeaderMail || '-' }}</text></view>
  26. <view class="info-row"><text class="label">备注</text><text class="value">{{ state.form.remark || '-' }}</text></view>
  27. </view>
  28. <!-- 成员信息 -->
  29. <view class="common-section-card mt20" v-if="state.form.memberList?.length">
  30. <view class="section-title">成员信息</view>
  31. <view class="member-list">
  32. <view class="member-item" v-for="(row, index) in state.form.memberList" :key="index">
  33. <view class="member-header">
  34. <view class="m-left">
  35. <text class="m-name">{{ row.memberName }}</text>
  36. <text class="m-type-tag" v-if="row.memberType">
  37. {{ row.memberType === '10' ? '本院人员' : row.memberType === '20' ? '非本院人员' : '研究生' }}
  38. </text>
  39. </view>
  40. <text class="m-tag">{{ row.projectRole === '10' ? '负责人' : row.projectRole === '20' ? '主要参与人' : '一般参与人' }}</text>
  41. </view>
  42. <view class="m-body">
  43. <view class="m-line" v-if="row.deptName"><text class="l">所属科室:</text><text class="v">{{ row.deptName }}</text></view>
  44. <view class="m-line" v-if="row.degree || row.technicalTitle">
  45. <text class="l">职称学位:</text>
  46. <text class="v">{{ getDictLabel('sci_technical_title', row.technicalTitle) || '-' }} / {{ getDictLabel('sci_academic_degree', row.degree) || '-' }}</text>
  47. </view>
  48. <view class="m-line" v-if="row.responsibleContent"><text class="l">负责内容:</text><text class="v">{{ row.responsibleContent }}</text></view>
  49. <view class="m-line" v-if="row.serialNum"><text class="l">签署顺序:</text><text class="v">{{ row.serialNum }}</text></view>
  50. </view>
  51. </view>
  52. </view>
  53. </view>
  54. <!-- 预算信息 -->
  55. <view class="common-section-card mt20" v-if="state.form.fundsList?.length">
  56. <view class="section-title">预算信息</view>
  57. <view class="funds-list">
  58. <view class="funds-item" v-for="(item, index) in state.form.fundsList" :key="index">
  59. <view class="f-row">
  60. <text class="f-name">{{ item.fundsSubjName }}</text>
  61. <text class="f-class">{{ item.fundsClass === '10' ? '直接费用' : '间接费用/管理费' }}</text>
  62. </view>
  63. <view class="f-grid">
  64. <view class="g-item"><text class="gl">财政拨款(万元)</text><text class="gv">{{ formatAmount(item.projectFundsAmount) }}</text></view>
  65. <view class="g-item"><text class="gl">匹配经费(万元)</text><text class="gv">{{ formatAmount(item.otherFundsAmount) }}</text></view>
  66. <view class="g-item"><text class="gl">自筹经费(万元)</text><text class="gv">{{ formatAmount(item.raiseFundsAmount) }}</text></view>
  67. <view class="g-item highlight"><text class="gl">预算金额(万元)</text><text class="gv">{{ formatAmount(item.totalFundsAmount) }}</text></view>
  68. </view>
  69. </view>
  70. </view>
  71. </view>
  72. <!-- 附件信息 -->
  73. <view class="common-section-card mt20" v-if="state.form.fileList?.length">
  74. <view class="section-title">附件资料</view>
  75. <view class="common-file-list">
  76. <view class="file-item" v-for="(file, index) in state.form.fileList" :key="index" @click="previewFile(file.fileUrl, file.fileName)">
  77. <view class="file-info">
  78. <text class="f-name">{{ file.fileName }}</text>
  79. <view class="f-meta">
  80. <text class="f-type">{{ file.fileType || '附件' }}</text>
  81. </view>
  82. </view>
  83. </view>
  84. </view>
  85. </view>
  86. </template>
  87. <uv-empty v-else mode="data" text="暂无数据"></uv-empty>
  88. </view>
  89. </template>
  90. <script setup lang="ts">
  91. import { reactive, onMounted, watch, nextTick } from 'vue';
  92. import { useDict } from '@/hooks/useDict';
  93. import { useDocumentApi } from '@/api/document';
  94. import { formatDate } from '@/utils/date';
  95. import { formatAmount } from '@/utils/format';
  96. import { previewFile } from '@/utils/file';
  97. import to from 'await-to-js';
  98. const props = defineProps<{
  99. code: string;
  100. }>();
  101. const { getDictLabel } = useDict('sci_pjt_level', 'sci_pjt_type', 'sci_academic_degree', 'sci_technical_title');
  102. const documentApi = useDocumentApi();
  103. const state = reactive({
  104. form: null as any,
  105. loading: false
  106. });
  107. const initForm = async (code: string) => {
  108. if (!code) return;
  109. state.loading = true;
  110. const [err, res] = await to(documentApi.getVerticalByCode(code));
  111. if (!err && res?.data) {
  112. await nextTick();
  113. state.form = res.data;
  114. // 数据标准化,确保列表字段不为 null
  115. state.form.fileList = state.form.fileList || [];
  116. state.form.fundsList = state.form.fundsList || [];
  117. state.form.memberList = state.form.memberList || [];
  118. state.form.unitsList = state.form.unitsList || [];
  119. }
  120. state.loading = false;
  121. };
  122. onMounted(() => {
  123. // 已经在 watch immediate 中调用,这里可以省略或保持一致
  124. });
  125. watch(() => props.code, (val) => {
  126. initForm(val);
  127. }, { immediate: true });
  128. defineExpose({
  129. initForm
  130. });
  131. </script>
  132. <style lang="scss" scoped>
  133. @import "./common.scss";
  134. .member-header {
  135. display: flex;
  136. justify-content: space-between;
  137. align-items: center;
  138. margin-bottom: 12rpx;
  139. .m-left {
  140. display: flex;
  141. align-items: center;
  142. gap: 12rpx;
  143. }
  144. .m-type-tag {
  145. font-size: 20rpx;
  146. padding: 2rpx 8rpx;
  147. background-color: #f0f7ff;
  148. color: #007aff;
  149. border-radius: 4rpx;
  150. border: 1px solid #d0e7ff;
  151. }
  152. }
  153. .m-line {
  154. display: flex;
  155. font-size: 26rpx;
  156. line-height: 44rpx;
  157. .l {
  158. color: #909399;
  159. flex-shrink: 0;
  160. width: 140rpx;
  161. }
  162. .v {
  163. color: #303133;
  164. word-break: break-all;
  165. }
  166. }
  167. </style>