Explorar o código

添加今日/总体ip成交率字段,展示在团队信息首页

wilhelm wong hai 2 meses
pai
achega
5bb5ee87ea
Modificáronse 2 ficheiros con 155 adicións e 39 borrados
  1. 6 0
      src/services/api.js
  2. 149 39
      src/views/TeamView.vue

+ 6 - 0
src/services/api.js

@@ -320,6 +320,12 @@ export const getAllTeamStatistics = async () => {
   return response.data
 }
 
+// 获取团队IP成交率(仅团队用户)
+export const getTeamIpConversionRate = async () => {
+  const response = await api.get('/teams/statistics/ip-conversion-rate')
+  return response.data
+}
+
 // 单个团队统计数据-废弃
 // export const getTeamStatistics = async () => {
 //   const response = await api.get('/teams/statistics/team')

+ 149 - 39
src/views/TeamView.vue

@@ -153,52 +153,97 @@
       </Column>
     </DataTable>
 
-    <div v-else-if="isTeam && teamData" class="flex flex-col items-center justify-center py-4">
-      <div class="w-full max-w-xl p-10">
-        <div class="text-2xl font-extrabold mb-2 text-center text-blue-700 tracking-wide">我的团队信息</div>
-        <div class="border-b border-gray-200 mb-6"></div>
-        <div class="grid grid-cols-2 gap-y-6 gap-x-4">
-          <div class="flex flex-col items-start">
-            <span class="text-gray-500 text-base font-medium mb-1">团队名称</span>
-            <span class="text-lg font-bold text-gray-900">{{ teamData.name }}</span>
+    <div v-else-if="isTeam && teamData" class="py-2">
+      <div class="grid grid-cols-1 lg:grid-cols-2 gap-4">
+        <!-- 左侧:基本信息卡片(与右侧均分) -->
+        <div>
+          <div class="rounded-lg border border-gray-200 bg-white p-6 shadow-sm">
+            <div class="text-xl font-extrabold mb-4 text-gray-900">我的团队信息</div>
+            <div class="grid grid-cols-1 sm:grid-cols-2 gap-y-5 gap-x-6">
+              <div class="flex flex-col">
+                <span class="text-gray-500 text-sm mb-1">团队名称</span>
+                <span class="text-base font-bold text-gray-900">{{ teamData.name }}</span>
+              </div>
+              <div class="flex flex-col">
+                <span class="text-gray-500 text-sm mb-1">佣金比例</span>
+                <span class="text-base font-bold text-purple-700">{{ teamData.commissionRate }}%</span>
+              </div>
+              <div class="flex flex-col">
+                <span class="text-gray-500 text-sm mb-1">推广码</span>
+                <span
+                  class="text-base font-bold text-orange-600 font-mono copyable-text cursor-pointer"
+                  :title="teamData.affCode"
+                  @click="copyToClipboard(teamData.affCode)"
+                >
+                  {{ teamData.affCode }}
+                </span>
+              </div>
+              <div class="flex flex-col">
+                <span class="text-gray-500 text-sm mb-1">总用户数</span>
+                <span class="text-lg font-extrabold text-teal-600">{{ teamData.totalUsers || 0 }}</span>
+              </div>
+              
+            </div>
           </div>
-          <div class="flex flex-col items-start">
-            <span class="text-gray-500 text-base font-medium mb-1">佣金比例</span>
-            <span class="text-lg font-bold text-purple-700">{{ teamData.commissionRate }}%</span>
+        </div>
+
+        <!-- 右侧:统计快照(与左侧均分) -->
+        <div class="grid grid-cols-2 gap-4">
+          <div class="stat-card stat-yellow">
+            <div class="icon-badge icon-yellow"><i class="pi pi-dollar"></i></div>
+            <div class="stat-body">
+              <div class="stat-title">今日分润收入</div>
+              <div class="stat-value text-yellow-700">¥{{ formatAmount(teamData.todayRevenue) }}</div>
+            </div>
           </div>
