comparison.vue 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195
  1. <template>
  2. <view class="page">
  3. <view class="filter-bar">
  4. <view class="filter-item">{{ currentMemberName }} ▾</view>
  5. <picker :range="reportIds" @change="onReportIdChange">
  6. <view class="filter-item">{{ currentReportIdLabel }}</view>
  7. </picker>
  8. <picker :range="[ 'uploadIdA', 'uploadIdB' ]" @change="onUploadIdChange">
  9. <view class="filter-item">{{ currentUploadLabel }}</view>
  10. </picker>
  11. </view>
  12. <view v-if="loading" class="loading-wrap">
  13. <text class="loading-text">加载中...</text>
  14. </view>
  15. <view v-if="error" class="error-wrap">
  16. <text class="error-text">{{ error }}</text>
  17. </view>
  18. <view v-if="comparison" class="comparison-wrap">
  19. <view class="section-title">DAN 报告对比</view>
  20. <view class="comparison-info">
  21. <view class="info-item">
  22. <text class="label">首次上传</text>
  23. <text class="value">{{ firstUploadLabel }}</text>
  24. </view>
  25. <view class="info-item">
  26. <text class="label">最近上传</text>
  27. <text class="value">{{ secondUploadLabel }}</text>
  28. </view>
  29. </view>
  30. <view v-if="deltas && deltas.length > 0" class="deltas-list">
  31. <view v-for="(delta, di) in deltas" :key="di" class="delta-item">
  32. <view :class="delta.status === 'added' ? 'delta-added' : delta.status === 'removed' ? 'delta-removed' : 'delta-modified'">
  33. <text class="delta-code">{{ delta.code || '—' }}</text>
  34. <view class="delta-detail">
  35. <text v-if="delta.status === 'added'">新增</text>
  36. <text v-if="delta.status === 'removed'">删除</text>
  37. <text v-if="delta.status === 'modified'">修改</text>
  38. <text class="delta-name">{{ delta.name1 || '' }} → {{ delta.name2 || '' }}</text>
  39. <text v-if="delta.change">({{ delta.change }})</text>
  40. <text v-if="delta.valueDelta !== null">Δ:{{ delta.valueDelta }}</text>
  41. <text v-if="delta.categoryChanged">类别变化</text>
  42. </view>
  43. </view>
  44. </view>
  45. </view>
  46. <view v-else class="empty-tip">两次上传无变化差异</view>
  47. </view>
  48. <view v-else class="empty-tip">暂无对比数据</view>
  49. </view>
  50. </template>
  51. <script>
  52. import { getFamilyMemberList, getDanReportList, compareDanReports } from '@/utils/api'
  53. export default {
  54. components: {},
  55. data() {
  56. return {
  57. memberId: null,
  58. reportIds: [],
  59. currentReportId: null,
  60. currentMemberName: '',
  61. currentReportIdLabel: '选择报告',
  62. currentUploadLabel: '选择上传',
  63. uploadIdA: null,
  64. uploadIdB: null,
  65. loading: true,
  66. error: null,
  67. comparison: null
  68. }
  69. },
  70. computed: {
  71. memberNames() {
  72. return this.memberId ? this.members.map(m => m.name || m.nickname || '').filter(Boolean) : []
  73. }
  74. },
  75. onLoad(options) {
  76. var self = this
  77. // 从 query 获取 memberId,或默认取当前登录成员
  78. if (options.memberId) {
  79. self.memberId = parseInt(options.memberId)
  80. }
  81. self.loadMembers()
  82. },
  83. methods: {
  84. loadMembers() {
  85. var self = this
  86. getFamilyMemberList({ visibleOnly: true }).then(function(res) {
  87. var arr = (res.data && res.data.list) || (res.data instanceof Array ? res.data : [])
  88. self.members = arr
  89. if (!self.memberId && arr.length > 0) {
  90. self.memberId = arr[0].id
  91. }
  92. self.loadReportIds()
  93. })
  94. },
  95. loadReportIds() {
  96. var self = this
  97. if (!self.memberId) {
  98. self.reportIds = []
  99. self.currentReportIdLabel = '选择成员后加载'
  100. return
  101. }
  102. getDanReportList({ memberId: self.memberId }).then(function(res) {
  103. self.reportIds = (res.data || []).map(function(u) { return String(u.id) })
  104. if (self.reportIds.length > 0) {
  105. self.currentReportId = self.reportIds[0]
  106. self.onReportIdChange(self.reportIds[0])
  107. }
  108. }).catch(function(e) {
  109. self.error = '获取报告列表失败'
  110. console.error(e)
  111. })
  112. },
  113. onReportIdChange(value) {
  114. this.currentReportId = value
  115. this.loadComparison()
  116. },
  117. onUploadIdChange(value, uploadType) {
  118. if (uploadType === 'A') {
  119. this.uploadIdA = value
  120. } else {
  121. this.uploadIdB = value
  122. }
  123. this.loadComparison()
  124. },
  125. loadComparison() {
  126. var self = this
  127. if (!self.uploadIdA || !self.uploadIdB) {
  128. self.comparison = null
  129. return
  130. }
  131. self.loading = true
  132. compareDanReports({ uploadIdA: self.uploadIdA, uploadIdB: self.uploadIdB }).then(function(res) {
  133. self.loading = false
  134. if (res.code !== 200) {
  135. self.error = res.message || '对比失败'
  136. return
  137. }
  138. self.comparison = res.data
  139. self.error = null
  140. }).catch(function(e) {
  141. self.loading = false
  142. self.error = '对比异常'
  143. console.error(e)
  144. })
  145. }
  146. }
  147. }
  148. </script>
  149. <style scoped>
  150. .page {
  151. min-height: 100vh;
  152. background: #F5F9FC;
  153. padding: 24rpx;
  154. box-sizing: border-box;
  155. }
  156. .filter-bar {
  157. display: flex;
  158. flex-direction: row;
  159. margin-bottom: 24rpx;
  160. }
  161. .filter-item {
  162. flex: 1;
  163. background: #FFFFFF;
  164. border: 1rpx solid #E2E8F0;
  165. border-radius: 16rpx;
  166. padding: 16rpx 24rpx;
  167. margin-right: 16rpx;
  168. font-size: 26rpx;
  169. color: #1E293B;
  170. text-align: center;
  171. }
  172. .filter-item:last-child { margin-right: 0; }
  173. /* 对比区 */
  174. .comparison-wrap { background: #FFFFFF; border-radius: 20rpx; padding: 24rpx; margin-bottom: 24rpx; box-shadow: 0 4rpx 16rpx rgba(30,41,59,0.06); }
  175. .section-title { font-size: 30rpx; font-weight: 600; color: #1E293B; margin-bottom: 20rpx; }
  176. .info-item { display: flex; align-items: center; margin-bottom: 16rpx; }
  177. .label { font-size: 24rpx; color: #64748B; margin-right: 12rpx; }
  178. .value { font-size: 24rpx; color: #1E293B; }
  179. .deltas-list { margin-top: 24rpx; }
  180. .delta-item { padding: 12rpx 0; border-bottom: 1rpx solid #E2E8F0; display: flex; align-items: center; }
  181. .delta-code { flex: 1; font-size: 24rpx; color: #64748B; }
  182. .delta-added { color: #32CD32; }
  183. .delta-removed { color: #FF6B6B; }
  184. .delta-modified { color: #FFA500; }
  185. .empty-tip { padding: 60rpx 0; text-align: center; font-size: 26rpx; color: #94A3B8; }
  186. </style>