Эх сурвалжийг харах

feat(health): 新增 MemberChipPicker 多选成员选择组件,修复 health-plan-summary 缺失组件编译失败

liaoxg 2 долоо хоног өмнө
parent
commit
fd987923e4

+ 233 - 0
cfc-frontend/components/MemberChipPicker.vue

@@ -0,0 +1,233 @@
+<template>
+  <view class="member-chip-picker">
+    <view v-if="loading" class="mcp-loading">
+      <text class="mcp-loading-text">加载成员中...</text>
+    </view>
+
+    <view v-else-if="members.length > 0" class="mcp-chips">
+      <view
+        class="mcp-chip"
+        v-for="(m, i) in members"
+        :key="getMemberKey(m, i)"
+        :class="{ 'mcp-chip-on': isSelected(m.memberId) }"
+        @tap="toggleMember(m.memberId)"
+      >
+        <image
+          v-if="m.avatar"
+          class="mcp-chip-avatar"
+          :src="m.avatar"
+          mode="aspectFill"
+        />
+        <view v-else class="mcp-chip-avatar mcp-chip-avatar-default">
+          <text class="mcp-chip-avatar-text">{{ getInitial(m) }}</text>
+        </view>
+        <text class="mcp-chip-text">{{ m.name || m.nickname || '成员' }}</text>
+      </view>
+
+      <view class="mcp-chip mcp-chip-add" @tap="goAddMember">
+        <text class="mcp-chip-add-icon">+</text>
+        <text class="mcp-chip-text">添加</text>
+      </view>
+    </view>
+
+    <view v-else class="mcp-empty">
+      <text class="mcp-empty-text">暂无家庭成员</text>
+      <view class="mcp-empty-btn" @tap="goAddMember">+ 去添加成员</view>
+    </view>
+  </view>
+</template>
+
+<script>
+import { getFamilyMemberList } from '@/utils/api.js'
+
+export default {
+  name: 'MemberChipPicker',
+  props: {
+    // 已选中的成员 id 数组(字符串)
+    selectedIds: {
+      type: Array,
+      default: function() { return [] }
+    }
+  },
+  data: function() {
+    return {
+      members: [],
+      innerSelectedIds: [],
+      loading: false,
+      loaded: false
+    }
+  },
+  watch: {
+    selectedIds: {
+      handler: function(val) {
+        this.innerSelectedIds = (val || []).map(function(id) { return String(id) })
+      },
+      immediate: true
+    }
+  },
+  created: function() {
+    this.reload()
+  },
+  methods: {
+    // ——— :key 方法(避免模板内运算符表达式) ———
+    getMemberKey: function(m, i) {
+      return String(m.memberId || m.id || i)
+    },
+    // ——— 首字(无头像兜底) ———
+    getInitial: function(m) {
+      var name = m.name || m.nickname || ''
+      return name ? name.substring(0, 1) : '?'
+    },
+    isSelected: function(id) {
+      return this.innerSelectedIds.indexOf(String(id)) >= 0
+    },
+    // ——— 加载家庭成员 ———
+    reload: function() {
+      var self = this
+      this.loading = true
+      getFamilyMemberList({ visibleOnly: true }).then(function(res) {
+        self.loading = false
+        if (res && res.code === 200 && res.data) {
+          var raw = res.data instanceof Array ? res.data : ((res.data && res.data.list) || [])
+          var list = []
+          for (var i = 0; i < raw.length; i++) {
+            var m = raw[i]
+            // 同时输出 id/memberId、name/nickname,兼容父组件对成员字段的不同取法
+            list.push({
+              id: m.id,
+              memberId: m.memberId !== undefined ? m.memberId : m.id,
+              name: m.name || m.nickname,
+              nickname: m.nickname || m.name,
+              avatar: m.avatar || '',
+              relationship: m.relationship || m.relativeLabel || m.roleLabel || ''
+            })
+          }
+          self.members = list
+          self.emitState()
+        } else {
+          self.members = []
+          self.emitState()
+        }
+      }).catch(function() {
+        self.loading = false
+        self.members = []
+      })
+    },
+    // ——— 切换选中态 ———
+    toggleMember: function(id) {
+      var sid = String(id)
+      var idx = this.innerSelectedIds.indexOf(sid)
+      if (idx >= 0) {
+        this.innerSelectedIds.splice(idx, 1)
+      } else {
+        this.innerSelectedIds.push(sid)
+      }
+      this.emitState()
+    },
+    // ——— 通知父组件 ———
+    emitState: function() {
+      this.$emit('change', {
+        selectedIds: this.innerSelectedIds.slice(),
+        members: this.members.slice()
+      })
+      if (!this.loaded) {
+        this.loaded = true
+        this.$emit('loaded', {
+          selectedIds: this.innerSelectedIds.slice(),
+          members: this.members.slice()
+        })
+      }
+    },
+    // ——— 添加成员 ———
+    goAddMember: function() {
+      this.$emit('add-member')
+      uni.navigateTo({ url: '/pages/family/add-member' })
+    }
+  }
+}
+</script>
+
+<style scoped>
+.member-chip-picker {
+  width: 100%;
+}
+.mcp-loading {
+  padding: 30rpx 0;
+  text-align: center;
+}
+.mcp-loading-text {
+  font-size: 26rpx;
+  color: #9CA3AF;
+}
+.mcp-chips {
+  display: flex;
+  flex-wrap: wrap;
+  align-items: center;
+}
+.mcp-chip {
+  display: flex;
+  align-items: center;
+  padding: 12rpx 24rpx;
+  margin: 0 16rpx 16rpx 0;
+  background: #F3F4F6;
+  border-radius: 999rpx;
+  border: 2rpx solid transparent;
+}
+.mcp-chip-on {
+  background: #FFEDD5;
+  border-color: #F97316;
+}
+.mcp-chip-avatar {
+  width: 40rpx;
+  height: 40rpx;
+  border-radius: 50%;
+  margin-right: 10rpx;
+  overflow: hidden;
+}
+.mcp-chip-avatar-default {
+  background: #E5E7EB;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+.mcp-chip-avatar-text {
+  font-size: 22rpx;
+  color: #6B7280;
+}
+.mcp-chip-text {
+  font-size: 26rpx;
+  color: #374151;
+}
+.mcp-chip-on .mcp-chip-text {
+  color: #C2410C;
+  font-weight: 500;
+}
+.mcp-chip-add {
+  background: transparent;
+  border: 2rpx dashed #D1D5DB;
+  color: #9CA3AF;
+}
+.mcp-chip-add-icon {
+  font-size: 28rpx;
+  margin-right: 6rpx;
+  color: #9CA3AF;
+}
+.mcp-empty {
+  padding: 40rpx 0;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+}
+.mcp-empty-text {
+  font-size: 26rpx;
+  color: #9CA3AF;
+  margin-bottom: 16rpx;
+}
+.mcp-empty-btn {
+  padding: 14rpx 40rpx;
+  background: #F97316;
+  color: #FFFFFF;
+  font-size: 26rpx;
+  border-radius: 40rpx;
+}
+</style>