-          <div class="flex flex-col items-start">
-            <span class="text-gray-500 text-base font-medium mb-1">总分润收入</span>
-            <span class="text-2xl font-extrabold text-blue-600">¥{{ formatAmount(teamData.totalRevenue) }}</span>
+          <div class="stat-card stat-blue">
+            <div class="icon-badge icon-blue"><i class="pi pi-wallet"></i></div>
+            <div class="stat-body">
+              <div class="stat-title">总分润收入</div>
+              <div class="stat-value text-blue-700">¥{{ formatAmount(teamData.totalRevenue) }}</div>
+            </div>
           </div>
-          <div class="flex flex-col items-start">
-            <span class="text-gray-500 text-base font-medium mb-1">总销售额</span>
-            <span class="text-2xl font-extrabold text-orange-600">¥{{ formatAmount(teamData.totalSales) }}</span>
+          <div class="stat-card stat-green">
+            <div class="icon-badge icon-green"><i class="pi pi-shopping-cart"></i></div>
+            <div class="stat-body">
+              <div class="stat-title">今日销售额</div>
+              <div class="stat-value text-green-700">¥{{ formatAmount(teamData.todaySales) }}</div>
+            </div>
           </div>
-          <div class="flex flex-col items-start">
-            <span class="text-gray-500 text-base font-medium mb-1">今日分润收入</span>
-            <span class="text-2xl font-extrabold text-green-600">¥{{ formatAmount(teamData.todayRevenue) }}</span>
+          <div class="stat-card stat-orange">
+            <div class="icon-badge icon-orange"><i class="pi pi-chart-line"></i></div>
+            <div class="stat-body">
+              <div class="stat-title">总销售额</div>
+              <div class="stat-value text-orange-700">¥{{ formatAmount(teamData.totalSales) }}</div>
+            </div>
           </div>
-          <div class="flex flex-col items-start">
-            <span class="text-gray-500 text-base font-medium mb-1">今日销售额</span>
-            <span class="text-2xl font-extrabold text-red-600">¥{{ formatAmount(teamData.todaySales) }}</span>
+          <div class="stat-card stat-indigo">
+            <div class="icon-badge icon-indigo"><i class="pi pi-users"></i></div>
+            <div class="stat-body">
+              <div class="stat-title">今日日活</div>
+              <div class="stat-value text-indigo-700">{{ teamData.todayDAU || 0 }}</div>
+            </div>
           </div>
-          <div class="flex flex-col items-start">
-            <span class="text-gray-500 text-base font-medium mb-1">今日日活用户</span>
-            <span class="text-2xl font-extrabold text-indigo-600">{{ teamData.todayDAU || 0 }}</span>
+          <div class="stat-card stat-slate">
+            <div class="icon-badge icon-slate"><i class="pi pi-database"></i></div>
+            <div class="stat-body">
+              <div class="stat-title">总用户</div>
+              <div class="stat-value text-slate-800">{{ teamData.totalUsers || 0 }}</div>
+            </div>
           </div>
-          <div class="flex flex-col items-start">
-            <span class="text-gray-500 text-base font-medium mb-1">总用户数</span>
-            <span class="text-2xl font-extrabold text-teal-600">{{ teamData.totalUsers || 0 }}</span>
+          <div class="stat-card stat-indigo">
+            <div class="icon-badge icon-indigo"><i class="pi pi-chart-bar"></i></div>
+            <div class="stat-body">
+              <div class="stat-title">今日IP成交率</div>
+              <div class="stat-value text-indigo-700">{{ formatPercent(ipStats?.todayIpConversionRate) }}</div>
+            </div>
           </div>
-          <div class="flex flex-col items-start">
-            <span class="text-gray-500 text-base font-medium mb-1">推广码</span>
-            <span 
-              class="text-lg font-bold text-orange-600 font-mono copyable-text cursor-pointer"
-              :title="teamData.affCode"
-              @click="copyToClipboard(teamData.affCode)"
-            >
-              {{ teamData.affCode }}
-            </span>
+          <div class="stat-card stat-slate">
+            <div class="icon-badge icon-slate"><i class="pi pi-chart-line"></i></div>
+            <div class="stat-body">
+              <div class="stat-title">总IP成交率</div>
+              <div class="stat-value text-slate-800">{{ formatPercent(ipStats?.totalIpConversionRate) }}</div>
+            </div>
           </div>
         </div>
       </div>
