Explorar o código

添加团队成员分润功能管理后台页面展示

wilhelm wong hai 2 meses
pai
achega
499279a574
Modificáronse 3 ficheiros con 302 adicións e 32 borrados
  1. 11 0
      src/services/api.js
  2. 225 23
      src/views/DomainView.vue
  3. 66 9
      src/views/TeamMembersView.vue

+ 11 - 0
src/services/api.js

@@ -382,6 +382,17 @@ export const getMemberStatistics = async () => {
   return response.data
 }
 
+// 获取团队成员个人统计信息
+export const getMemberStatisticsDetail = async (teamMemberId, startDate, endDate, domain) => {
+  const params = { teamMemberId }
+  if (startDate) params.startDate = startDate
+  if (endDate) params.endDate = endDate
+  if (domain) params.domain = domain
+  
+  const response = await api.get('/team-members/statistics/member', { params })
+  return response.data
+}
+
 // ==================== 推广链接相关API ====================
 
 // 创建推广链接

+ 225 - 23
src/views/DomainView.vue

@@ -8,7 +8,8 @@ import {
   getTeamDomain,
   showTeamDomains,
   getTeamDomainDailyStatistics,
-  getTeamDomainAllStatistics
+  getTeamDomainAllStatistics,
+  listMembers
 } from '@/services/api'
 import { useToast } from 'primevue/usetoast'
 import { useConfirm } from 'primevue/useconfirm'
@@ -54,6 +55,7 @@ const dialogVisible = ref(false)
 const isEditing = ref(false)
 const domainModel = reactive({
   teamId: null,
+  teamMemberId: null,
   domain: '',
   description: ''
 })
