Prechádzať zdrojové kódy

merge: integrate remote changes + supply hierarchy management

- Sync with remote cfclub branch
- cfc-web: supply-hierarchy management (new), supply-system/Detail update
- cfc-frontend: App.vue, invite/join.vue updates
- cfc-web: admin.js API, router updates
Xiaogang Liao 2 mesiacov pred
rodič
commit
2064d914cc

+ 9 - 0
cfc-frontend/App.vue

@@ -104,6 +104,15 @@ export default {
         uni.navigateTo({
           url: '/pages/invite/join?token=' + token
         })
+      } else if (sceneDecoded.indexOf('ref=') === 0) {
+        var refCode = sceneDecoded.substring(4)
+        if (!refCode) {
+          console.warn('推广码场景缺少 refCode', sceneDecoded)
+          return
+        }
+        uni.navigateTo({
+          url: '/pages/invite/join?refCode=' + encodeURIComponent(refCode)
+        })
       }
     }
   }

+ 76 - 4
cfc-frontend/pages/invite/join.vue

@@ -78,6 +78,41 @@
       </view>
     </view>
 
+    <!-- 推广码绑定卡片 -->
+    <view class="invite-card" v-if="!loading && refCode && !bindDone">
+      <view class="invite-header">
+        <view class="invite-icon">🤝</view>
+        <text class="invite-title">推广绑定</text>
+      </view>
+      <view class="invite-body">
+        <text class="member-count">推荐人推广码:{{ refCode }}</text>
+      </view>
+      <view class="action-area" v-if="isLoggedIn">
+        <button class="btn btn-primary" @tap="bindRef" :disabled="bindLoading">
+          <text v-if="!bindLoading">绑定推荐人</text>
+          <text v-else>绑定中...</text>
+        </button>
+        <text class="tip-text">绑定后该推荐人后续消费时记入其家庭公共账户</text>
+      </view>
+      <view class="action-area" v-else>
+        <button class="btn btn-primary" @tap="handleWechatLogin" :disabled="loginLoading">
+          <text v-if="!loginLoading">微信一键登录</text>
+          <text v-else>登录中...</text>
+        </button>
+        <text class="tip-text">登录后可绑定推荐人</text>
+      </view>
+    </view>
+
+    <!-- 推广码绑定成功 -->
+    <view class="invite-card" v-if="!loading && refCode && bindDone">
+      <view class="success-area">
+        <view class="success-icon">✅</view>
+        <text class="success-text">绑定成功</text>
+        <text class="success-sub">已绑定推荐人,可进入首页探索</text>
+        <button class="btn btn-primary" @tap="goHome">进入首页</button>
+      </view>
+    </view>
+
     <!-- 邀请无效 -->
     <view class="error-card" v-if="!loading && error">
       <view class="error-icon">❌</view>
@@ -88,12 +123,15 @@
 </template>
 
 <script>
