ProjectMembers.vue 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185
  1. <template>
  2. <view class="module-container">
  3. <!-- 成员列表容器 -->
  4. <view class="member-list" v-if="memberList?.length">
  5. <view class="member-item" v-for="(row, index) in memberList" :key="index">
  6. <!-- 默认头像装饰 -->
  7. <image src="/static/imgs/tabBar/my.png" mode="aspectFit" class="avatar"></image>
  8. <view class="member-info">
  9. <!-- 姓名与身份标签 -->
  10. <view class="header">
  11. <text class="m-name">{{ row.memberName }}</text>
  12. <text class="m-tag m-role">{{ getRoleName(row.projectRole) }}</text>
  13. <text class="m-tag m-type">{{ getMemberTypeName(row.memberType) }}</text>
  14. </view>
  15. <!-- 详情信息区块 -->
  16. <view class="m-detail">
  17. <CommonInfoRow
  18. label="学位"
  19. :value="getDictLabel('sci_academic_degree', row.degree) || '-'"
  20. />
  21. <CommonInfoRow
  22. label="职称"
  23. :value="row.technicalTitle || '-'"
  24. />
  25. <CommonInfoRow
  26. label="所属部门"
  27. :value="row.deptName || '-'"
  28. />
  29. <CommonInfoRow
  30. label="负责内容"
  31. :value="row.responsibleContent || '-'"
  32. />
  33. <CommonInfoRow
  34. label="贡献率(%)"
  35. :value="row.contributionRate || '-'"
  36. />
  37. <CommonInfoRow
  38. label="签署顺序"
  39. :value="row.order || '-'"
  40. noBorder
  41. />
  42. </view>
  43. </view>
  44. </view>
  45. </view>
  46. <!-- 空状态展示 -->
  47. <view v-else class="empty-wrap">
  48. <uv-empty mode="data" text="暂无成员信息"></uv-empty>
  49. </view>
  50. </view>
  51. </template>
  52. <script setup lang="ts">
  53. import { useDict } from '@/hooks/useDict';
  54. import { computed } from 'vue';
  55. import type { ProjectMember } from '@/types/project';
  56. import { projectRoleOptions, memberTypeOptions } from '@/constants';
  57. import CommonInfoRow from '@/components/ui/CommonInfoRow.vue';
  58. /**
  59. * 接收父组件传递的属性
  60. * projectId: 项目内码
  61. * projectType: 项目分类标识
  62. * projectData: 包含完整成员列表的项目基本信息对象
  63. */
  64. const props = defineProps<{
  65. projectId: number;
  66. projectType: string;
  67. projectData: any;
  68. }>();
  69. // 使用字典 hooks 获取学位职称的明细标签
  70. const { getDictLabel } = useDict('sci_academic_degree');
  71. /**
  72. * 计算属性:成员列表
  73. * 针对不同类型的项目,后端返回的成员列表字段名存差异,此处统一进行向下兼容提取
  74. */
  75. const memberList = computed<ProjectMember[]>(() => {
  76. return props.projectData?.memberList ||
  77. props.projectData?.member ||
  78. [];
  79. });
  80. /**
  81. * 获取项目角色显示文本
  82. * @param roleVal 字典数值形式的状态
  83. * @returns 对应的中文解释说明
  84. */
  85. const getRoleName = (roleVal: string) => {
  86. const match = projectRoleOptions.find(o => o.dictValue === roleVal);
  87. return match ? match.dictLabel : '未知角色';
  88. };
  89. /**
  90. * 获取人员类型显示文本
  91. * @param typeVal 类型代码值
  92. * @returns 对应人员类型的文字标识(本院/非本院...)
  93. */
  94. const getMemberTypeName = (typeVal: string) => {
  95. const match = memberTypeOptions.find(o => o.dictValue === typeVal);
  96. return match ? match.dictLabel : '未知类型';
  97. };
  98. </script>
  99. <style lang="scss" scoped>
  100. .module-container {
  101. padding: 30rpx;
  102. background-color: #fff;
  103. border-radius: 16rpx;
  104. box-shadow: 0 4rpx 16rpx rgba(0,0,0,0.03);
  105. .member-list {
  106. .member-item {
  107. display: flex;
  108. align-items: flex-start;
  109. padding: 30rpx 0;
  110. border-bottom: 2rpx dashed #f5f5f5;
  111. &:last-child { border-bottom: none; }
  112. .avatar {
  113. width: 88rpx;
  114. height: 88rpx;
  115. border-radius: 50%;
  116. background-color: #f0f4ff;
  117. margin-right: 24rpx;
  118. border: 2rpx solid #e1e8ff;
  119. }
  120. .member-info {
  121. flex: 1;
  122. display: flex;
  123. flex-direction: column;
  124. .header {
  125. display: flex;
  126. align-items: center;
  127. margin-bottom: 16rpx;
  128. flex-wrap: wrap;
  129. gap: 12rpx;
  130. .m-name {
  131. font-size: 32rpx;
  132. color: #343A3F;
  133. font-weight: bold;
  134. }
  135. .m-tag {
  136. font-size: 20rpx;
  137. padding: 4rpx 12rpx;
  138. border-radius: 6rpx;
  139. }
  140. .m-role {
  141. background-color: #e6f4ff;
  142. color: #1677ff;
  143. border: 2rpx solid #91caff;
  144. }
  145. .m-type {
  146. background-color: #f6ffed;
  147. color: #52c41a;
  148. border: 2rpx solid #b7eb8f;
  149. }
  150. }
  151. .m-detail {
  152. background-color: #fafbfc;
  153. padding: 16rpx;
  154. border-radius: 12rpx;
  155. }
  156. }
  157. }
  158. }
  159. .empty-wrap {
  160. padding: 60rpx 0;
  161. display: flex;
  162. justify-content: center;
  163. }
  164. }
  165. </style>