@@ -66,15 +68,24 @@ const searchForm = ref({
 
 // 计算当前用户的团队ID
 const currentTeamId = computed(() => {
+  console.log('isAdmin:', isAdmin.value)
+  console.log('isTeam:', isTeam.value)
+  console.log('isPromoter:', isPromoter.value)
+  console.log('userStore.userInfo:', userStore.userInfo)
+  
   if (isAdmin.value) {
     // 管理员可以选择团队,这里先返回null,在创建/编辑时手动指定
     return null
   } else if (isTeam.value) {
     // 队长从team表获取teamId
-    return userStore.userInfo?.teamId
+    const teamId = userStore.userInfo?.teamId
+    console.log('团队用户teamId:', teamId)
+    return teamId
   } else if (isPromoter.value) {
     // 推广员从team-members表获取teamId
-    return userStore.userInfo?.teamId
+    const teamId = userStore.userInfo?.teamId
+    console.log('推广员teamId:', teamId)
+    return teamId
   }
   return null
 })
@@ -93,6 +104,8 @@ const fetchData = async (page = 0) => {
       adminGroupedData.value = result || {}
       // 同时获取统计数据
       await fetchDomainStatistics()
+      // 为管理员加载所有团队的成员数据
+      await loadAllTeamMembers()
     } else {
       // 其他角色使用原有接口
       const result = await listTeamDomains(
@@ -113,26 +126,30 @@ const fetchData = async (page = 0) => {
       }
       
       // 为团队用户获取统计信息
-      if (!isAdmin.value) {
-        await fetchDomainStatistics()
-        // 将统计信息合并到表格数据中
-        if (tableData.value.data && tableData.value.data.length > 0) {
-          tableData.value.data = tableData.value.data.map(domain => {
-            const todayStats = domainStatistics.value[domain.domain] || {}
-            const allStats = domainAllStatistics.value[domain.domain] || {}
-            return {
-              ...domain,
-              todayNewUsers: todayStats.todayNewUsers || 0,
-              todayActiveUsers: todayStats.todayActiveUsers || 0,
-              todayIncome: todayStats.todayIncome || 0,
-              todaySales: todayStats.todaySales || 0,
-              totalIncome: allStats.totalIncome || 0,
-              totalSales: allStats.totalSales || 0
-            }
-          })
-        }
+      await fetchDomainStatistics()
+      // 将统计信息合并到表格数据中
+      if (tableData.value.data && tableData.value.data.length > 0) {
+        tableData.value.data = tableData.value.data.map(domain => {
+          const todayStats = domainStatistics.value[domain.domain] || {}
+          const allStats = domainAllStatistics.value[domain.domain] || {}
+          return {
+            ...domain,
+            todayNewUsers: todayStats.todayNewUsers || 0,
+            todayActiveUsers: todayStats.todayActiveUsers || 0,
+            todayIncome: todayStats.todayIncome || 0,
+            todaySales: todayStats.todaySales || 0,
+            totalIncome: allStats.totalIncome || 0,
+            totalSales: allStats.totalSales || 0
+          }
+        })
       }
     }
+    
+    // 为所有用户加载团队成员数据
+    if (!isAdmin.value) {
+      // 直接请求团队成员接口,不依赖teamId
+      await fetchTeamMembersForCurrentUser()
+    }
   } catch (error) {
     console.error('获取域名列表失败', error)
     toast.add({ severity: 'error', summary: '错误', detail: '获取域名列表失败', life: 3000 })
@@ -251,6 +268,7 @@ const fetchDomainStatistics = async () => {
 
 const resetModel = () => {
   domainModel.teamId = currentTeamId.value
+  domainModel.teamMemberId = null
   domainModel.domain = ''
   domainModel.description = ''
 }
@@ -272,8 +290,14 @@ const onEdit = async (domain = null) => {
 
       // 填充表单数据
       domainModel.teamId = detail.teamId
+      domainModel.teamMemberId = detail.teamMemberId || null
       domainModel.domain = detail.domain
       domainModel.description = detail.description
+      
+      // 如果是管理员,加载团队成员数据
+      if (isAdmin.value && detail.teamId) {
+        await fetchTeamMembers(detail.teamId)
+      }
     } catch (error) {
       toast.add({ severity: 'error', summary: '错误', detail: '获取域名详情失败', life: 3000 })
       return
@@ -327,6 +351,11 @@ const onSubmit = async () => {
         domainData.teamId = domainModel.teamId
       }
 
+      // 添加团队成员绑定
+      if (domainModel.teamMemberId !== null && domainModel.teamMemberId !== '') {
+        domainData.teamMemberId = domainModel.teamMemberId
+      }
+
       await updateTeamDomain(selectedDomain.value.id, domainData)
       toast.add({ severity: 'success', summary: '成功', detail: '更新域名成功', life: 3000 })
     } else {
@@ -347,6 +376,11 @@ const onSubmit = async () => {
             domainData.teamId = domainModel.teamId
           }
 
+          // 添加团队成员绑定
+          if (domainModel.teamMemberId !== null && domainModel.teamMemberId !== '') {
+            domainData.teamMemberId = domainModel.teamMemberId
+          }
+
           await createTeamDomain(domainData)
           successCount++
         } catch (error) {
@@ -370,10 +404,11 @@ const onSubmit = async () => {
     dialogVisible.value = false
     refreshData()
   } catch (error) {
+    const errorMessage = error?.message || error?.detail || (isEditing.value ? '更新域名失败' : '创建域名失败')
     toast.add({
       severity: 'error',
       summary: '错误',
-      detail: isEditing.value ? '更新域名失败' : '创建域名失败',
+      detail: errorMessage,
       life: 3000
     })
   }
@@ -400,7 +435,8 @@ const onDelete = async (domain) => {
         toast.add({ severity: 'success', summary: '成功', detail: '删除域名成功', life: 3000 })
         refreshData()
       } catch (error) {
-        toast.add({ severity: 'error', summary: '错误', detail: '删除域名失败', life: 3000 })
+        const errorMessage = error?.message || error?.detail || '删除域名失败'
+        toast.add({ severity: 'error', summary: '错误', detail: errorMessage, life: 3000 })
       }
     }
   })
@@ -418,6 +454,87 @@ const teamOptions = computed(() => {
   ]
 })
 
+// 团队成员数据
+const teamMembers = ref([])
+
+// 计算团队成员选项
+const teamMemberOptions = computed(() => {
+  if (!domainModel.teamId) return []
+  
+  return [
+    { label: '绑定到团队', value: null },
+    ...teamMembers.value.map((member) => ({
+      label: `${member.name} (${member.commissionRate || 0}%)`,
+      value: member.id
+    }))
+  ]
+})
+
+// 获取团队成员数据
+const fetchTeamMembers = async (teamId) => {
+  if (!teamId) {
+    teamMembers.value = []
+    return
+  }
+  
+  try {
+    const response = await listMembers(0, 100, undefined, teamId)
+    teamMembers.value = response.content || []
+  } catch (error) {
+    console.error('获取团队成员失败:', error)
+    teamMembers.value = []
+    toast.add({
+      severity: 'error',
+      summary: '错误',
+      detail: '获取团队成员失败',
+      life: 3000
+    })
+  }
+}
+
+// 为当前用户获取团队成员数据(不依赖teamId)
+const fetchTeamMembersForCurrentUser = async () => {
+  try {
+    console.log('正在获取当前用户的团队成员数据')
+    const response = await listMembers(0, 100)
+    teamMembers.value = response.content || []
+    console.log('获取到的团队成员数据:', teamMembers.value)
+  } catch (error) {
+    console.error('获取团队成员失败:', error)
+    teamMembers.value = []
+    toast.add({
+      severity: 'error',
+      summary: '错误',
+      detail: '获取团队成员失败',
+      life: 3000
+    })
+  }
+}
+
+// 为管理员加载所有团队的成员数据
+const loadAllTeamMembers = async () => {
+  try {
+    const allMembers = []
+    for (const team of teamStore.teams) {
+      const response = await listMembers(0, 100, undefined, team.id)
+      if (response.content) {
+        allMembers.push(...response.content)
+      }
+    }
+    teamMembers.value = allMembers
+  } catch (error) {
+    console.error('获取所有团队成员失败:', error)
+    teamMembers.value = []
+  }
+}
+
+// 处理团队选择变化
+const handleTeamChange = async (event) => {
+  const teamId = event.value
+  domainModel.teamMemberId = null
+  await fetchTeamMembers(teamId)
+}
+
 // 获取团队名称
 const getTeamName = (teamId) => {
   if (!teamId) return '-'
@@ -425,6 +542,33 @@ const getTeamName = (teamId) => {
   return team ? team.name : '-'
 }
 
+// 获取绑定用户名
+const getBoundUserName = (domain) => {
+  if (domain.teamMemberId) {
+    // 如果绑定到个人,从团队成员列表中查找
+    // 确保类型匹配(字符串和数字)
+    console.log("teamMembers.value", teamMembers.value)
+    const member = teamMembers.value.find(m => 
+      m.id === domain.teamMemberId || 
+      m.id === parseInt(domain.teamMemberId) ||
+      parseInt(m.id) === domain.teamMemberId
+    )
+    if (member) {
+      return member.name
+    }
+    
+    // 如果团队成员列表中没有找到,尝试从域名数据中获取
+    if (domain.teamMemberName) {
+      return domain.teamMemberName
+    }
+    
+    return '未知用户'
+  } else {
+    // 如果绑定到团队,显示团队名称
+    return getTeamName(domain.teamId)
+  }
+}
+
 // 计算管理员的分组数据,转换为数组格式
 const adminGroupedList = computed(() => {
   if (!isAdmin.value) return []
@@ -499,6 +643,10 @@ onMounted(() => {
                   <i class="pi pi-copy copy-icon"></i>
                 </div>
                 <div class="domain-description">{{ domain.description || '暂无描述' }}</div>
+                <div class="domain-bound-user">
+                  <span class="bound-label">绑定到:</span>
+                  <span class="bound-user-name">{{ getBoundUserName(domain) }}</span>
+                </div>
                 <div class="domain-time">创建时间: {{ formatDate(domain.createdAt) }}</div>
               </div>
               <div class="domain-stats-section">
@@ -590,6 +738,13 @@ onMounted(() => {
               </div>
             </template>
           </Column>
+          <Column field="boundUser" header="绑定用户" style="min-width: 150px" headerClass="font-bold">
+            <template #body="slotProps">
+              <span class="bound-user-text">
+                {{ getBoundUserName(slotProps.data) }}
+              </span>
+            </template>
+          </Column>
           <Column field="todayNewUsers" header="今日新增" style="min-width: 100px" headerClass="font-bold">
             <template #body="slotProps">
               <span class="new-user-amount">{{ slotProps.data.todayNewUsers || 0 }}</span>
@@ -661,10 +816,25 @@ onMounted(() => {
               placeholder="请选择团队"
               :disabled="isEditing"
               :showClear="true"
+              @change="handleTeamChange"
             />
             <small v-if="!domainModel.teamId" class="text-red-500">请选择团队</small>
           </div>
 
+          <!-- 选择团队成员绑定 -->
+          <div v-if="domainModel.teamId" class="flex flex-col gap-2">
+            <label class="font-medium">绑定到个人</label>
+            <Select
+              v-model="domainModel.teamMemberId"
+              :options="teamMemberOptions"
+              optionLabel="label"
+              optionValue="value"
+              placeholder="选择团队成员(可选)"
+              :showClear="true"
+            />
+            <small class="text-gray-500">不选择则绑定到团队</small>
+          </div>
+
           <div class="flex flex-col gap-2">
             <label class="font-medium">域名</label>
             <Textarea v-model="domainModel.domain" placeholder="请输入域名,多个域名请换行输入" rows="4" />
@@ -804,6 +974,29 @@ onMounted(() => {
   align-items: flex-start;
 }
 
+.domain-bound-user {
+  font-size: 13px;
+  color: #475569;
+  margin-bottom: 8px;
+  display: flex;
+  align-items: center;
+  gap: 6px;
+}
+
+.bound-label {
+  font-weight: 500;
+  color: #64748b;
+}
+
+.bound-user-name {
+  font-weight: 600;
+  color: #1e40af;
+  background: #dbeafe;
+  padding: 2px 8px;
+  border-radius: 4px;
+  font-size: 12px;
+}
+
 .domain-time {
   font-size: 12px;
   color: #94a3b8;
@@ -909,6 +1102,15 @@ onMounted(() => {
   font-weight: 600;
 }
 
+.bound-user-text {
+  color: #1e40af;
+  font-weight: 500;
+  background: #dbeafe;
+  padding: 4px 8px;
+  border-radius: 4px;
+  font-size: 12px;
+}
+
 .domain-actions-bottom .p-button {
   width: 28px;
   height: 28px;

+ 66 - 9
src/views/TeamMembersView.vue

@@ -92,6 +92,31 @@
         </template>
       </Column> -->
 
+      <Column field="commissionRate" header="分成比例" style="min-width: 120px">
+        <template #body="slotProps">
+          <span class="commission-rate-text font-semibold">
+            {{ slotProps.data.commissionRate || 0 }}%
+          </span>
+        </template>
+      </Column>
+
+      <Column field="totalRevenue" header="总分成" style="min-width: 120px">
+        <template #body="slotProps">
+          <span class="total-revenue-text font-semibold">
+            ¥{{ formatAmount(slotProps.data.totalRevenue) }}
+          </span>
+        </template>
+      </Column>
+
+      <Column field="todayRevenue" header="今日分成" style="min-width: 120px">
+        <template #body="slotProps">
+          <span class="today-revenue-text font-semibold">
+            ¥{{ formatAmount(slotProps.data.todayRevenue) }}
+          </span>
+        </template>
+      </Column>
+
+
       <Column field="createdAt" header="创建时间" style="min-width: 150px">
         <template #body="slotProps">
           <span class="text-sm">
@@ -189,6 +214,23 @@
             inputClass="w-full"
           />
         </div>
+
+        <div class="field mt-4">
+          <label for="edit-commissionRate" class="font-medium text-sm mb-2 block">分成比例 (%)</label>
+          <InputNumber
+            id="edit-commissionRate"
+            v-model="editForm.commissionRate"
+            :min="0"
+            :max="100"
+            :step="0.1"
+            suffix=" %"
+            class="w-full"
+            placeholder="请输入分成比例"
+            :useGrouping="false"
+            :allowEmpty="true"
+            :showButtons="false"
+          />
+        </div>
       </div>
 
       <template #footer>
@@ -273,7 +315,8 @@ const isEdit = ref(false)
 const editForm = ref({
   id: null,
   name: null,
-  teamId: null
+  teamId: null,
+  commissionRate: null
 })
 
 // 搜索表单
@@ -372,11 +415,12 @@ const deleteMemberRecord = async (id) => {
       life: 3000
     })
     fetchData()
-  } catch {
+  } catch (error) {
+    const errorMessage = error?.message || error?.detail || '删除失败'
     toast.add({
       severity: 'error',
       summary: '错误',
-      detail: '删除失败',
+      detail: errorMessage,
       life: 3000
     })
   }
@@ -402,7 +446,8 @@ const openAddDialog = () => {
   editForm.value = {
     id: null,
     name: null,
-    teamId: null
+    teamId: null,
+    commissionRate: null
   }
   editDialog.value = true
 }
@@ -413,7 +458,8 @@ const openEditDialog = (member) => {
   editForm.value = {
     id: member.id,
     name: member.name || null,
-    teamId: member.teamId || null
+    teamId: member.teamId || null,
+    commissionRate: member.commissionRate || null
   }
   editDialog.value = true
 }
@@ -427,9 +473,13 @@ const saveEdit = async () => {
       formData.name = editForm.value.name
     }
 
-    if (isAdmin && editForm.value.teamId !== null && editForm.value.teamId !== '') {
+    if (editForm.value.commissionRate !== null && editForm.value.commissionRate !== '') {
+      formData.commissionRate = editForm.value.commissionRate
+    }
+
+    if (editForm.value.teamId !== null && editForm.value.teamId !== '') {
       formData.teamId = editForm.value.teamId
-      formData.teamUserId = getTeamUserId(editForm.value.teamId)
+      formData.userId = getTeamUserId(editForm.value.teamId)
     }
 
     if (isEdit.value) {
@@ -445,11 +495,12 @@ const saveEdit = async () => {
     })
     editDialog.value = false
     fetchData()
-  } catch {
+  } catch (error) {
+    const errorMessage = error?.message || error?.detail || (isEdit.value ? '更新失败' : '创建失败')
     toast.add({
       severity: 'error',
       summary: '错误',
-      detail: isEdit.value ? '更新失败' : '创建失败',
+      detail: errorMessage,
       life: 3000
     })
   } finally {
@@ -525,6 +576,12 @@ onMounted(() => {
   font-weight: 600;
 }
 
+.commission-rate-text {
+  color: #7c3aed;
+  font-weight: 600;
+}
+
+
 .font-medium {
   font-weight: 500;
 }