bodyfat-confirm.vue 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139
  1. <template>
  2. <scroll-view class="content" scroll-y>
  3. <!-- 已识别指标 -->
  4. <view class="card">
  5. <view class="card-accent"></view>
  6. <view class="card-body">
  7. <text class="card-title">AI 已识别指标</text>
  8. <text class="card-subtitle">请确认并补充成员信息后保存</text>
  9. <view v-if="metrics.length === 0" class="empty-state">
  10. <text>未识别到指标,请重新拍照</text>
  11. </view>
  12. <view v-for="(m, i) in metrics" :key="i" class="metric-row">
  13. <text class="metric-name">{{ m.name }}</text>
  14. <view class="metric-val-wrap">
  15. <input class="metric-val" type="digit" v-model="m.value" placeholder="数值" />
  16. <text class="metric-unit">{{ m.unit || '' }}</text>
  17. </view>
  18. </view>
  19. </view>
  20. </view>
  21. <!-- 绑定成员 -->
  22. <view class="card">
  23. <view class="card-accent"></view>
  24. <view class="card-body">
  25. <text class="card-title">绑定成员</text>
  26. <picker mode="selector" :range="memberList" range-key="nickname" @change="onMemberChange">
  27. <view class="picker-row">
  28. <text class="picker-text">{{ currentMember ? currentMember.nickname : '选择成员' }}</text>
  29. <text class="picker-arrow">›</text>
  30. </view>
  31. </picker>
  32. </view>
  33. </view>
  34. <view class="save-bar">
  35. <button class="save-btn" :disabled="saving" @click="save">{{ saving ? '保存中...' : '保存指标' }}</button>
  36. </view>
  37. </scroll-view>
  38. </template>
  39. <script>
  40. import { saveBodyFatMetrics } from '../../utils/api.js'
  41. export default {
  42. data() {
  43. return {
  44. metrics: [],
  45. memberList: [],
  46. currentMember: null,
  47. saving: false
  48. }
  49. },
  50. onLoad: function(options) {
  51. if (options.metrics) {
  52. try { this.metrics = JSON.parse(decodeURIComponent(options.metrics)) } catch (e) {}
  53. }
  54. this.loadMembers()
  55. },
  56. methods: {
  57. loadMembers: function() {
  58. var self = this
  59. var token = uni.getStorageSync('token')
  60. uni.request({
  61. url: config.API_BASE_URL + '/api/family/member/list',
  62. method: 'POST',
  63. data: { visibleOnly: true },
  64. header: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + token },
  65. success: function(res) {
  66. if (res.data && res.data.code === 200 && res.data.data) {
  67. self.memberList = res.data.data
  68. }
  69. }
  70. })
  71. },
  72. onMemberChange: function(e) {
  73. var idx = parseInt(e.detail.value)
  74. if (idx >= 0 && idx < this.memberList.length) {
  75. this.currentMember = this.memberList[idx]
  76. }
  77. },
  78. goBack: function() {
  79. uni.navigateBack()
  80. },
  81. save: function() {
  82. var self = this
  83. if (!self.currentMember) {
  84. uni.showToast({ title: '请选择成员', icon: 'none' })
  85. return
  86. }
  87. if (self.metrics.length === 0) {
  88. uni.showToast({ title: '无可保存的指标', icon: 'none' })
  89. return
  90. }
  91. self.saving = true
  92. saveBodyFatMetrics({
  93. memberId: self.currentMember.id,
  94. metrics: self.metrics
  95. }).then(function(res) {
  96. self.saving = false
  97. if (res && res.code === 200) {
  98. uni.showToast({ title: '保存成功', icon: 'success' })
  99. setTimeout(function() { uni.navigateBack() }, 1000)
  100. } else {
  101. uni.showToast({ title: (res && res.message) || '保存失败', icon: 'none' })
  102. }
  103. }).catch(function() {
  104. self.saving = false
  105. uni.showToast({ title: '网络错误', icon: 'none' })
  106. })
  107. }
  108. }
  109. }
  110. </script>
  111. <style scoped>
  112. .page { min-height: 100vh; background: #F5F7FA; padding-bottom: 140rpx; }
  113. .content { height: calc(100vh - 100rpx); }
  114. .card { margin: 24rpx 30rpx; background: #fff; border-radius: 16rpx; overflow: hidden; }
  115. .card-accent { height: 6rpx; background: linear-gradient(90deg, #F97316, #F59E0B); }
  116. .card-body { padding: 24rpx; }
  117. .card-title { font-size: 30rpx; font-weight: bold; color: #333; display: block; }
  118. .card-subtitle { font-size: 24rpx; color: #999; margin-top: 6rpx; display: block; }
  119. .empty-state { padding: 40rpx 0; text-align: center; color: #999; font-size: 26rpx; }
  120. .metric-row { display: flex; align-items: center; justify-content: space-between; padding: 20rpx 0; border-bottom: 1rpx solid #F0F0F0; }
  121. .metric-row:last-child { border-bottom: none; }
  122. .metric-name { font-size: 28rpx; color: #333; }
  123. .metric-val-wrap { display: flex; align-items: center; }
  124. .metric-val { height: 56rpx; background: #F5F7FA; border-radius: 10rpx; padding: 0 16rpx; font-size: 28rpx; width: 160rpx; text-align: right; }
  125. .metric-unit { font-size: 26rpx; color: #999; margin-left: 8rpx; }
  126. .picker-row { display: flex; align-items: center; justify-content: space-between; padding: 16rpx 0; }
  127. .picker-text { font-size: 28rpx; color: #333; }
  128. .picker-arrow { font-size: 28rpx; color: #999; }
  129. .save-bar { position: fixed; bottom: 0; left: 0; right: 0; background: #fff; padding: 20rpx 30rpx; padding-bottom: calc(20rpx + env(safe-area-inset-bottom)); }
  130. .save-btn { width: 100%; background: #F97316; color: #fff; border-radius: 44rpx; height: 84rpx; line-height: 84rpx; font-size: 32rpx; }
  131. .save-btn[disabled] { opacity: 0.6; }
  132. </style>