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