hookehuyr

feat(家庭管理): 添加家庭成员管理功能并优化角色显示

- 在MyFamily页面添加成员管理弹窗,支持创建者查看和移除成员
- 将JoinFamily页面的角色id从英文改为中文显示
- 为家庭创建者添加特殊标记和操作权限
- 区分退出家庭和解散家庭的不同操作
...@@ -229,16 +229,16 @@ const handleBlur = (index) => { ...@@ -229,16 +229,16 @@ const handleBlur = (index) => {
229 }; 229 };
230 230
231 const familyRoles = [ 231 const familyRoles = [
232 - { id: 'husband', label: '丈夫' }, 232 + { id: '丈夫', label: '丈夫' },
233 - { id: 'wife', label: '妻子' }, 233 + { id: '妻子', label: '妻子' },
234 - { id: 'son', label: '儿子' }, 234 + { id: '儿子', label: '儿子' },
235 - { id: 'daughter-in-law', label: '儿媳' }, 235 + { id: '女儿', label: '女儿' },
236 - { id: 'son-in-law', label: '女婿' }, 236 + { id: '女婿', label: '女婿' },
237 - { id: 'daughter', label: '女儿' }, 237 + { id: '儿媳', label: '儿媳' },
238 - { id: 'grandson', label: '孙子' }, 238 + { id: '孙子', label: '孙子' },
239 - { id: 'maternal-grandson', label: '外孙' }, 239 + { id: '外孙', label: '外孙' },
240 - { id: 'granddaughter', label: '孙女' }, 240 + { id: '孙女', label: '孙女' },
241 - { id: 'maternal-granddaughter', label: '外孙女' } 241 + { id: '外孙女', label: '外孙女' }
242 ]; 242 ];
243 243
244 const isComplete = computed(() => { 244 const isComplete = computed(() => {
......
1 <!-- 1 <!--
2 * @Date: 2022-09-19 14:11:06 2 * @Date: 2022-09-19 14:11:06
3 * @LastEditors: hookehuyr hookehuyr@gmail.com 3 * @LastEditors: hookehuyr hookehuyr@gmail.com
4 - * @LastEditTime: 2025-09-02 21:45:14 4 + * @LastEditTime: 2025-09-03 00:11:18
5 * @FilePath: /lls_program/src/pages/MyFamily/index.vue 5 * @FilePath: /lls_program/src/pages/MyFamily/index.vue
6 * @Description: 我的家庭页面 - 展示用户加入的家庭列表 6 * @Description: 我的家庭页面 - 展示用户加入的家庭列表
7 --> 7 -->
...@@ -24,6 +24,14 @@ ...@@ -24,6 +24,14 @@
24 当前家庭 24 当前家庭
25 </view> 25 </view>
26 26
27 + <!-- 创建者标记 -->
28 + <view
29 + v-if="family.is_my"
30 + class="absolute top-2 left-2 bg-yellow-500 text-white text-xs px-2 py-1 rounded-sm z-10"
31 + >
32 + 创建者
33 + </view>
34 +
27 <!-- 封面图 --> 35 <!-- 封面图 -->
28 <image 36 <image
29 :src="family.avatar_url || defaultFamilyCoverSvg" 37 :src="family.avatar_url || defaultFamilyCoverSvg"
...@@ -71,6 +79,13 @@ ...@@ -71,6 +79,13 @@
71 <!-- 操作按钮 --> 79 <!-- 操作按钮 -->
72 <view class="flex gap-3 justify-end"> 80 <view class="flex gap-3 justify-end">
73 <view 81 <view
82 + v-if="family.is_my"
83 + @tap="showMemberManagement(family)"
84 + class="px-4 py-2 bg-green-500 text-white text-sm rounded-lg"
85 + >
86 + 查看成员
87 + </view>
88 + <view
74 v-if="!family.is_in" 89 v-if="!family.is_in"
75 @tap="switchToFamily(family.id)" 90 @tap="switchToFamily(family.id)"
76 class="px-4 py-2 bg-blue-500 text-white text-sm rounded-lg" 91 class="px-4 py-2 bg-blue-500 text-white text-sm rounded-lg"
...@@ -78,10 +93,10 @@ ...@@ -78,10 +93,10 @@
78 切换到此家庭 93 切换到此家庭
79 </view> 94 </view>
80 <view 95 <view
81 - @tap="exitFamily(family.id)" 96 + @tap="family.is_my ? dissolveFamily(family.id) : exitFamily(family.id)"
82 class="px-4 py-2 bg-red-500 text-white text-sm rounded-lg" 97 class="px-4 py-2 bg-red-500 text-white text-sm rounded-lg"
83 > 98 >
84 - 退出家庭 99 + {{ family.is_my ? '解散家庭' : '退出家庭' }}
85 </view> 100 </view>
86 </view> 101 </view>
87 </view> 102 </view>
...@@ -106,6 +121,95 @@ ...@@ -106,6 +121,95 @@
106 </view> 121 </view>
107 </view> 122 </view>
108 123
124 + <!-- 成员管理弹窗 -->
125 + <nut-popup
126 + v-model:visible="showMemberPopup"
127 + position="bottom"
128 + :style="{ height: '60%' }"
129 + round
130 + closeable
131 + @close="closeMemberPopup"
132 + >
133 + <view class="h-full flex flex-col">
134 + <!-- 固定标题区域 -->
135 + <view class="p-4 pb-2 border-b border-gray-100">
136 + <view class="text-lg font-bold mb-2 text-center">家庭成员管理</view>
137 + <view class="text-sm text-gray-600 text-center">{{ currentFamily?.name }}</view>
138 + </view>
139 +
140 + <!-- 可滚动的成员列表区域 -->
141 + <view class="flex-1 overflow-hidden">
142 + <scroll-view :scroll-y="true" class="h-full">
143 + <view class="px-4 py-3">
144 + <view class="space-y-3">
145 + <view
146 + v-for="member in mockMembers"
147 + :key="member.id"
148 + class="bg-gray-50 rounded-lg p-3"
149 + @tap="toggleMemberSelection(member.id)"
150 + >
151 + <view class="flex items-center">
152 + <!-- 左侧:选择框 + 头像 + 信息 -->
153 + <view class="flex items-center flex-1 min-w-0">
154 + <!-- 选择框 -->
155 + <view
156 + class="w-5 h-5 rounded border-2 flex items-center justify-center flex-shrink-0"
157 + :class="selectedMembers.includes(member.id) ? 'bg-blue-500 border-blue-500' : 'border-gray-300'"
158 + >
159 + <view
160 + v-if="selectedMembers.includes(member.id)"
161 + class="w-2 h-2 bg-white rounded-sm"
162 + ></view>
163 + </view>
164 +
165 + <!-- 头像 -->
166 + <image
167 + :src="member.avatar"
168 + class="w-10 h-10 rounded-full ml-3 object-cover flex-shrink-0"
169 + />
170 +
171 + <!-- 昵称和角色 -->
172 + <view class="ml-3 flex-1 min-w-0">
173 + <view class="font-medium text-sm truncate">{{ member.nickname }}</view>
174 + <view class="text-xs text-gray-500 truncate">{{ member.role }}</view>
175 + </view>
176 + </view>
177 +
178 + <!-- 右侧:创建者标识 -->
179 + <view
180 + v-if="member.is_owner"
181 + class="ml-3 px-2 py-1 bg-yellow-100 text-yellow-600 text-xs rounded flex-shrink-0"
182 + >
183 + 创建者
184 + </view>
185 + </view>
186 + </view>
187 + </view>
188 + </view>
189 + </scroll-view>
190 + </view>
191 +
192 + <!-- 固定底部按钮区域 -->
193 + <view class="p-4 pt-2 border-t border-gray-100">
194 + <view class="flex gap-3">
195 + <view
196 + @tap="closeMemberPopup"
197 + class="flex-1 py-3 bg-gray-200 text-gray-700 text-center rounded-lg"
198 + >
199 + 关闭
200 + </view>
201 + <view
202 + @tap="removeSelectedMembers"
203 + class="flex-1 py-3 bg-red-500 text-white text-center rounded-lg"
204 + :class="selectedMembers.length === 0 ? 'opacity-50' : ''"
205 + >
206 + 移除 ({{ selectedMembers.length }})
207 + </view>
208 + </view>
209 + </view>
210 + </view>
211 + </nut-popup>
212 +
109 <!-- 确认弹窗已替换为Taro.showModal --> 213 <!-- 确认弹窗已替换为Taro.showModal -->
110 </view> 214 </view>
111 </template> 215 </template>
...@@ -123,6 +227,12 @@ const defaultFamilyCoverSvg = 'https://cdn.ipadbiz.cn/lls_prog/images/default-fa ...@@ -123,6 +227,12 @@ const defaultFamilyCoverSvg = 'https://cdn.ipadbiz.cn/lls_prog/images/default-fa
123 // 响应式数据 227 // 响应式数据
124 const familyList = ref([]); 228 const familyList = ref([]);
125 229
230 +// 成员管理相关数据
231 +const showMemberPopup = ref(false);
232 +const currentFamily = ref(null);
233 +const selectedMembers = ref([]);
234 +const mockMembers = ref([]);
235 +
126 /** 236 /**
127 * 初始化页面数据 237 * 初始化页面数据
128 */ 238 */
...@@ -132,43 +242,46 @@ const initPageData = async () => { ...@@ -132,43 +242,46 @@ const initPageData = async () => {
132 familyList.value = data; 242 familyList.value = data;
133 console.warn(data); 243 console.warn(data);
134 } 244 }
135 - // 模拟家庭数据 245 + // 模拟家庭数据 - 添加is_my字段用于测试
136 - // familyList.value = [ 246 + familyList.value = [
137 - // { 247 + {
138 - // id: 1, 248 + id: 1,
139 - // name: '幸福之家', 249 + name: '幸福之家',
140 - // ownerName: '张明明', 250 + ownerName: '张明明',
141 - // avatar_url: 'https://images.unsplash.com/photo-1511895426328-dc8714191300?w=400&h=200&fit=crop', 251 + avatar_url: 'https://images.unsplash.com/photo-1511895426328-dc8714191300?w=400&h=200&fit=crop',
142 - // is_in: true, 252 + is_in: true,
143 - // members: [ 253 + is_my: true, // 当前用户是家长,可以管理成员
144 - // { id: 1, avatar: 'https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?w=100&h=100&fit=crop&crop=face' }, 254 + members: [
145 - // { id: 2, avatar: 'https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?w=100&h=100&fit=crop&crop=face' }, 255 + { id: 1, avatar: 'https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?w=100&h=100&fit=crop&crop=face' },
146 - // { id: 3, avatar: 'https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?w=100&h=100&fit=crop&crop=face' } 256 + { id: 2, avatar: 'https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?w=100&h=100&fit=crop&crop=face' },
147 - // ] 257 + { id: 3, avatar: 'https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?w=100&h=100&fit=crop&crop=face' }
148 - // }, 258 + ]
149 - // { 259 + },
150 - // id: 2, 260 + {
151 - // name: '欢乐之家', 261 + id: 2,
152 - // ownerName: '李志强', 262 + name: '欢乐之家',
153 - // avatar_url: 'https://images.unsplash.com/photo-1502086223501-7ea6ecd79368?w=400&h=200&fit=crop', 263 + ownerName: '李志强',
154 - // is_in: false, 264 + avatar_url: 'https://images.unsplash.com/photo-1502086223501-7ea6ecd79368?w=400&h=200&fit=crop',
155 - // members: [ 265 + is_in: false,
156 - // { id: 4, avatar: 'https://images.unsplash.com/photo-1500648767791-00dcc994a43e?w=100&h=100&fit=crop&crop=face' }, 266 + is_my: false, // 当前用户不是家长
157 - // { id: 5, avatar: 'https://images.unsplash.com/photo-1438761681033-6461ffad8d80?w=100&h=100&fit=crop&crop=face' } 267 + members: [
158 - // ] 268 + { id: 4, avatar: 'https://images.unsplash.com/photo-1500648767791-00dcc994a43e?w=100&h=100&fit=crop&crop=face' },
159 - // }, 269 + { id: 5, avatar: 'https://images.unsplash.com/photo-1438761681033-6461ffad8d80?w=100&h=100&fit=crop&crop=face' }
160 - // { 270 + ]
161 - // id: 3, 271 + },
162 - // name: '快乐之家', 272 + {
163 - // ownerName: '王芳', 273 + id: 3,
164 - // avatar_url: 'https://images.unsplash.com/photo-1502086223501-7ea6ecd79368?w=400&h=200&fit=crop', 274 + name: '快乐之家',
165 - // is_in: false, 275 + ownerName: '王芳',
166 - // members: [ 276 + avatar_url: 'https://images.unsplash.com/photo-1502086223501-7ea6ecd79368?w=400&h=200&fit=crop',
167 - // { id: 6, avatar: 'https://images.unsplash.com/photo-1500648767791-00dcc994a43e?w=100&h=100&fit=crop&crop=face' }, 277 + is_in: false,
168 - // { id: 7, avatar: 'https://images.unsplash.com/photo-1438761681033-6461ffad8d80?w=100&h=100&fit=crop&crop=face' } 278 + is_my: true, // 当前用户是家长,但不在此家庭
169 - // ] 279 + members: [
170 - // }, 280 + { id: 6, avatar: 'https://images.unsplash.com/photo-1500648767791-00dcc994a43e?w=100&h=100&fit=crop&crop=face' },
171 - // ]; 281 + { id: 7, avatar: 'https://images.unsplash.com/photo-1438761681033-6461ffad8d80?w=100&h=100&fit=crop&crop=face' }
282 + ]
283 + }
284 + ];
172 }; 285 };
173 286
174 // 如果家庭列表存在时, 才显示加入新家庭的按钮 287 // 如果家庭列表存在时, 才显示加入新家庭的按钮
...@@ -250,6 +363,38 @@ const exitFamily = (familyId) => { ...@@ -250,6 +363,38 @@ const exitFamily = (familyId) => {
250 }; 363 };
251 364
252 /** 365 /**
366 + * 解散家庭
367 + * @param {number} familyId - 家庭ID
368 + */
369 +const dissolveFamily = (familyId) => {
370 + const family = familyList.value.find(f => f.id === familyId);
371 + if (!family) return;
372 +
373 + Taro.showModal({
374 + title: '解散家庭',
375 + content: `确定要解散「${family.name}」吗?\n\n解散后:\n• 所有家庭成员将被移除\n• 家庭数据将被永久删除\n• 此操作无法撤销\n\n请谨慎操作!`,
376 + confirmText: '确认解散',
377 + cancelText: '取消',
378 + success: (res) => {
379 + if (res.confirm) {
380 + // 解散家庭逻辑
381 + familyList.value = familyList.value.filter(f => f.id !== familyId);
382 +
383 + Taro.showToast({
384 + title: '家庭已解散',
385 + icon: 'success'
386 + });
387 +
388 + // 延迟返回我的页面
389 + setTimeout(() => {
390 + Taro.navigateBack();
391 + }, 1500);
392 + }
393 + }
394 + });
395 +};
396 +
397 +/**
253 * 加入新家庭 398 * 加入新家庭
254 */ 399 */
255 const joinNewFamily = () => { 400 const joinNewFamily = () => {
...@@ -258,6 +403,123 @@ const joinNewFamily = () => { ...@@ -258,6 +403,123 @@ const joinNewFamily = () => {
258 }); 403 });
259 }; 404 };
260 405
406 +/**
407 + * 显示成员管理弹窗
408 + * @param {Object} family - 家庭对象
409 + */
410 +const showMemberManagement = (family) => {
411 + currentFamily.value = family;
412 + // 生成模拟成员数据
413 + mockMembers.value = [
414 + {
415 + id: 1,
416 + nickname: '张明明',
417 + avatar: 'https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?w=100&h=100&fit=crop&crop=face',
418 + role: '父亲',
419 + is_owner: true
420 + },
421 + {
422 + id: 2,
423 + nickname: '李美丽',
424 + avatar: 'https://images.unsplash.com/photo-1438761681033-6461ffad8d80?w=100&h=100&fit=crop&crop=face',
425 + role: '母亲',
426 + is_owner: false
427 + },
428 + {
429 + id: 3,
430 + nickname: '张小明',
431 + avatar: 'https://images.unsplash.com/photo-1500648767791-00dcc994a43e?w=100&h=100&fit=crop&crop=face',
432 + role: '儿子',
433 + is_owner: false
434 + },
435 + {
436 + id: 4,
437 + nickname: '张小花',
438 + avatar: 'https://images.unsplash.com/photo-1494790108755-2616b612b786?w=100&h=100&fit=crop&crop=face',
439 + role: '女儿',
440 + is_owner: false
441 + },
442 + {
443 + id: 5,
444 + nickname: '王奶奶',
445 + avatar: 'https://images.unsplash.com/photo-1551836022-d5d88e9218df?w=100&h=100&fit=crop&crop=face',
446 + role: '奶奶',
447 + is_owner: false
448 + }
449 + ];
450 + selectedMembers.value = [];
451 + showMemberPopup.value = true;
452 +};
453 +
454 +/**
455 + * 关闭成员管理弹窗
456 + */
457 +const closeMemberPopup = () => {
458 + showMemberPopup.value = false;
459 + currentFamily.value = null;
460 + selectedMembers.value = [];
461 + mockMembers.value = [];
462 +};
463 +
464 +/**
465 + * 切换成员选择状态
466 + * @param {number} memberId - 成员ID
467 + */
468 +const toggleMemberSelection = (memberId) => {
469 + const member = mockMembers.value.find(m => m.id === memberId);
470 +
471 + // 创建者不能被选择移除
472 + if (member?.is_owner) {
473 + Taro.showToast({
474 + title: '创建者不能被移除',
475 + icon: 'none'
476 + });
477 + return;
478 + }
479 +
480 + const index = selectedMembers.value.indexOf(memberId);
481 + if (index > -1) {
482 + selectedMembers.value.splice(index, 1);
483 + } else {
484 + selectedMembers.value.push(memberId);
485 + }
486 +};
487 +
488 +/**
489 + * 移除选中的成员
490 + */
491 +const removeSelectedMembers = () => {
492 + if (selectedMembers.value.length === 0) {
493 + Taro.showToast({
494 + title: '请选择要移除的成员',
495 + icon: 'none'
496 + });
497 + return;
498 + }
499 +
500 + const selectedNames = mockMembers.value
501 + .filter(m => selectedMembers.value.includes(m.id))
502 + .map(m => m.nickname)
503 + .join('、');
504 +
505 + Taro.showModal({
506 + title: '移除成员',
507 + content: `确定要移除「${selectedNames}」吗?移除后他们将无法访问家庭信息。`,
508 + success: (res) => {
509 + if (res.confirm) {
510 + // 从模拟数据中移除选中的成员
511 + mockMembers.value = mockMembers.value.filter(m => !selectedMembers.value.includes(m.id));
512 + selectedMembers.value = [];
513 +
514 + Taro.showToast({
515 + title: '移除成功',
516 + icon: 'success'
517 + });
518 + }
519 + }
520 + });
521 +};
522 +
261 // 页面加载时初始化数据 523 // 页面加载时初始化数据
262 useDidShow(() => { 524 useDidShow(() => {
263 initPageData(); 525 initPageData();
......