|
|
@@ -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>
|