@@ -293,7 +338,7 @@ import InputNumber from 'primevue/inputnumber'
 import Password from 'primevue/password'
 import { useConfirm } from 'primevue/useconfirm'
 import { useToast } from 'primevue/usetoast'
-import { listTeams, createTeam, updateTeam, deleteTeam } from '@/services/api'
+import { listTeams, createTeam, updateTeam, deleteTeam, getTeamIpConversionRate } from '@/services/api'
 
 const toast = useToast()
 const confirm = useConfirm()
@@ -338,6 +383,14 @@ const formatAmount = (amount) => {
   return Number(amount).toFixed(2)
 }
 
+// 百分比格式化(0-1 -> 0.00%)
+const formatPercent = (rate) => {
+  if (rate === undefined || rate === null) return '0.00%'
+  const num = Number(rate)
+  if (Number.isNaN(num)) return '0.00%'
+  return `${(num * 100).toFixed(2)}%`
+}
+
 // 格式化日期时间
 const formatDateTime = (dateString) => {
   if (!dateString) return '-'
@@ -376,6 +429,24 @@ const fetchData = async () => {
   }
 }
 
+// 团队IP成交率
+const ipStats = ref(null)
+const loadIpConversionRate = async () => {
+  try {
+    const data = await getTeamIpConversionRate()
+    ipStats.value = data
+  } catch (e) {
+    // 团队角色无权限外的错误提示
+    // 接口做了权限限制,这里仅在请求失败时给出轻提示
+    toast.add({
+      severity: 'warn',
+      summary: '提示',
+      detail: '获取IP成交率失败',
+      life: 2500
+    })
+  }
+}
+
 // 分页处理
 const handlePageChange = (event) => {
   tableData.value.metadata.page = event.page
@@ -537,6 +608,9 @@ const saveEdit = async () => {
 // 初始化
 onMounted(() => {
   fetchData()
+  if (isTeam?.value) {
+    loadIpConversionRate()
+  }
 })
 
 const teamData = computed(() => tableData.value?.content?.[0] || null)
@@ -697,4 +771,40 @@ const teamData = computed(() => tableData.value?.content?.[0] || null)
     min-height: 56px;
   }
 }
+
+/* 统计卡片样式(右侧) */
+.stat-card {
+  border-radius: 0.5rem;
+  border: 1px solid #fde68a; /* amber-300 */
+  padding: 1rem;
+  background: #fffbeb; /* amber-50 统一浅黄色背景 */
+  display: flex;
+  align-items: center;
+  gap: 1rem;
+}
+.stat-title {
+  font-size: 0.875rem; /* text-sm */
+  color: #4b5563; /* gray-600 */
+}
+.stat-value {
+  margin-top: 0.25rem;
+  font-size: 1.5rem; /* text-2xl */
+  font-weight: 800; /* extrabold */
+}
+.icon-badge {
+  width: 40px;
+  height: 40px;
+  border-radius: 9999px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  font-size: 1.125rem; /* text-lg */
+  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
+}
+.icon-yellow { background: #fef3c7; border: 1px solid #fde68a; color: #ca8a04; }
+.icon-blue { background: #dbeafe; border: 1px solid #bfdbfe; color: #2563eb; }
+.icon-green { background: #dcfce7; border: 1px solid #bbf7d0; color: #16a34a; }
+.icon-orange { background: #ffedd5; border: 1px solid #fed7aa; color: #ea580c; }
+.icon-indigo { background: #e0e7ff; border: 1px solid #c7d2fe; color: #4f46e5; }
+.icon-slate { background: #f1f5f9; border: 1px solid #e2e8f0; color: #475569; }
 </style>