ソースを参照

feat: TabBar改文字+RelationshipPicker组件+编辑关系功能

- TabBar: 首页→行,我的→富(custom-tab-bar + pages.json)
- 新增 RelationshipPicker.vue 可复用组件(自动加载关系类型列表,v-model 双向绑定)
- add-member.vue 改用 RelationshipPicker 替代内联 picker
- family-members.vue 增加编辑成员关系类型功能(弹窗+保存)
User 4 ヶ月 前
親
コミット
9bc516c6fb

+ 101 - 0
cfc-frontend/components/RelationshipPicker.vue

@@ -0,0 +1,101 @@
+<template>
+  <view class="relationship-picker-wrap">
+    <view class="form-item required" v-if="showLabel">
+      <text class="label"><text class="required-mark">*</text>关系类型</text>
+    </view>
+    <picker :range="typeNames" @change="onChange" :disabled="disabled">
+      <view class="picker" :class="{ 'picker-disabled': disabled }">
+        {{ selectedName || '请选择关系类型' }}
+      </view>
+    </picker>
+  </view>
+</template>
+
+<script>
+import { getRelationshipTypes } from '../utils/api.js'
+
+export default {
+  name: 'RelationshipPicker',
+  props: {
+    value: { type: String, default: '' },
+    disabled: { type: Boolean, default: false },
+    showLabel: { type: Boolean, default: true }
+  },
+  data() {
+    return {
+      relationshipTypes: [],
+      loading: false
+    }
+  },
+  computed: {
+    typeNames() {
+      var names = []
+      for (var i = 0; i < this.relationshipTypes.length; i++) {
+        names.push(this.relationshipTypes[i].typeName)
+      }
+      return names
+    },
+    selectedName() {
+      if (!this.value) return ''
+      for (var i = 0; i < this.relationshipTypes.length; i++) {
+        if (this.relationshipTypes[i].typeKey === this.value) {
+          return this.relationshipTypes[i].typeName
+        }
+      }
+      return ''
+    }
+  },
+  mounted() {
+    this.loadTypes()
+  },
+  methods: {
+    async loadTypes() {
+      this.loading = true
+      try {
+        var res = await getRelationshipTypes()
+        if (res.code === 200 && res.data) {
+          this.relationshipTypes = res.data
+        }
+      } catch (e) {
+        console.error('加载关系类型失败', e)
+      } finally {
+        this.loading = false
+      }
+    },
+    onChange(e) {
+      var idx = e.detail.value
+      if (idx >= 0 && idx < this.relationshipTypes.length) {
+        this.$emit('input', this.relationshipTypes[idx].typeKey)
+      }
+    }
+  }
+}
+</script>
+
+<style scoped>
+.picker {
+  border: 1rpx solid #ddd;
+  border-radius: 10rpx;
+  padding: 20rpx;
+  font-size: 28rpx;
+  color: #666;
+}
+.picker-disabled {
+  background: #f5f5f5;
+  color: #999;
+}
+.form-item {
+  margin-bottom: 10rpx;
+}
+.form-item .label {
+  display: block;
+  font-size: 28rpx;
+  color: #333;
+  margin-bottom: 10rpx;
+  font-weight: bold;
+}
+.required-mark {
+  color: #F97316;
+  margin-right: 4rpx;
+}
+</style>

+ 2 - 2
cfc-frontend/custom-tab-bar/index.vue