-import { validateInvitation, acceptInvitation, checkUserFamily } from '@/utils/api.js'
+import { validateInvitation, acceptInvitation, checkUserFamily, bindReferral } from '@/utils/api.js'
 
 export default {
   data() {
     return {
       token: '',
+      refCode: '',
+      bindLoading: false,
+      bindDone: false,
       loading: true,
       loginLoading: false,
       acceptLoading: false,
@@ -108,10 +146,9 @@ export default {
     }
   },
   onLoad(query) {
-    // 获取 token:优先从 query 参数,其次从场景值
+    // token:优先从 query 参数,其次从场景值(invite=xxx)
     var token = query.token || query.inviteToken || ''
     if (!token) {
-      // 检查场景值(从小程序码扫码进入)
       var scene = query.scene
       if (scene) {
         var sceneDecoded = decodeURIComponent(scene)
@@ -120,11 +157,27 @@ export default {
         }
       }
     }
-    if (!token) {
+
+    // refCode:query.refCode 或场景值(ref=xxx)— 推广码注册绑定
+    var refCode = query.refCode || ''
+    if (!refCode && query.scene) {
+      var refScene = decodeURIComponent(query.scene)
+      if (refScene.indexOf('ref=') === 0) {
+        refCode = refScene.substring(4)
+      }
+    }
+    if (!token && !refCode) {
       this.error = '邀请链接无效'
       this.loading = false
       return
     }
+    if (refCode) {
+      // 推广码模式:仅存储 refCode,无需校验邀请
+      this.refCode = refCode
+      this.loading = false
+      this.checkLoginStatus()
+      return
+    }
     this.token = token
     this.checkLoginStatus()
     this.validateToken()
@@ -260,6 +313,25 @@ export default {
     },
     goHome() {
       uni.reLaunch({ url: '/pages/index/index' })
+    },
+    async bindRef() {
+      if (!this.refCode) {
+        uni.showToast({ title: '推广码为空', icon: 'none' })
+        return
+      }
+      this.bindLoading = true
+      try {
+        var res = await bindReferral(this.refCode)
+        if (res && res.code === 200) {
+          this.bindDone = true
+          uni.showToast({ title: '绑定成功', icon: 'success' })
+        } else {
+          uni.showToast({ title: (res && res.message) || '绑定失败', icon: 'none' })
+        }
+      } catch (e) {
+        uni.showToast({ title: e.message || '绑定失败', icon: 'none' })
+      }
+      this.bindLoading = false
     }
   }
 }

+ 95 - 6
cfc-web/src/api/admin.js

@@ -1213,12 +1213,101 @@ export function removeSupplierRole(params) {
 }
 
 export function adjustSupplyTier(params) {
-  return request({
-    url: '/api/admin/supply-manage/adjust-tier',
-    method: 'post',
-    data: params
-  })
-}
+    return request({
+        url: '/api/admin/supply-manage/adjust-tier',
+        method: 'post',
+        data: params
+    })
+}
+
+// 供应商层级关系管理
+export function getSupplyHierarchyTree(systemId) {
+    return request({
+        url: '/api/admin/supply-hierarchy/tree',
+        method: 'post',
+        params: { systemId }
+    })
+}
+
+export function setSupplyHierarchy(data) {
+    return request({
+        url: '/api/admin/supply-hierarchy/set',
+        method: 'post',
+        data
+    })
+}
+
+export function removeSupplyHierarchy(data) {
+    return request({
+        url: '/api/admin/supply-hierarchy/remove',
+        method: 'post',
+        data
+    })
+}
+
+export function getSupplyHierarchyDirectChildren(data) {
+    return request({
+        url: '/api/admin/supply-hierarchy/direct-children',
+        method: 'post',
+        data
+    })
+}
+
+export function getSupplyHierarchyAllChildren(data) {
+    return request({
+        url: '/api/admin/supply-hierarchy/all-children',
+        method: 'post',
+        data
+    })
+}
+
+export function getSupplyHierarchyParent(data) {
+    return request({
+        url: '/api/admin/supply-hierarchy/parent',
+        method: 'post',
+        data
+    })
+}
+
+export function getSupplyHierarchyParentChain(data) {
+    return request({
+        url: '/api/admin/supply-hierarchy/parent-chain',
+        method: 'post',
+        data
+    })
+}
+
+export function getSupplyHierarchyMy(data) {
+    return request({
+        url: '/api/admin/supply-hierarchy/my',
+        method: 'post',
+        data
+    })
+}
+
+export function getSupplyHierarchyChangeRequests(data) {
+    return request({
+        url: '/api/admin/supply-hierarchy/change-requests',
+        method: 'post',
+        data
+    })
+}
+
+export function applySupplyHierarchyChange(data) {
+    return request({
+        url: '/api/admin/supply-hierarchy/change/apply',
+        method: 'post',
+        data
+    })
+}
+
+export function reviewSupplyHierarchyChange(data) {
+    return request({
+        url: '/api/admin/supply-hierarchy/change/review',
+        method: 'post',
+        data
+    })
+}
 
 // ========== 家庭会员订阅管理 API ==========
 

+ 13 - 7
cfc-web/src/router/index.js

@@ -357,13 +357,19 @@ const routes = [
         meta: { title: '体系详情', perm: 'system:supply' },
         props: true
       },
-      {
-        path: 'supply-system/:id/edit',
-        name: 'SupplySystemEdit',
-        component: () => import('@/views/admin/supply-system/Form.vue'),
-        meta: { title: '编辑体系', perm: 'system:supply' },
-        props: true
-      },
+        {
+            path: 'supply-system/:id/edit',
+            name: 'SupplySystemEdit',
+            component: () => import('@/views/admin/supply-system/Form.vue'),
+            meta: { title: '编辑体系', perm: 'system:supply' },
+            props: true
+        },
+        {
+            path: 'supply-hierarchy/change-requests',
+            name: 'SupplyHierarchyChangeRequests',
+            component: () => import('@/views/admin/supply-hierarchy/ChangeRequest.vue'),
+            meta: { title: '更换上级审核', perm: 'system:supply' }
+        },
       // ========== 供应商管理 (supplier_admin) ==========
       {
         path: 'supply-manage',

+ 189 - 0
cfc-web/src/views/admin/supply-hierarchy/ChangeRequest.vue

@@ -0,0 +1,189 @@
+<template>
+    <div class="supply-hierarchy-change-request">
+        <el-card>
+            <div slot="header">
+                <span>更换上级审核</span>
+            </div>
+            
+            <div class="filter-section">
+                <el-form :inline="true" :model="filterForm" size="small">
+                    <el-form-item label="供应体系">
+                        <el-select v-model="filterForm.systemId" placeholder="选择体系" @change="loadRequests">
+                            <el-option v-for="system in supplySystems" :key="system.id" :label="system.name" :value="system.id"></el-option>
+                        </el-select>
+                    </el-form-item>
+                    <el-form-item label="状态">
+                        <el-select v-model="filterForm.status" placeholder="全部状态" clearable @change="loadRequests">
+                            <el-option label="待审核" value="pending"></el-option>
+                            <el-option label="已通过" value="approved"></el-option>
+                            <el-option label="已拒绝" value="rejected"></el-option>
+                        </el-select>
+                    </el-form-item>
+                </el-form>
+            </div>
+            
+            <el-table :data="requests" stripe>
+                <el-table-column prop="id" label="ID" width="60"></el-table-column>
+                <el-table-column prop="systemId" label="体系ID" width="80"></el-table-column>
+                <el-table-column label="申请人" width="120">
+                    <template slot-scope="scope">{{ scope.row.applicantNickname }} ({{ scope.row.applicantRealName }})</template>
+                </el-table-column>
+                <el-table-column label="目标上级" width="120">
+                    <template slot-scope="scope">{{ scope.row.targetParentNickname }} ({{ scope.row.targetParentRealName }})</template>
+                </el-table-column>
+                <el-table-column prop="reason" label="申请理由" min-width="180"></el-table-column>
+                <el-table-column prop="status" label="状态" width="100">
+                    <template slot-scope="scope">
+                        <el-tag :type="getStatusTagType(scope.row.status)" size="small">
+                            {{ getStatusText(scope.row.status) }}
+                        </el-tag>
+                    </template>
+                </el-table-column>
+                <el-table-column prop="createdAt" label="申请时间" width="160">
+                    <template slot-scope="scope">{{ scope.row.createdAt | parseTime }}</template>
+                </el-table-column>
+                <el-table-column label="操作" width="180">
+                    <template slot-scope="scope">
+                        <el-button size="mini" type="success" @click="handleApprove(scope.row)" v-if="scope.row.status === 'pending'">通过</el-button>
+                        <el-button size="mini" type="danger" @click="handleReject(scope.row)" v-if="scope.row.status === 'pending'">拒绝</el-button>
+                    </template>
+                </el-table-column>
+            </el-table>
+        </el-card>
+        
+        <!-- 拒绝理由对话框 -->
+        <el-dialog title="拒绝申请" :visible.sync="rejectDialogVisible" width="400px">
+            <el-form>
+                <el-form-item label="拒绝理由">
+                    <el-input v-model="rejectReason" type="textarea" placeholder="请输入拒绝理由"></el-input>
+                </el-form-item>
+            </el-form>
+            <div slot="footer">
+                <el-button @click="rejectDialogVisible = false">取消</el-button>
+                <el-button type="primary" @click="confirmReject">确定</el-button>
+            </div>
+        </el-dialog>
+    </div>
+</template>
+
+<script>
+import { listAllSupplySystems, getSupplyHierarchyChangeRequests, reviewSupplyHierarchyChange } from '@/api/admin'
+
+export default {
+    name: 'SupplyHierarchyChangeRequest',
+    data() {
+        return {
+            supplySystems: [],
+            requests: [],
+            filterForm: {
+                systemId: null,
+                status: null
+            },
+            rejectDialogVisible: false,
+            rejectReason: '',
+            currentRequestId: null
+        }
+    },
+    created() {
+        this.loadSupplySystems()
+    },
+    filters: {
+        parseTime(time, format) {
+            if (!time) return '-'
+            const d = new Date(time)
+            if (!format) return d.toLocaleString()
+            const pad = n => n < 10 ? '0' + n : n
+            return format.replace('{y}', d.getFullYear()).replace('{m}', pad(d.getMonth() + 1)).replace('{d}', pad(d.getDate()))
+        }
+    },
+    methods: {
+        async loadSupplySystems() {
+            try {
+                const res = await listAllSupplySystems()
+                if (res.data) {
+                    this.supplySystems = res.data
+                }
+            } catch (e) {
+                this.$message.error('加载供应体系列表失败')
+            }
+        },
+        async loadRequests() {
+            try {
+                const params = {}
+                if (this.filterForm.systemId) {
+                    params.systemId = this.filterForm.systemId
+                }
+                if (this.filterForm.status) {
+                    params.status = this.filterForm.status
+                }
+                const res = await getSupplyHierarchyChangeRequests(params)
+                if (res.data) {
+                    this.requests = res.data
+                }
+            } catch (e) {
+                this.$message.error('加载申请列表失败')
+            }
+        },
+        getStatusTagType(status) {
+            switch(status) {
+                case 'pending': return 'warning'
+                case 'approved': return 'success'
+                case 'rejected': return 'danger'
+                default: return 'info'
+            }
+        },
+        getStatusText(status) {
+            switch(status) {
+                case 'pending': return '待审核'
+                case 'approved': return '已通过'
+                case 'rejected': return '已拒绝'
+                default: return status
+            }
+        },
+        handleApprove(row) {
+            this.$confirm('确定通过该申请吗?', '提示', {
+                confirmButtonText: '确定',
+                cancelButtonText: '取消',
+                type: 'warning'
+            }).then(() => {
+                this.reviewRequest(row.id, 'approved', '')
+            }).catch(() => {})
+        },
+        handleReject(row) {
+            this.currentRequestId = row.id
+            this.rejectReason = ''
+            this.rejectDialogVisible = true
+        },
+        confirmReject() {
+            if (!this.rejectReason) {
+                this.$message.warning('请输入拒绝理由')
+                return
+            }
+            this.reviewRequest(this.currentRequestId, 'rejected', this.rejectReason)
+            this.rejectDialogVisible = false
+        },
+        async reviewRequest(requestId, status, rejectReason) {
+            try {
+                await reviewSupplyHierarchyChange({
+                    requestId,
+                    status,
+                    rejectReason
+                })
+                this.$message.success('操作成功')
+                this.loadRequests()
+            } catch (e) {
+                this.$message.error('操作失败')
+            }
+        }
+    }
+}
+</script>
+
+<style scoped>
+.supply-hierarchy-change-request {
+    padding: 20px;
+}
+.filter-section {
+    margin-bottom: 20px;
+}
+</style>

+ 212 - 73
cfc-web/src/views/admin/supply-system/Detail.vue

@@ -90,33 +90,47 @@
 
         <!-- Tab 4: 结算 -->
         <el-tab-pane label="结算" name="settlements">
-          <div style="margin-bottom: 15px;">
-            <el-button type="primary" size="small" @click="showCreateSettlementDialog">生成结算单</el-button>
-          </div>
-          <el-table :data="settlements" stripe>
-            <el-table-column prop="id" label="ID" width="60"></el-table-column>
-            <el-table-column label="结算周期" width="200">
-              <template slot-scope="scope">{{ scope.row.periodStart | parseTime('{y}-{m}-{d}') }} ~ {{ scope.row.periodEnd | parseTime('{y}-{m}-{d}') }}</template>
-            </el-table-column>
-            <el-table-column prop="totalSales" label="销售总额(分)" width="120">
-              <template slot-scope="scope">{{ scope.row.totalSales }}</template>
-            </el-table-column>
-            <el-table-column prop="platformProfit" label="平台留利(分)" width="120"></el-table-column>
-            <el-table-column prop="supplierPayout" label="供应商实付(分)" width="120"></el-table-column>
-            <el-table-column prop="status" label="状态" width="100">
-              <template slot-scope="scope">
-                <el-tag :type="scope.row.status === 'settled' ? 'success' : 'warning'" size="small">
-                  {{ scope.row.status === 'settled' ? '已结算' : '待确认' }}
-                </el-tag>
-              </template>
-            </el-table-column>
-            <el-table-column label="操作" width="180">
-              <template slot-scope="scope">
-                <el-button size="mini" @click="viewSettlementDetail(scope.row)">明细</el-button>
-                <el-button size="mini" type="success" @click="handleConfirmSettlement(scope.row)" :disabled="scope.row.status === 'settled'">确认结算</el-button>
-              </template>
-            </el-table-column>
-          </el-table>
+            <div style="margin-bottom: 15px;">
+                <el-button type="primary" size="small" @click="showCreateSettlementDialog">生成结算单</el-button>
+            </div>
+            <el-table :data="settlements" stripe>
+                <el-table-column prop="id" label="ID" width="60"></el-table-column>
+                <el-table-column label="结算周期" width="200">
+                    <template slot-scope="scope">{{ scope.row.periodStart | parseTime('{y}-{m}-{d}') }} ~ {{ scope.row.periodEnd | parseTime('{y}-{m}-{d}') }}</template>
+                </el-table-column>
+                <el-table-column prop="totalSales" label="销售总额(分)" width="120">
+                    <template slot-scope="scope">{{ scope.row.totalSales }}</template>
+                </el-table-column>
+                <el-table-column prop="platformProfit" label="平台留利(分)" width="120"></el-table-column>
+                <el-table-column prop="supplierPayout" label="供应商实付(分)" width="120"></el-table-column>
+                <el-table-column prop="status" label="状态" width="100">
+                    <template slot-scope="scope">
+                        <el-tag :type="scope.row.status === 'settled' ? 'success' : 'warning'" size="small">
+                            {{ scope.row.status === 'settled' ? '已结算' : '待确认' }}
+                        </el-tag>
+                    </template>
+                </el-table-column>
+                <el-table-column label="操作" width="180">
+                    <template slot-scope="scope">
+                        <el-button size="mini" @click="viewSettlementDetail(scope.row)">明细</el-button>
+                        <el-button size="mini" type="success" @click="handleConfirmSettlement(scope.row)" :disabled="scope.row.status === 'settled'">确认结算</el-button>
+                    </template>
+                </el-table-column>
+            </el-table>
+        </el-tab-pane>
+        
+        <!-- Tab 5: 下级关系 -->
+        <el-tab-pane label="下级关系" name="hierarchy">
+            <div style="margin-bottom: 15px;">
+                <el-button type="primary" size="small" @click="showSetHierarchyDialog">调整关系</el-button>
+            </div>
+            <el-tree
+                :data="hierarchyTree"
+                node-key="userId"
+                default-expand-all
+                :expand-on-click-node="false"
+                :render-content="renderHierarchyNode">
+            </el-tree>
         </el-tab-pane>
       </el-tabs>
     </el-card>
@@ -144,10 +158,30 @@
         <el-table-column prop="supplierName" label="收款供应商" width="100"></el-table-column>
         <el-table-column prop="payout" label="实付(分)" width="100"></el-table-column>
       </el-table>
-    </el-dialog>
+        </el-dialog>
+
+        <!-- 调整层级关系对话框 -->
+        <el-dialog title="调整层级关系" :visible.sync="setHierarchyVisible" width="450px">
+            <el-form label-width="100px">
+                <el-form-item label="上级">
+                    <el-select v-model="setHierarchyForm.parentId" placeholder="选择上级" style="width: 100%">
+                        <el-option v-for="member in members" :key="member.userId" :label="`${member.nickname} (${member.realName})`" :value="member.userId"></el-option>
+                    </el-select>
+                </el-form-item>
+                <el-form-item label="下级">
+                    <el-select v-model="setHierarchyForm.childId" placeholder="选择下级" style="width: 100%">
+                        <el-option v-for="member in members" :key="member.userId" :label="`${member.nickname} (${member.realName})`" :value="member.userId"></el-option>
+                    </el-select>
+                </el-form-item>
+            </el-form>
+            <div slot="footer">
+                <el-button @click="setHierarchyVisible = false">取消</el-button>
+                <el-button type="primary" @click="confirmSetHierarchy">确定</el-button>
+            </div>
+        </el-dialog>
 
-    <!-- 生成结算单对话框 -->
-    <el-dialog title="生成结算单" :visible.sync="createSettlementVisible" width="450px">
+        <!-- 生成结算单对话框 -->
+        <el-dialog title="生成结算单" :visible.sync="createSettlementVisible" width="450px">
       <el-form label-width="120px">
         <el-form-item label="结算周期开始">
           <el-date-picker v-model="settlementForm.periodStart" type="date" placeholder="选择开始日期" value-format="yyyy-MM-dd" style="width: 100%"></el-date-picker>
@@ -169,33 +203,40 @@
 
 <script>
 import { getSupplySystemDetail, getSupplySystemMembers, addSupplySystemMember,
-         removeSupplySystemMember, changeSupplySystemMemberRole,
-         getSupplySettlementList, getSupplySettlementDetailItems,
-         createSupplySettlement, confirmSupplySettlement } from '@/api/admin'
+    removeSupplySystemMember, changeSupplySystemMemberRole,
+    getSupplySettlementList, getSupplySettlementDetailItems,
+    createSupplySettlement, confirmSupplySettlement, getSupplyHierarchyTree,
+    setSupplyHierarchy, removeSupplyHierarchy } from '@/api/admin'
 
 export default {
   name: 'SupplySystemDetail',
-  data() {
-    return {
-      activeTab: 'members',
-      system: {},
-      products: [],
-      members: [],
-      orders: [],
-      settlements: [],
-      settlementItems: [],
-      addMemberVisible: false,
-      addMemberUserId: null,
-      settlementDetailVisible: false,
-      createSettlementVisible: false,
-      creatingSettlement: false,
-      settlementForm: {
-        periodStart: '',
-        periodEnd: '',
-        remark: ''
-      }
-    }
-  },
+    data() {
+        return {
+            activeTab: 'members',
+            system: {},
+            products: [],
+            members: [],
+            orders: [],
+            settlements: [],
+            settlementItems: [],
+            hierarchyTree: [],
+            addMemberVisible: false,
+            addMemberUserId: null,
+            settlementDetailVisible: false,
+            createSettlementVisible: false,
+            setHierarchyVisible: false,
+            creatingSettlement: false,
+            setHierarchyForm: {
+                parentId: null,
+                childId: null
+            },
+            settlementForm: {
+                periodStart: '',
+                periodEnd: '',
+                remark: ''
+            }
+        }
+    },
   created() {
     const id = this.$route.query.id || this.$route.params.id
     if (id) {
@@ -213,16 +254,17 @@ export default {
   },
   methods: {
     async loadDetail(id) {
-      try {
-        const res = await getSupplySystemDetail(id)
-        if (res.data) {
-          this.system = res.data
+        try {
+            const res = await getSupplySystemDetail(id)
+            if (res.data) {
+                this.system = res.data
+            }
+            this.loadMembers(id)
+            this.loadSettlements(id)
+            this.loadHierarchy()
+        } catch (e) {
+            this.$message.error('加载失败')
         }
-        this.loadMembers(id)
-        this.loadSettlements(id)
-      } catch (e) {
-        this.$message.error('加载失败')
-      }
     },
     async loadMembers(systemId) {
       try {
@@ -324,20 +366,117 @@ export default {
       }
     },
     async handleConfirmSettlement(row) {
-      try {
-        await confirmSupplySettlement(row.id)
-        this.$message.success('结算已确认')
-        this.loadSettlements(this.system.id)
-      } catch (e) {
-        this.$message.error('确认失败')
-      }
-    }
-  }
+        try {
+            await confirmSupplySettlement(row.id)
+            this.$message.success('结算已确认')
+            this.loadSettlements(this.system.id)
+        } catch (e) {
+            this.$message.error('确认失败')
+        }
+    },
+    
+    async loadHierarchy() {
+        try {
+            const res = await getSupplyHierarchyTree(this.system.id)
+            if (res.data) {
+                this.hierarchyTree = res.data
+            }
+        } catch (e) {
+            console.error(e)
+            this.$message.error('加载层级关系失败')
+        }
+    },
+    
+    showSetHierarchyDialog(nodeData) {
+        this.setHierarchyForm = { parentId: null, childId: nodeData ? nodeData.userId : null }
+        this.setHierarchyVisible = true
+    },
+    
+    async confirmSetHierarchy() {
+        if (!this.setHierarchyForm.parentId || !this.setHierarchyForm.childId) {
+            this.$message.warning('请选择上级和下级')
+            return
+        }
+        try {
+            await setSupplyHierarchy({
+                systemId: this.system.id,
+                parentId: this.setHierarchyForm.parentId,
+                childId: this.setHierarchyForm.childId
+            })
+            this.$message.success('关系调整成功')
+            this.setHierarchyVisible = false
+            this.loadHierarchy()
+        } catch (e) {
+            this.$message.error('调整失败')
+        }
+    },
+    
+    async handleRemoveRelation(node) {
+        try {
+            await this.$confirm('确定移除该成员的层级关系吗?', '提示', {
+                confirmButtonText: '确定',
+                cancelButtonText: '取消',
+                type: 'warning'
+            })
+            await removeSupplyHierarchy({
+                systemId: this.system.id,
+                childId: node.userId
+            })
+            this.$message.success('关系已移除')
+            this.loadHierarchy()
+        } catch (e) {
+            if (e !== 'cancel') this.$message.error('移除失败')
+        }
+    },
+    
+    renderHierarchyNode(h, { node }) {
+        return h('div', { class: 'custom-tree-node' }, [
+            h('span', `${node.data.nickname} (${node.data.realName || '-'}) - 级别: ${node.data.level}`),
+            h('span', {
+                style: {
+                    marginLeft: '10px'
+                }
+            }, [
+                h('el-button', {
+                    attrs: {
+                        size: 'mini',
+                        type: 'text'
+                    },
+                    on: {
+                        click: (e) => {
+                            e.stopPropagation()
+                            this.showSetHierarchyDialog(node.data)
+                        }
+                    }
+                }, '调整上级'),
+                h('el-button', {
+                    attrs: {
+                        size: 'mini',
+                        type: 'text',
+                        style: 'color: #f56c6c; margin-left: 5px;'
+                    },
+                    on: {
+                        click: (e) => {
+                            e.stopPropagation()
+                            this.handleRemoveRelation(node.data)
+                        }
+                    }
+                }, '移除关系')
+            ])
+        ])
+    },
 }
 </script>
 
 <style scoped>
 .supply-system-detail {
-  padding: 20px;
+    padding: 20px;
+}
+
+.custom-tree-node {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    width: 100%;
 }
 </style>