فهرست منبع

新页面数据

pxb 2 هفته پیش
والد
کامیت
87e0786587
4فایلهای تغییر یافته به همراه312 افزوده شده و 162 حذف شده
  1. 36 0
      src/api/data.js
  2. 9 2
      src/components/Breadcrumb/index.vue
  3. 156 97
      src/views/user/userOnlineSummary/index.vue
  4. 111 63
      src/views/user/userStatistics/index.vue

+ 36 - 0
src/api/data.js

@@ -772,6 +772,42 @@ export function setJisuOpenCode(params) {
   })
 }
 
+// 在线汇总:在线用户
+export function getOnlineUsers(params) {
+  return request({
+    url: 'online/users',
+    method: 'get',
+    params: params
+  })
+}
+
+// 在线汇总:新用户汇总
+export function getOnlineNewUserSummary(params) {
+  return request({
+    url: 'online/newUserSummary',
+    method: 'get',
+    params: params
+  })
+}
+
+// 注册/域名统计:会员注册走势
+export function getRegisterTrend(params) {
+  return request({
+    url: 'user/registerTrend',
+    method: 'get',
+    params: params
+  })
+}
+
+// 注册/域名统计:注册域名统计
+export function getRegisterDomain(params) {
+  return request({
+    url: 'user/registerDomain',
+    method: 'get',
+    params: params
+  })
+}
+
 /**
  * 彩票配置
  * @description GET请求

+ 9 - 2
src/components/Breadcrumb/index.vue

@@ -13,7 +13,7 @@
       @close="close(tag)"
       style="margin-top: 20px;margin-right: 10px;cursor: pointer"
       v-for="tag in keepList"
-      :key="tag.name"
+      :key="tag.path"
       closable
       :type="activeMenu(tag)"
     >
@@ -32,7 +32,14 @@ export default {
   },
   computed: {
     keepList() {
-      return this.$store.state.user.keepList
+      const list = this.$store.state.user.keepList || []
+      // 按 path 去重,避免标题相同但路径不同的标签造成重复 key
+      const seen = new Set()
+      return list.filter(item => {
+        if (!item || !item.path || seen.has(item.path)) return false
+        seen.add(item.path)
+        return true
+      })
     }
 
   },

+ 156 - 97
src/views/user/userOnlineSummary/index.vue

@@ -1,4 +1,4 @@
-<template>
+<template>
   <div class="new-app-container layout-vertical summary-page">
     <div class="content-panel">
       <el-tabs v-model="activeTab" class="summary-tabs" @tab-click="handleTabClick">
@@ -10,14 +10,14 @@
       <template v-if="activeTab === 'online'">
         <YMTableSearch :loading="loading" :search-form="onlineSearchForm" @submit="onSubmit" />
         <div class="table-section">
-          <el-table v-loading="loading" :data="onlinePagedData"
+          <el-table v-loading="loading" :data="onlineListData"
             height="calc(100vh - 340px)" style="width: 100%; border-radius: 12px; overflow: hidden;"
             empty-text="暂无在线用户数据">
             <el-table-column prop="member_id" label="会员ID" width="100" align="center" />
             <el-table-column prop="first_name" label="会员昵称" width="130" align="center" />
             <el-table-column prop="balance" label="账户余额" width="120" align="center">
               <template slot-scope="{ row }">
-                <span style="color: #F56C6C; font-weight: bold;">{{ row.balance != null ? Number(row.balance).toFixed(2) : '--' }}</span>
+                <span style="color: #18C80A; font-weight: bold;">{{ row.balance != null ? Number(row.balance).toFixed(2) : '--' }}</span>
               </template>
             </el-table-column>
             <el-table-column prop="ip" label="IP地址" width="150" align="center" />
@@ -26,27 +26,49 @@
             <el-table-column prop="platform" label="平台" width="100" align="center" />
             <el-table-column prop="browser" label="浏览器" width="120" align="center" />
             <el-table-column prop="os" label="操作系统" width="120" align="center" />
-            <el-table-column prop="login_time" label="登入时间" width="175" align="center" :formatter="formatTime" />
+            <el-table-column prop="login_time" label="登入时间" width="175" align="center" />
             <el-table-column label="操作" width="90" align="center">
               <template slot-scope="{ row }">
                 <el-button type="text" class="ym-view-btn" @click="handleOnlineView(row)">查看</el-button>
               </template>
             </el-table-column>
           </el-table>
-          <el-pagination v-if="onlineFilteredData.length" background
+          <el-pagination v-if="onlineTotal" background
             layout="total, prev, pager, next, jumper" :page-size="onlinePageSize"
-            :page-sizes="[15, 30, 45, 60]" :current-page="onlinePage"
-            :total="onlineFilteredData.length"
-            @current-change="onlinePage = $event" @size-change="handleOnlineSizeChange" />
+            :page-sizes="[15, 30, 45, 60]" :current-page.sync="onlinePage"
+            :total="onlineTotal"
+            @current-change="fetchOnline" @size-change="handleOnlineSizeChange" />
         </div>
       </template>
 
       <!-- 新用户汇总 -->
       <template v-if="activeTab === 'newUser'">
-        <YMTableSearch :loading="loading" :search-form="newUserSearchForm" @submit="onSubmit" />
-        <div class="info-lines">
-          <div class="info-line">1. 活跃会员:新注册会员下注&gt;=¥50</div>
-          <div class="info-line">2. 有效会员:下注金额&gt;=¥50的会员数</div>
+        <div class="filter-panel">
+          <el-form inline :model="query" class="filter-form">
+            <el-form-item label="时间">
+              <el-date-picker
+                v-model="query.dateRange"
+                type="daterange"
+                unlink-panels
+                range-separator="至"
+                start-placeholder="开始日期"
+                end-placeholder="结束日期"
+                value-format="yyyy-MM-dd"
+                :picker-options="pickerOptions"
+                :clearable="false"
+              />
+            </el-form-item>
+            <el-form-item class="filter-actions">
+              <el-button type="primary" size="small" :loading="loading" @click="handleSearch">
+                查询
+              </el-button>
+              <el-button size="small" @click="resetQuery">重置</el-button>
+            </el-form-item>
+          </el-form>
+        </div>
+        <div class="info-lines" v-if="newUserRules">
+          <div class="info-line">1. 活跃会员:{{ newUserRules.active_member }}</div>
+          <div class="info-line">2. 有效会员:{{ newUserRules.valid_member }}</div>
         </div>
         <div class="table-section">
           <el-table v-loading="loading" :data="newUserPagedData"
@@ -155,31 +177,9 @@
 </template>
 
 <script>
+import { getOnlineUsers, getOnlineNewUserSummary } from '@/api/data'
 import Dialog from '@/components/Dialog/index.vue'
 
-const ONLINE_MOCK = [
-  { member_id: 10001, first_name: "test01", balance: 1250.50, ip: "192.168.1.100", location: "上海", website: "www.example.com", platform: "PC", browser: "Chrome 120", os: "Windows 10", login_time: 1722499200 },
-  { member_id: 10002, first_name: "test02", balance: 3200.00, ip: "192.168.1.101", location: "北京", website: "com.example.app", platform: "Android", browser: "WebView", os: "Android 14", login_time: 1722502800 },
-  { member_id: 10003, first_name: "test03", balance: 580.20, ip: "192.168.1.102", location: "广州", website: "www.example.com", platform: "iOS", browser: "Safari 17", os: "iOS 17.5", login_time: 1722506400 },
-  { member_id: 10004, first_name: "test04", balance: 8880.00, ip: "10.0.0.1", location: "深圳", website: "www.example.com", platform: "PC", browser: "Edge 120", os: "Windows 11", login_time: 1722513600 },
-  { member_id: 10005, first_name: "test05", balance: 150.00, ip: "192.168.1.105", location: "杭州", website: "com.example.mini", platform: "WeChat", browser: "XWeb", os: "iOS 17.4", login_time: 1722517200 },
-  { member_id: 10006, first_name: "test06", balance: 0.00, ip: "172.16.0.1", location: "纽约", website: "www.example.com", platform: "PC", browser: "Chrome 121", os: "macOS 14", login_time: 1722524400 },
-  { member_id: 10007, first_name: "test07", balance: 2350.00, ip: "192.168.1.107", location: "武汉", website: "com.example.app", platform: "iOS", browser: "Safari 17", os: "iOS 17.5", login_time: 1722528000 },
-  { member_id: 10008, first_name: "test08", balance: 680.50, ip: "192.168.1.108", location: "南京", website: "m.example.com", platform: "Mobile", browser: "Chrome Mobile", os: "Android 13", login_time: 1722535200 },
-  { member_id: 10009, first_name: "test09", balance: 4200.00, ip: "192.168.1.109", location: "重庆", website: "com.example.app", platform: "Android", browser: "WebView", os: "Android 12", login_time: 1722538800 },
-  { member_id: 10010, first_name: "test10", balance: 920.00, ip: "192.168.1.110", location: "厦门", website: "com.example.app", platform: "iOS", browser: "Safari 17", os: "iOS 17.5", login_time: 1722546000 },
-]
-
-const NEWUSER_MOCK = [
-  { date: "2026-08-01", h5_reg: 45, app_reg: 32, h5_first_deposit: 28, app_first_deposit: 20, active_members: 35, total_deposit: 28500, total_bet: 156000, company_profit: 8200, valid_members: 30 },
-  { date: "2026-08-02", h5_reg: 52, app_reg: 38, h5_first_deposit: 31, app_first_deposit: 24, active_members: 42, total_deposit: 32100, total_bet: 183000, company_profit: 9500, valid_members: 36 },
-  { date: "2026-08-03", h5_reg: 38, app_reg: 29, h5_first_deposit: 22, app_first_deposit: 18, active_members: 30, total_deposit: 19800, total_bet: 112000, company_profit: 6100, valid_members: 25 },
-  { date: "2026-08-04", h5_reg: 61, app_reg: 45, h5_first_deposit: 35, app_first_deposit: 28, active_members: 48, total_deposit: 35600, total_bet: 201000, company_profit: 11200, valid_members: 42 },
-  { date: "2026-08-05", h5_reg: 49, app_reg: 36, h5_first_deposit: 30, app_first_deposit: 22, active_members: 38, total_deposit: 26800, total_bet: 145000, company_profit: 7800, valid_members: 33 },
-  { date: "2026-08-06", h5_reg: 55, app_reg: 41, h5_first_deposit: 33, app_first_deposit: 26, active_members: 44, total_deposit: 34000, total_bet: 192000, company_profit: 10500, valid_members: 38 },
-  { date: "2026-08-07", h5_reg: 42, app_reg: 30, h5_first_deposit: 25, app_first_deposit: 19, active_members: 33, total_deposit: 22000, total_bet: 128000, company_profit: 6900, valid_members: 28 },
-]
-
 export default {
   name: 'UserOnlineSummary',
   components: {
@@ -190,39 +190,39 @@ export default {
       activeTab: 'online',
       loading: false,
       onlineSearchForm: [
-        { label: "会员ID", type: "input", prop: "member_id", clearable: true },
-        { label: "会员昵称", type: "input", prop: "first_name", clearable: true },
-        { label: "IP地址", type: "input", prop: "ip", clearable: true },
-        { label: "平台", type: "select", prop: "platform", options: [
-          { label: "全部", value: "" },
-          { label: "PC", value: "PC" },
-          { label: "Android", value: "Android" },
-          { label: "iOS", value: "iOS" },
-          { label: "Mobile", value: "Mobile" },
-          { label: "WeChat", value: "WeChat" },
+        { label: '会员ID', type: 'input', prop: 'member_id', clearable: true },
+        { label: '会员昵称', type: 'input', prop: 'first_name', clearable: true },
+        { label: 'IP地址', type: 'input', prop: 'login_ip', clearable: true },
+        { label: '平台', type: 'select', prop: 'platform', options: [
+          { label: '全部', value: '' },
+          { label: 'PC', value: 'PC' },
+          { label: 'Android', value: 'Android' },
+          { label: 'iOS', value: 'iOS' },
+          { label: 'WeChat', value: 'WeChat' },
         ], clearable: true },
       ],
-      newUserSearchForm: [
-        { label: "开始日期", type: "date", prop: "start_date", clearable: true },
-        { label: "结束日期", type: "date", prop: "end_date", clearable: true },
-      ],
-      onlineFilteredData: [],
-      newUserFilteredData: [],
-      allOnlineData: [...ONLINE_MOCK],
-      allNewUserData: [...NEWUSER_MOCK],
-      onlineViewRow: null,
-      newUserViewRow: null,
+      query: {
+        dateRange: [this.formatDefaultDate(-30), this.formatDefaultDate(0)]
+      },
+      pickerOptions: {
+        disabledDate(time) {
+          return time.getTime() > Date.now()
+        }
+      },
+      onlineListData: [],
+      onlineTotal: 0,
       onlinePage: 1,
       onlinePageSize: 15,
+      newUserFilteredData: [],
       newUserPage: 1,
       newUserPageSize: 15,
+      newUserRules: null,
+      onlineViewRow: null,
+      newUserViewRow: null,
+      searchParams: {},
     }
   },
   computed: {
-    onlinePagedData() {
-      const start = (this.onlinePage - 1) * this.onlinePageSize
-      return this.onlineFilteredData.slice(start, start + this.onlinePageSize)
-    },
     newUserPagedData() {
       const start = (this.newUserPage - 1) * this.newUserPageSize
       return this.newUserFilteredData.slice(start, start + this.newUserPageSize)
@@ -240,7 +240,7 @@ export default {
         { label: '平台', prop: 'platform', value: row.platform || '--' },
         { label: '浏览器', prop: 'browser', value: row.browser || '--' },
         { label: '操作系统', prop: 'os', value: row.os || '--' },
-        { label: '登入时间', prop: 'login_time', value: this.formatTime(row) }
+        { label: '登入时间', prop: 'login_time', value: row.login_time || '--' }
       ]
     },
     newUserViewFields() {
@@ -264,6 +264,7 @@ export default {
     handleOnlineSizeChange(val) {
       this.onlinePageSize = val
       this.onlinePage = 1
+      this.fetchOnline()
     },
     handleNewUserSizeChange(val) {
       this.newUserPageSize = val
@@ -277,45 +278,103 @@ export default {
       this.newUserViewRow = { ...row }
       this.$refs.newUserViewDialog.open()
     },
-    formatTime(row) {
-      if (!row.login_time) return "--"
-      const d = new Date(row.login_time * 1000)
-      const pad = n => String(n).padStart(2, "0")
-      return d.getFullYear() + "-" + pad(d.getMonth()+1) + "-" + pad(d.getDate()) + " " + pad(d.getHours()) + ":" + pad(d.getMinutes()) + ":" + pad(d.getSeconds())
-    },
     handleTabClick() {
       this.onSubmit({})
     },
+    handleSearch() {
+      this.onSubmit({})
+    },
+    resetQuery() {
+      // 重置日期为最近一个月(今天往前 30 天到今天)
+      this.query.dateRange = [this.formatDefaultDate(-30), this.formatDefaultDate(0)]
+      this.onSubmit({})
+    },
     onValueChange(val) {},
+    formatDefaultDate(offsetDays) {
+      const d = new Date()
+      d.setDate(d.getDate() + offsetDays)
+      const pad = n => String(n).padStart(2, '0')
+      return d.getFullYear() + '-' + pad(d.getMonth() + 1) + '-' + pad(d.getDate())
+    },
     onSubmit(params) {
-      this.loading = true
       params = params || {}
-      if (this.activeTab === "online") {
-        this.filterOnline(params)
+      this.searchParams = { ...params }
+      this.onlinePage = 1
+      this.newUserPage = 1
+      if (this.activeTab === 'online') {
+        this.fetchOnline()
       } else {
-        this.filterNewUser(params)
+        this.fetchNewUser()
       }
-      this.loading = false
-    },
-    filterOnline(params) {
-      let filtered = [...this.allOnlineData]
-      if (params.member_id) filtered = filtered.filter(r => String(r.member_id).includes(params.member_id))
-      if (params.first_name) filtered = filtered.filter(r => r.first_name.includes(params.first_name))
-      if (params.ip) filtered = filtered.filter(r => r.ip.includes(params.ip))
-      if (params.platform) filtered = filtered.filter(r => r.platform === params.platform)
-      this.onlineFilteredData = filtered
-      this.onlinePage = 1
     },
-    filterNewUser(params) {
-      let filtered = [...this.allNewUserData]
-      if (params.start_date) {
-        filtered = filtered.filter(r => r.date >= params.start_date)
-      }
-      if (params.end_date) {
-        filtered = filtered.filter(r => r.date <= params.end_date)
+    fetchOnline() {
+      this.loading = true
+      const params = {
+        page: this.onlinePage,
+        limit: this.onlinePageSize,
+        ...this.searchParams
       }
-      this.newUserFilteredData = filtered
-      this.newUserPage = 1
+      getOnlineUsers(params)
+        .then(res => {
+          if (res.code === 0 || res.code === 99) {
+            const data = res.data || {}
+            this.onlineTotal = Number(data.total) || 0
+            this.onlineListData = (data.data || []).map(r => ({
+              member_id: r.member_id,
+              first_name: r.first_name,
+              balance: r.available_balance,
+              ip: r.login_ip,
+              location: r.location,
+              website: r.login_domain,
+              platform: r.platform,
+              browser: r.browser,
+              os: r.os,
+              login_time: r.login_time
+            }))
+          } else {
+            this.$message.error(res.msg || '获取在线用户失败')
+          }
+        })
+        .catch(() => {
+          this.$message.error('请求异常')
+        })
+        .finally(() => {
+          this.loading = false
+        })
+    },
+    fetchNewUser() {
+      this.loading = true
+      const params = {}
+      const [start_time, end_time] = this.query.dateRange || []
+      if (start_time) params.start_time = start_time
+      if (end_time) params.end_time = end_time
+      getOnlineNewUserSummary(params)
+        .then(res => {
+          if (res.code === 0 || res.code === 99) {
+            const data = res.data || {}
+            this.newUserRules = data.rules || null
+            this.newUserFilteredData = (data.data || []).map(r => ({
+              date: r.date,
+              h5_reg: r.h5_register,
+              app_reg: r.app_register,
+              h5_first_deposit: r.h5_first_deposit,
+              app_first_deposit: r.app_first_deposit,
+              active_members: r.active_member,
+              valid_members: r.valid_member,
+              total_deposit: r.deposit_total,
+              total_bet: r.bet_total,
+              company_profit: r.company_profit
+            }))
+          } else {
+            this.$message.error(res.msg || '获取新用户汇总失败')
+          }
+        })
+        .catch(() => {
+          this.$message.error('请求异常')
+        })
+        .finally(() => {
+          this.loading = false
+        })
     }
   },
   mounted() {
@@ -355,6 +414,14 @@ export default {
   border-bottom: 1px solid #ebeef5;
   background: #fff;
 }
+.filter-form {
+  display: flex;
+  flex-wrap: wrap;
+  align-items: center;
+}
+.filter-actions {
+  margin-left: auto;
+}
 
 .table-section {
   padding: 18px 20px 20px;
@@ -398,11 +465,3 @@ export default {
   margin-top: 4px;
 }
 </style>
-
-
-
-
-
-
-
-

+ 111 - 63
src/views/user/userStatistics/index.vue

@@ -1,4 +1,4 @@
-<template>
+<template>
   <div class="new-app-container layout-vertical statistics-page">
     <div class="content-panel">
       <el-tabs v-model="activeTab" class="statistics-tabs" @tab-click="handleTabChange">
@@ -28,10 +28,10 @@
             />
           </el-form-item>
           <el-form-item class="filter-actions">
-            <el-button type="primary" size="small" icon="el-icon-search" :loading="loading" @click="getData">
+            <el-button type="primary" size="small" :loading="loading" @click="getData">
               查询
             </el-button>
-            <el-button size="small" icon="el-icon-refresh-left" @click="resetQuery">重置</el-button>
+            <el-button size="small" @click="resetQuery">重置</el-button>
           </el-form-item>
         </el-form>
       </div>
@@ -47,10 +47,17 @@
         <div class="panel-title">
           域名统计
         </div>
-        <el-table v-loading="loading" :data="domainRows" border stripe height="calc(100vh - 300px)" empty-text="暂无域名统计数据">
-          <el-table-column prop="domain" label="登录网址" min-width="280" />
-          <el-table-column prop="count" label="登录人数" min-width="180" />
+        <el-table v-loading="loading" :data="domainPagedData"
+          height="calc(100vh - 300px)" style="width: 100%; border-radius: 12px; overflow: hidden;"
+          empty-text="暂无域名统计数据">
+          <el-table-column prop="register_url" label="注册网址" min-width="280" align="center" />
+          <el-table-column prop="register_count" label="注册人数" min-width="180" align="center" />
         </el-table>
+        <el-pagination v-if="domainTotal" background
+          layout="total, prev, pager, next, jumper" :page-size="domainPageSize"
+          :page-sizes="[50, 100, 200]" :current-page="domainPage"
+          :total="domainTotal"
+          @current-change="fetchDomain" @size-change="handleDomainSizeChange" />
       </div>
     </div>
   </div>
@@ -58,52 +65,7 @@
 
 <script>
 import * as echarts from 'echarts'
-
-const TREND_HOURLY = [
-  { label: '00:00', user: 3, virtual: 1 },
-  { label: '01:00', user: 1, virtual: 0 },
-  { label: '02:00', user: 0, virtual: 0 },
-  { label: '03:00', user: 0, virtual: 0 },
-  { label: '04:00', user: 1, virtual: 0 },
-  { label: '05:00', user: 2, virtual: 1 },
-  { label: '06:00', user: 4, virtual: 2 },
-  { label: '07:00', user: 6, virtual: 3 },
-  { label: '08:00', user: 8, virtual: 4 },
-  { label: '09:00', user: 12, virtual: 5 },
-  { label: '10:00', user: 15, virtual: 6 },
-  { label: '11:00', user: 18, virtual: 7 },
-  { label: '12:00', user: 14, virtual: 5 },
-  { label: '13:00', user: 16, virtual: 6 },
-  { label: '14:00', user: 20, virtual: 8 },
-  { label: '15:00', user: 22, virtual: 9 },
-  { label: '16:00', user: 19, virtual: 7 },
-  { label: '17:00', user: 17, virtual: 6 },
-  { label: '18:00', user: 13, virtual: 5 },
-  { label: '19:00', user: 11, virtual: 4 },
-  { label: '20:00', user: 9, virtual: 3 },
-  { label: '21:00', user: 7, virtual: 2 },
-  { label: '22:00', user: 5, virtual: 1 },
-  { label: '23:00', user: 3, virtual: 1 },
-]
-
-const TREND_DAILY = [
-  { label: '07-26', user: 45, virtual: 8 },
-  { label: '07-27', user: 52, virtual: 12 },
-  { label: '07-28', user: 38, virtual: 6 },
-  { label: '07-29', user: 61, virtual: 15 },
-  { label: '07-30', user: 49, virtual: 10 },
-  { label: '07-31', user: 55, virtual: 14 },
-  { label: '08-01', user: 42, virtual: 9 },
-]
-
-const DOMAIN_MOCK = [
-  { domain: 'www.example.com', count: 156 },
-  { domain: 'm.example.com', count: 89 },
-  { domain: 'com.example.app', count: 234 },
-  { domain: 'www.example.net', count: 67 },
-  { domain: 'com.example.mini', count: 45 },
-  { domain: 'api.example.com', count: 123 },
-]
+import { getRegisterTrend, getRegisterDomain } from '@/api/data'
 
 export default {
   name: 'UserStatistics',
@@ -118,7 +80,9 @@ export default {
       },
       trendRows: [],
       domainRows: [],
-      allDomainData: [...DOMAIN_MOCK],
+      domainTotal: 0,
+      domainPage: 1,
+      domainPageSize: 50,
       pickerOptions: {
         disabledDate(time) {
           return time.getTime() > Date.now()
@@ -126,6 +90,12 @@ export default {
       }
     }
   },
+  computed: {
+    domainPagedData() {
+      const start = (this.domainPage - 1) * this.domainPageSize
+      return this.domainRows.slice(start, start + this.domainPageSize)
+    }
+  },
   created() {
     this.setDefaultDateRange()
   },
@@ -166,16 +136,75 @@ export default {
       this.getData()
     },
     getData() {
+      if (this.activeTab === 'trend') {
+        this.fetchTrend()
+      } else {
+        this.domainPage = 1
+        this.fetchDomain()
+      }
+    },
+    fetchTrend() {
       this.loading = true
-      setTimeout(() => {
-        if (this.activeTab === 'trend') {
-          this.trendRows = this.query.type === 'today' ? [...TREND_HOURLY] : [...TREND_DAILY]
-          this.$nextTick(this.renderChart)
-        } else {
-          this.domainRows = [...this.allDomainData]
-        }
-        this.loading = false
-      }, 300)
+      const [start_time, end_time] = this.query.dateRange || []
+      const params = {
+        type: this.query.type,
+        start_time: start_time || '',
+        end_time: end_time || ''
+      }
+      getRegisterTrend(params)
+        .then(res => {
+          if (res.code === 0 || res.code === 99) {
+            const data = res.data || {}
+            const times = data.times || []
+            const member = data.member || []
+            const virtual = data.virtual || []
+            this.trendRows = times.map((t, i) => ({
+              label: t,
+              user: member[i] != null ? Number(member[i]) : 0,
+              virtual: virtual[i] != null ? Number(virtual[i]) : 0
+            }))
+            this.$nextTick(this.renderChart)
+          } else {
+            this.$message.error(res.msg || '获取走势数据失败')
+          }
+        })
+        .catch(() => {
+          this.$message.error('请求异常')
+        })
+        .finally(() => {
+          this.loading = false
+        })
+    },
+    fetchDomain() {
+      this.loading = true
+      const [start_time, end_time] = this.query.dateRange || []
+      const params = {
+        start_time: start_time || '',
+        end_time: end_time || '',
+        page: this.domainPage,
+        limit: this.domainPageSize
+      }
+      getRegisterDomain(params)
+        .then(res => {
+          if (res.code === 0 || res.code === 99) {
+            const data = res.data || {}
+            this.domainTotal = Number(data.total) || 0
+            this.domainRows = data.data || []
+          } else {
+            this.$message.error(res.msg || '获取域名统计失败')
+          }
+        })
+        .catch(() => {
+          this.$message.error('请求异常')
+        })
+        .finally(() => {
+          this.loading = false
+        })
+    },
+    handleDomainSizeChange(val) {
+      this.domainPageSize = val
+      this.domainPage = 1
+      this.fetchDomain()
     },
     renderChart() {
       if (!this.$refs.trendChart) return
@@ -319,10 +348,29 @@ export default {
   height: calc(100vh - 270px);
   min-height: 380px;
 }
+
+/* 与会员登录日志 YMTable 列表样式一致 */
+.domain-panel {
+  ::v-deep .el-table th {
+    background: #fff;
+  }
+  ::v-deep .el-table th .cell {
+    color: #606266;
+    font-weight: 500;
+  }
+  ::v-deep .el-table td .cell {
+    color: #606266;
+    font-size: 14px;
+  }
+  .el-pagination {
+    margin-top: 14px;
+    text-align: right;
+  }
+}
+
 @media (max-width: 768px) {
   .statistics-page { padding: 10px; }
   .filter-actions { width: 100%; margin-left: 0; }
   .trend-chart { height: 420px; min-height: 420px; }
 }
 </style>
-