@@ -44,11 +44,11 @@ export default {
     return {
       currentIndex: 0,
       normalTabs: [
-        { text: '首页', pagePath: '/pages/index/index', icon: '/static/tab-home.png', selectedIcon: '/static/tab-home-active.png' },
+        { text: '行', pagePath: '/pages/index/index', icon: '/static/tab-home.png', selectedIcon: '/static/tab-home-active.png' },
         { text: '身', pagePath: '/pages/body/index', icon: '/static/tab-body.png', selectedIcon: '/static/tab-body-active.png' },
         { text: '智', pagePath: '/pages/wisdom/index', icon: '/static/tab-wisdom.png', selectedIcon: '/static/tab-wisdom-active.png' },
         { text: '心', pagePath: '/pages/mind/index', icon: '/static/tab-mind.png', selectedIcon: '/static/tab-mind-active.png' },
-        { text: '我的', pagePath: '/pages/profile/profile', icon: '/static/tab-profile.png', selectedIcon: '/static/tab-profile-active.png' }
+        { text: '富', pagePath: '/pages/profile/profile', icon: '/static/tab-profile.png', selectedIcon: '/static/tab-profile-active.png' }
       ],
       teacherTabs: [
         { text: '首页', pagePath: '/pages/teacher/index', icon: '/static/tab-home.png', selectedIcon: '/static/tab-home-active.png' },

+ 8 - 2
cfc-frontend/pages.json

@@ -209,6 +209,12 @@
         "navigationBarTitleText": "家庭成员"
       }
     },
+    {
+      "path": "pages/family/add-member",
+      "style": {
+        "navigationBarTitleText": "添加家庭成员"
+      }
+    },
     {
       "path": "pages/discover/product-detail/product-detail",
       "style": {
@@ -666,7 +672,7 @@
     "list": [
       {
         "pagePath": "pages/index/index",
-        "text": "首页",
+        "text": "行",
         "iconPath": "static/tab-home.png",
         "selectedIconPath": "static/tab-home-active.png"
       },
@@ -690,7 +696,7 @@
       },
       {
         "pagePath": "pages/profile/profile",
-        "text": "我的",
+        "text": "富",
         "iconPath": "static/tab-profile.png",
         "selectedIconPath": "static/tab-profile-active.png"
       }

+ 170 - 0
cfc-frontend/pages/family/add-member.vue

@@ -0,0 +1,170 @@
+<template>
+  <view class="container">
+    <view class="form-container">
+      <view class="page-title">添加家庭成员</view>
+
+      <!-- 昵称(必填) -->
+      <view class="form-item required">
+        <text class="label"><text class="required-mark">*</text>成员昵称</text>
+        <input class="input" v-model="form.nickname" placeholder="请输入成员昵称" maxlength="20" />
+      </view>
+
+      <!-- 关系类型(必填) -->
+      <view class="form-item required">
+        <text class="label"><text class="required-mark">*</text>关系类型</text>
+        <relationship-picker v-model="form.relationshipType" />
+      </view>
+
+      <!-- 性别(必填) -->
+      <view class="form-item required">
+        <text class="label"><text class="required-mark">*</text>性别</text>
+        <picker :range="genderOptions" range-key="label" @change="onGenderChange">
+          <view class="picker">
+            {{ genderLabel || '请选择性别' }}
+          </view>
+        </picker>
+      </view>
+
+      <!-- 出生日期(必填) -->
+      <view class="form-item required">
+        <text class="label"><text class="required-mark">*</text>出生日期</text>
+        <picker mode="date" :value="form.birthday" @change="onBirthdayChange" :end="todayDate">
+          <view class="picker">
+            {{ form.birthday || '请选择出生日期' }}
+          </view>
+        </picker>
+      </view>
+
+      <!-- 年龄(自动计算) -->
+      <view class="form-item">
+        <text class="label">年龄</text>
+        <view class="age-display">{{ calculatedAge || '根据出生日期自动计算' }}</view>
+      </view>
+
+      <!-- 手机号(选填) -->
+      <view class="form-item">
+        <text class="label">手机号(选填)</text>
+        <input class="input" type="number" v-model="form.phone" placeholder="填写手机号可关联已有账号" maxlength="11" />
+        <text class="hint">若手机号已注册,将自动关联该账号</text>
+      </view>
+
+      <button class="btn-primary" :loading="submitting" @click="submit">保存</button>
+    </view>
+  </view>
+</template>
+
+<script>
+import { addFamilyMember } from '../../utils/api.js'
+import RelationshipPicker from '../../components/RelationshipPicker.vue'
+
+export default {
+  components: { RelationshipPicker },
+  data() {
+    return {
+      submitting: false,
+      form: {
+        nickname: '',
+        relationshipType: '',
+        gender: '',
+        birthday: '',
+        phone: ''
+      },
+      genderOptions: [
+        { value: 'male', label: '男' },
+        { value: 'female', label: '女' }
+      ],
+      todayDate: ''
+    }
+  },
+  computed: {
+    genderLabel() {
+      var gender = this.genderOptions.find(function(g) { return g.value === this.form.gender }.bind(this))
+      return gender ? gender.label : ''
+    },
+    calculatedAge() {
+      if (!this.form.birthday) return ''
+      var birth = new Date(this.form.birthday)
+      var today = new Date()
+      var age = today.getFullYear() - birth.getFullYear()
+      var monthDiff = today.getMonth() - birth.getMonth()
+      if (monthDiff < 0 || (monthDiff === 0 && today.getDate() < birth.getDate())) {
+        age--
+      }
+      return age >= 0 ? age + '岁' : ''
+    }
+  },
+  onLoad() {
+    var today = new Date()
+    this.todayDate = today.getFullYear() + '-' + String(today.getMonth() + 1).padStart(2, '0') + '-' + String(today.getDate()).padStart(2, '0')
+  },
+  methods: {
+    onGenderChange(e) {
+      this.form.gender = this.genderOptions[e.detail.value].value
+    },
+    onBirthdayChange(e) {
+      this.form.birthday = e.detail.value
+    },
+    async submit() {
+      if (!this.form.nickname || !this.form.nickname.trim()) {
+        uni.showToast({ title: '请输入成员昵称', icon: 'none' })
+        return
+      }
+      if (!this.form.relationshipType) {
+        uni.showToast({ title: '请选择关系类型', icon: 'none' })
+        return
+      }
+      if (!this.form.gender) {
+        uni.showToast({ title: '请选择性别', icon: 'none' })
+        return
+      }
+      if (!this.form.birthday) {
+        uni.showToast({ title: '请选择出生日期', icon: 'none' })
+        return
+      }
+      if (this.form.phone && !/^1[3-9]\d{9}$/.test(this.form.phone)) {
+        uni.showToast({ title: '请输入正确的手机号', icon: 'none' })
+        return
+      }
+
+      try {
+        this.submitting = true
+        var payload = {
+          nickname: this.form.nickname.trim(),
+          relationshipType: this.form.relationshipType,
+          gender: this.form.gender,
+          birthday: this.form.birthday
+        }
+        if (this.form.phone) {
+          payload.phone = this.form.phone
+        }
+        var res = await addFamilyMember(payload)
+        if (res.code === 200) {
+          uni.showToast({ title: '添加成功', icon: 'success' })
+          setTimeout(function() { uni.navigateBack() }, 1500)
+        } else {
+          uni.showToast({ title: res.message || '添加失败', icon: 'none' })
+        }
+      } catch (e) {
+        uni.showToast({ title: '添加失败', icon: 'none' })
+      } finally {
+        this.submitting = false
+      }
+    }
+  }
+}
+</script>
+
+<style scoped>
+.container { padding: 30rpx; }
+.form-container { background: #fff; border-radius: 20rpx; padding: 30rpx; }
+.page-title { font-size: 34rpx; font-weight: bold; color: #333; margin-bottom: 30rpx; }
+.form-item { margin-bottom: 30rpx; }
+.form-item.required .label { font-weight: bold; }
+.label { display: block; font-size: 28rpx; color: #333; margin-bottom: 10rpx; }
+.required-mark { color: #F97316; margin-right: 4rpx; }
+.input { border: 1rpx solid #ddd; border-radius: 10rpx; padding: 20rpx; font-size: 28rpx; }
+.picker { border: 1rpx solid #ddd; border-radius: 10rpx; padding: 20rpx; font-size: 28rpx; color: #666; }
+.age-display { border: 1rpx solid #ddd; border-radius: 10rpx; padding: 20rpx; font-size: 28rpx; color: #999; background: #f9f9f9; }
+.hint { font-size: 24rpx; color: #999; margin-top: 8rpx; display: block; }
+.btn-primary { background: #F97316; color: #fff; border-radius: 20rpx; margin-top: 40rpx; }
+</style>

+ 221 - 152
cfc-frontend/pages/profile/family-members.vue

@@ -4,14 +4,14 @@
     <view class="family-header" v-if="familyName">
       <view class="family-header-icon">🏠</view>
       <text class="family-name">{{ familyName }}</text>
-      <text class="family-member-count">{{ visibleParents.length + children.length }} 位成员</text>
+      <text class="family-member-count">{{ members.length }} 位成员</text>
     </view>
 
     <!-- 操作按钮组 -->
     <view class="action-bar">
-      <view class="action-btn add-btn" @click="showAddChild = true">
+      <view class="action-btn add-btn" @click="onAddMember">
         <text class="action-icon">+</text>
-        <text class="action-text">添加孩子</text>
+        <text class="action-text">添加成员</text>
       </view>
       <view class="action-btn invite-btn" @click="onInvite">
         <text class="action-icon">📨</text>
@@ -19,53 +19,49 @@
       </view>
     </view>
 
-    <!-- 家长列表(不含当前用户) -->
-    <view class="section-title">家长 ({{ visibleParents.length }})</view>
+    <!-- 家长组 -->
+    <view class="section-title">家长 ({{ parentMembers.length }})</view>
     <view class="member-list">
-      <view class="member-item" v-for="p in visibleParents" :key="p.id">
-        <image class="member-avatar" :src="p.avatar || '/static/default-avatar.png'" mode="aspectFill"></image>
+      <view class="member-item" v-for="m in parentMembers" :key="m.id">
+        <image class="member-avatar" :src="m.avatar || '/static/default-avatar.png'" mode="aspectFill"></image>
         <view class="member-detail">
-          <text class="member-name">{{ p.nickname || '未设置' }}</text>
+          <text class="member-name">{{ m.nickname || '未设置' }}</text>
           <view class="member-tags">
-            <text class="member-role-tag">家长</text>
-            <text class="member-admin-tag" v-if="p.id === creatorId">管理员</text>
+            <text class="member-role-tag">{{ m.roleLabel || '家长' }}</text>
+            <text class="member-source-tag" v-if="m.source === 'family_member'">手动添加</text>
           </view>
         </view>
-        <button class="btn-kick" v-if="isAdmin && p.id !== userId" @click="confirmKick(p)">踢出</button>
+        <button class="btn-switch" v-if="m.source !== 'parent' || m.id !== userId" @click="onSwitchMember(m)">切换</button>
+        <button class="btn-edit" v-if="m.source === 'family_member'" @click="onEditRelation(m)">编辑</button>
+        <button class="btn-kick" v-if="isAdmin && m.source === 'family_member'" @click="confirmKick(m)">移除</button>
       </view>
-      <view v-if="visibleParents.length === 0" class="empty-row">
-        <text class="empty-text">暂无其他家长成员</text>
+      <view v-if="parentMembers.length === 0" class="empty-row">
+        <text class="empty-text">暂无家长成员</text>
       </view>
     </view>
 
-    <!-- 孩子列表 -->
+    <!-- 孩子组 -->
     <view class="section-title">
-      孩子 ({{ children.length }})
-      <text class="section-add" @click="showAddChild = true" v-if="isAdmin">+添加</text>
+      孩子 ({{ childMembers.length }})
     </view>
     <view class="member-list">
-      <view class="member-item" v-for="c in children" :key="c.id">
+      <view class="member-item" v-for="m in childMembers" :key="m.id">
         <view class="member-avatar child-avatar">👶</view>
         <view class="member-detail">
-          <text class="member-name">{{ c.nickname || c.childName || '未设置' }}</text>
-          <text class="member-extra" v-if="c.age"> {{ c.age }}岁</text>
-          <text class="member-extra" v-if="c.gender"> · {{ c.gender === 'male' ? '男' : '女' }}</text>
+          <text class="member-name">{{ m.nickname || '未设置' }}</text>
+          <view class="member-tags">
+            <text class="member-role-tag">{{ m.roleLabel || '孩子' }}</text>
+            <text class="member-source-tag" v-if="m.source === 'family_member'">手动添加</text>
+            <text class="member-extra" v-if="m.age"> {{ m.age }}岁</text>
+            <text class="member-extra" v-if="m.gender"> · {{ m.gender === 'male' ? '男' : '女' }}</text>
+          </view>
         </view>
+        <button class="btn-switch" @click="onSwitchMember(m)">切换</button>
+        <button class="btn-edit" v-if="m.source === 'family_member'" @click="onEditRelation(m)">编辑</button>
+        <button class="btn-kick" v-if="isAdmin && m.source === 'family_member'" @click="confirmKick(m)">移除</button>
       </view>
-      <view v-if="children.length === 0" class="empty-row">
-        <text class="empty-text">暂无孩子</text>
-      </view>
-    </view>
-
-    <!-- 添加孩子弹窗 -->
-    <view class="modal-mask" v-if="showAddChild" @click="showAddChild = false">
-      <view class="modal-content" @click.stop>
-        <text class="modal-title">添加孩子</text>
-        <input class="modal-input" v-model="newChildName" placeholder="请输入孩子昵称" maxlength="20" />
-        <view class="modal-btns">
-          <button class="modal-btn cancel" @click="showAddChild = false">取消</button>
-          <button class="modal-btn confirm" :disabled="!newChildName.trim()" @click="onAddChild">确定</button>
-        </view>
+      <view v-if="childMembers.length === 0" class="empty-row">
+        <text class="empty-text">暂无孩子成员</text>
       </view>
     </view>
 
@@ -97,6 +93,21 @@
       </view>
     </view>
 
+    <!-- 编辑关系弹窗 -->
+    <view class="modal-mask" v-if="showEditRelation" @click="showEditRelation = false">
+      <view class="edit-relation-card" @click.stop>
+        <text class="edit-relation-title">编辑成员关系</text>
+        <view class="edit-relation-member">
+          <text class="edit-relation-name">{{ editTargetMember && editTargetMember.nickname || '' }}</text>
+        </view>
+        <relationship-picker v-model="editRelationType" />
+        <view class="edit-relation-actions">
+          <button class="btn-cancel" @click="showEditRelation = false">取消</button>
+          <button class="btn-save" @click="onSaveRelation">保存</button>
+        </view>
+      </view>
+    </view>
+
     <!-- 加载状态 -->
     <view v-if="loading" class="loading-wrap">
       <text class="loading-text">加载中...</text>
@@ -106,13 +117,18 @@
 
 <script>
 import {
+  listFamilyMembers,
+  switchToFamilyMember,
+  kickFamilyMemberById,
+  generateFamilyInviteQrCode,
   getFamilyMembers,
-  createChild,
-  kickFamilyMember,
-  generateFamilyInviteQrCode
+  updateFamilyMember
 } from '../../utils/api.js'
+import store from '../../store/index.js'
+import RelationshipPicker from '../../components/RelationshipPicker.vue'
 
 export default {
+  components: { RelationshipPicker },
   data() {
     return {
       userId: 0,
@@ -121,26 +137,28 @@ export default {
       inviteCode: '',
       inviteQrCode: '',
       creatorId: null,
-      parents: [],
-      children: [],
+      members: [],
       isAdmin: false,
       loading: false,
-
-      // 添加孩子
-      showAddChild: false,
-      newChildName: '',
-
-      // 邀请卡片
-      showInviteCard: false
+      showInviteCard: false,
+      showEditRelation: false,
+      editTargetMember: null,
+      editRelationType: ''
     }
   },
   computed: {
-    visibleParents() {
-      return this.parents.filter(p => p.id !== this.userId)
+    parentMembers() {
+      return this.members.filter(function(m) { return m.capabilityRole === 'parent' })
+    },
+    childMembers() {
+      return this.members.filter(function(m) { return m.capabilityRole === 'child' })
+    },
+    editableMembers() {
+      return this.members.filter(function(m) { return m.source === 'family_member' })
     }
   },
   onShow() {
-    const userInfo = uni.getStorageSync('userInfo')
+    var userInfo = uni.getStorageSync('userInfo')
     this.userId = (userInfo && userInfo.id) || 0
     this.loadMembers()
   },
@@ -148,51 +166,87 @@ export default {
     async loadMembers() {
       this.loading = true
       try {
-        const res = await getFamilyMembers()
+        var res = await listFamilyMembers()
         if (res.code === 200 && res.data) {
-          const data = res.data
+          this.members = res.data || []
+          // 从members中提取家庭信息(取第一个parent的familyId等)
+          // 仍需调用旧接口获取家庭名/邀请码/creatorId
+          this.loadFamilyInfo()
+        }
+      } catch (e) {
+        uni.showToast({ title: '加载失败', icon: 'none' })
+      } finally {
+        this.loading = false
+      }
+    },
+    async loadFamilyInfo() {
+      try {
+        var res = await getFamilyMembers()
+        if (res.code === 200 && res.data) {
+          var data = res.data
           this.familyId = data.familyId
           this.familyName = data.familyName
           this.inviteCode = data.inviteCode || ''
           this.creatorId = data.creatorId
-          this.parents = data.parents || []
-          this.children = data.children || []
           this.isAdmin = this.creatorId === this.userId
         }
       } catch (e) {
-        uni.showToast({ title: '加载失败', icon: 'none' })
-      } finally {
-        this.loading = false
+        // 静默处理
       }
     },
-    // 添加孩子
-    async onAddChild() {
-      const name = this.newChildName.trim()
-      if (!name) return
+    onAddMember() {
+      uni.navigateTo({ url: '/pages/family/add-member' })
+    },
+    
+    async onSwitchMember(member) {
       try {
-        const res = await createChild({
-          nickname: name,
-          age: 3,
-          gender: 'male'
-        })
+        var res = await switchToFamilyMember(member.id, member.source)
+        if (res.code === 200 && res.data) {
+          store.commit('switchToMember', {
+            memberId: res.data.memberId,
+            capabilityRole: res.data.capabilityRole
+          })
+          uni.showToast({ title: '已切换到' + (member.nickname || '该成员'), icon: 'success' })
+          setTimeout(function() {
+            uni.reLaunch({ url: '/pages/index/index' })
+          }, 1000)
+        } else {
+          uni.showToast({ title: res.message || '切换失败', icon: 'none' })
+        }
+      } catch (e) {
+        uni.showToast({ title: '切换失败', icon: 'none' })
+      }
+    },
+    confirmKick(member) {
+      var self = this
+      uni.showModal({
+        title: '移除成员',
+        content: '确定要将 ' + (member.nickname || '该成员') + ' 移出家庭吗?',
+        success: function(res) {
+          if (res.confirm) {
+            self.onKickMember(member)
+          }
+        }
+      })
+    },
+    async onKickMember(member) {
+      try {
+        var res = await kickFamilyMemberById(member.id)
         if (res.code === 200) {
-          uni.showToast({ title: '添加成功', icon: 'success' })
-          this.showAddChild = false
-          this.newChildName = ''
+          uni.showToast({ title: '已移除', icon: 'success' })
           this.loadMembers()
         } else {
-          uni.showToast({ title: res.message || '添加失败', icon: 'none' })
+          uni.showToast({ title: res.message || '操作失败', icon: 'none' })
         }
       } catch (e) {
-        uni.showToast({ title: '添加失败', icon: 'none' })
+        uni.showToast({ title: '操作失败', icon: 'none' })
       }
     },
-    // 邀请成员
     async onInvite() {
       this.showInviteCard = true
       this.inviteQrCode = ''
       try {
-        const res = await generateFamilyInviteQrCode()
+        var res = await generateFamilyInviteQrCode()
         if (res.code === 200 && res.data) {
           this.inviteCode = res.data.inviteCode
           this.inviteQrCode = res.data.qrCodeBase64
@@ -203,36 +257,39 @@ export default {
         uni.showToast({ title: '生成失败', icon: 'none' })
       }
     },
-    // 复制邀请码
     onCopyCode() {
       uni.setClipboardData({
         data: this.inviteCode,
-        success: () => {
+        success: function() {
           uni.showToast({ title: '已复制邀请码', icon: 'success' })
         }
       })
     },
-    // 踢出成员
-    confirmKick(parent) {
-      uni.showModal({
-        title: '踢出成员',
-        content: '确定要将 ' + (parent.nickname || '该家长') + ' 踢出家庭吗?',
-        success: async (res) => {
-          if (res.confirm) {
-            try {
-              const result = await kickFamilyMember(parent.id)
-              if (result.code === 200) {
-                uni.showToast({ title: '已踢出', icon: 'success' })
-                this.loadMembers()
-              } else {
-                uni.showToast({ title: result.message || '操作失败', icon: 'none' })
-              }
-            } catch (e) {
-              uni.showToast({ title: '操作失败', icon: 'none' })
-            }
-          }
+    onEditRelation(member) {
+      this.editTargetMember = member
+      this.editRelationType = member.relationshipType || ''
+      this.showEditRelation = true
+    },
+    async onSaveRelation() {
+      if (!this.editTargetMember || !this.editRelationType) {
+        uni.showToast({ title: '请选择关系类型', icon: 'none' })
+        return
+      }
+      try {
+        var res = await updateFamilyMember({
+          memberId: this.editTargetMember.id,
+          relationshipType: this.editRelationType
+        })
+        if (res.code === 200) {
+          uni.showToast({ title: '修改成功', icon: 'success' })
+          this.showEditRelation = false
+          this.loadMembers()
+        } else {
+          uni.showToast({ title: res.message || '修改失败', icon: 'none' })
         }
-      })
+      } catch (e) {
+        uni.showToast({ title: '修改失败', icon: 'none' })
+      }
     }
   }
 }
@@ -309,14 +366,6 @@ export default {
   align-items: center;
   justify-content: space-between;
 }
-.section-add {
-  font-size: 26rpx;
-  color: #667eea;
-  font-weight: normal;
-  padding: 4rpx 12rpx;
-  border: 1rpx solid #667eea;
-  border-radius: 8rpx;
-}
 
 /* 成员列表 */
 .member-list {
@@ -362,6 +411,7 @@ export default {
   align-items: center;
   margin-top: 6rpx;
   gap: 8rpx;
+  flex-wrap: wrap;
 }
 .member-role-tag {
   font-size: 22rpx;
@@ -370,10 +420,10 @@ export default {
   padding: 2rpx 12rpx;
   border-radius: 6rpx;
 }
-.member-admin-tag {
+.member-source-tag {
   font-size: 22rpx;
-  color: #e6a23c;
-  background: #fdf6ec;
+  color: #F97316;
+  background: #fff7ed;
   padding: 2rpx 12rpx;
   border-radius: 6rpx;
 }
@@ -383,6 +433,17 @@ export default {
   margin-top: 4rpx;
   display: inline;
 }
+.btn-switch {
+  font-size: 24rpx;
+  color: #0EA5E9;
+  background: #f0f9ff;
+  border: none;
+  padding: 8rpx 20rpx;
+  border-radius: 8rpx;
+  line-height: 1.5;
+  margin-left: 12rpx;
+  flex-shrink: 0;
+}
 .btn-kick {
   font-size: 24rpx;
   color: #f56c6c;
@@ -417,53 +478,6 @@ export default {
   z-index: 100;
 }
 
-/* 添加孩子弹窗 */
-.modal-content {
-  background: #fff;
-  border-radius: 24rpx;
-  padding: 40rpx;
-  width: 560rpx;
-}
-.modal-title {
-  font-size: 32rpx;
-  font-weight: bold;
-  display: block;
-  text-align: center;
-  margin-bottom: 30rpx;
-}
-.modal-input {
-  border: 1rpx solid #ddd;
-  border-radius: 12rpx;
-  padding: 20rpx 24rpx;
-  font-size: 28rpx;
-  width: 100%;
-  box-sizing: border-box;
-  margin-bottom: 30rpx;
-}
-.modal-btns {
-  display: flex;
-  gap: 20rpx;
-}
-.modal-btn {
-  flex: 1;
-  border-radius: 12rpx;
-  font-size: 28rpx;
-  text-align: center;
-  padding: 20rpx 0;
-  border: none;
-}
-.modal-btn.cancel {
-  background: #f5f5f5;
-  color: #666;
-}
-.modal-btn.confirm {
-  background: #667eea;
-  color: #fff;
-}
-.modal-btn.confirm[disabled] {
-  opacity: 0.5;
-}
-
 /* 邀请卡片 */
 .invite-card {
   background: #fff;
@@ -554,4 +568,59 @@ export default {
   font-size: 28rpx;
   color: #999;
 }
+.btn-edit {
+  font-size: 24rpx;
+  color: #67c23a;
+  background: #f0f9eb;
+  border: none;
+  padding: 8rpx 20rpx;
+  border-radius: 8rpx;
+  line-height: 1.5;
+  margin-left: 12rpx;
+  flex-shrink: 0;
+}
+.edit-relation-card {
+  background: #fff;
+  border-radius: 24rpx;
+  padding: 40rpx;
+  width: 580rpx;
+}
+.edit-relation-title {
+  font-size: 32rpx;
+  font-weight: bold;
+  display: block;
+  text-align: center;
+  margin-bottom: 20rpx;
+}
+.edit-relation-member {
+  text-align: center;
+  margin-bottom: 24rpx;
+}
+.edit-relation-name {
+  font-size: 28rpx;
+  color: #333;
+}
+.edit-relation-actions {
+  display: flex;
+  gap: 20rpx;
+  margin-top: 30rpx;
+}
+.btn-cancel {
+  flex: 1;
+  background: #f5f5f5;
+  border: none;
+  border-radius: 12rpx;
+  padding: 18rpx 0;
+  font-size: 28rpx;
+  color: #666;
+}
+.btn-save {
+  flex: 1;
+  background: #F97316;
+  border: none;
+  border-radius: 12rpx;
+  padding: 18rpx 0;
+  font-size: 28rpx;
+  color: #fff;
+}
 </style>