立即提现
diff --git a/distribution-admin/src/pages/Users.tsx b/distribution-admin/src/pages/Users.tsx
new file mode 100644
index 0000000..4879568
--- /dev/null
+++ b/distribution-admin/src/pages/Users.tsx
@@ -0,0 +1,418 @@
+import { useEffect, useState, useCallback } from 'react'
+import {
+ Card,
+ Table,
+ Typography,
+ Space,
+ Input,
+ Select,
+ Tag,
+ Button,
+ Avatar,
+ Row,
+ Col,
+ Statistic,
+ message,
+ Tooltip,
+} from 'antd'
+import {
+ UserOutlined,
+ SearchOutlined,
+ ReloadOutlined,
+ CopyOutlined,
+ CheckOutlined,
+ ShoppingOutlined,
+ TeamOutlined,
+ CheckCircleOutlined,
+ ClockCircleOutlined,
+} from '@ant-design/icons'
+import { useNavigate } from 'react-router-dom'
+import {
+ listDistributionUsers,
+ listSites,
+ formatMoney,
+ type DistributionUser,
+ type Site,
+} from '../api'
+import { useMember } from '../MemberContext'
+
+function formatDateTime(str?: string): string {
+ if (!str) return '-'
+ try {
+ const d = new Date(str)
+ if (isNaN(d.getTime())) return str
+ const pad = (n: number) => (n < 10 ? `0${n}` : `${n}`)
+ return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`
+ } catch {
+ return str
+ }
+}
+
+export default function Users() {
+ const member = useMember()
+ const navigate = useNavigate()
+
+ const [sites, setSites] = useState
([])
+ const [siteID, setSiteID] = useState(member?.site_id)
+ const [users, setUsers] = useState([])
+ const [total, setTotal] = useState(0)
+ const [totalUsers, setTotalUsers] = useState(0)
+ const [orderUsers, setOrderUsers] = useState(0)
+ const [loading, setLoading] = useState(false)
+
+ // 筛选条件
+ const [keyword, setKeyword] = useState('')
+ const [source, setSource] = useState<'all' | 'register' | 'order'>('all')
+ const [page, setPage] = useState(1)
+ const [pageSize, setPageSize] = useState(20)
+
+ // 复制状态记录
+ const [copiedPhone, setCopiedPhone] = useState(null)
+
+ const copyToClipboard = (text: string) => {
+ navigator.clipboard?.writeText(text)
+ setCopiedPhone(text)
+ message.success(`已复制手机号:${text}`)
+ setTimeout(() => {
+ setCopiedPhone(null)
+ }, 2000)
+ }
+
+ const loadData = useCallback(
+ async (curSiteID?: number, curPage = page, curSize = pageSize) => {
+ setLoading(true)
+ try {
+ const res = await listDistributionUsers({
+ siteID: curSiteID,
+ keyword: keyword.trim() || undefined,
+ source,
+ page: curPage,
+ pageSize: curSize,
+ })
+ setUsers(res.items || [])
+ setTotal(res.total || 0)
+ setTotalUsers(res.total_users || 0)
+ setOrderUsers(res.order_users || 0)
+ } catch {
+ message.error('加载用户列表失败')
+ } finally {
+ setLoading(false)
+ }
+ },
+ [keyword, source, page, pageSize]
+ )
+
+ useEffect(() => {
+ async function init() {
+ if (member?.is_superadmin) {
+ try {
+ const rows = await listSites()
+ setSites(rows)
+ if (!siteID && rows.length > 0) {
+ setSiteID(rows[0].id)
+ void loadData(rows[0].id, 1, pageSize)
+ return
+ }
+ } catch {
+ // ignore
+ }
+ }
+ void loadData(siteID, 1, pageSize)
+ }
+ void init()
+ }, [member]) // eslint-disable-line react-hooks/exhaustive-deps
+
+ const handleSearch = () => {
+ setPage(1)
+ void loadData(siteID, 1, pageSize)
+ }
+
+ const handleReset = () => {
+ setKeyword('')
+ setSource('all')
+ setPage(1)
+ setTimeout(() => {
+ void loadData(siteID, 1, pageSize)
+ }, 0)
+ }
+
+ const columns = [
+ {
+ title: '用户 ID',
+ dataIndex: 'id',
+ key: 'id',
+ width: 90,
+ render: (id: number) => (
+
+ #{id}
+
+ ),
+ },
+ {
+ title: '用户手机号',
+ dataIndex: 'phone',
+ key: 'phone',
+ width: 170,
+ render: (phone: string) => (
+
+
+ {phone}
+
+
+ : }
+ onClick={() => copyToClipboard(phone)}
+ />
+
+
+ ),
+ },
+ {
+ title: '昵称 / 头像',
+ key: 'user_info',
+ width: 160,
+ render: (_: unknown, row: DistributionUser) => (
+
+ : undefined}
+ style={{ backgroundColor: '#1677ff' }}
+ />
+ {row.nickname || '-'}
+
+ ),
+ },
+ {
+ title: '归属来源',
+ dataIndex: 'source',
+ key: 'source',
+ width: 130,
+ render: (src: string) => {
+ if (src === 'both') {
+ return 注册且下单
+ }
+ if (src === 'order') {
+ return 本站下单客户
+ }
+ return 本站注册用户
+ },
+ },
+ {
+ title: '本站下单数',
+ dataIndex: 'order_count',
+ key: 'order_count',
+ width: 120,
+ render: (count: number, row: DistributionUser) => {
+ if (count > 0) {
+ return (
+
+
+
+ )
+ }
+ return 0 单
+ },
+ },
+ {
+ title: '本站累计消费',
+ dataIndex: 'total_spent_cent',
+ key: 'total_spent_cent',
+ width: 130,
+ render: (cent: number) => {
+ if (cent > 0) {
+ return (
+
+ {formatMoney(cent)}
+
+ )
+ }
+ return ¥0.00
+ },
+ },
+ {
+ title: '实名状态',
+ dataIndex: 'realname_status',
+ key: 'realname_status',
+ width: 110,
+ render: (st: string) => {
+ if (st === 'verified') {
+ return (
+ } color="success">
+ 已实名
+
+ )
+ }
+ return (
+ } color="default">
+ 未实名
+
+ )
+ },
+ },
+ {
+ title: '注册时间',
+ dataIndex: 'created_at',
+ key: 'created_at',
+ width: 170,
+ render: (t: string) => (
+ {formatDateTime(t)}
+ ),
+ },
+ {
+ title: '最后登录',
+ dataIndex: 'last_login_at',
+ key: 'last_login_at',
+ width: 170,
+ render: (t?: string) => (
+ {formatDateTime(t)}
+ ),
+ },
+ ]
+
+ const selectedSiteName =
+ member?.site_name || sites.find((s) => s.id === siteID)?.site_name || '当前分销商'
+
+ return (
+
+
+
+
+ 客户与用户管理
+
+
+ 当前站点:{selectedSiteName} · 查看在本分销站注册的专属用户以及在本站产生交易的客户
+
+
+
+ {member?.is_superadmin && sites.length > 1 && (
+
+
+
+ {/* 顶部统计卡片 */}
+
+
+
+ }
+ suffix="人"
+ valueStyle={{ color: '#52c41a', fontWeight: 'bold' }}
+ />
+
+ 通过本分销站点/专属域名注册绑定的私域用户
+
+
+
+
+
+ }
+ suffix="人"
+ valueStyle={{ color: '#fa8c16', fontWeight: 'bold' }}
+ />
+
+ 在本分销站点完成过账号租借交易的有效客户
+
+
+
+
+
+ }
+ suffix="人"
+ valueStyle={{ color: '#1677ff', fontWeight: 'bold' }}
+ />
+
+ 本站注册或产生过消费行为的全部用户总计
+
+
+
+
+
+ {/* 筛选与检索栏 */}
+
+
+ setKeyword(e.target.value)}
+ onPressEnter={handleSearch}
+ prefix={}
+ style={{ width: 240 }}
+ allowClear
+ />
+
+
+ `共 ${t} 个用户`,
+ onChange: (p, ps) => {
+ setPage(p)
+ setPageSize(ps)
+ void loadData(siteID, p, ps)
+ },
+ }}
+ scroll={{ x: 1100 }}
+ />
+
+
+ )
+}
diff --git a/frontend/src/features/admin/api/adminDistribution.ts b/frontend/src/features/admin/api/adminDistribution.ts
index aa9efe8..6b46c71 100644
--- a/frontend/src/features/admin/api/adminDistribution.ts
+++ b/frontend/src/features/admin/api/adminDistribution.ts
@@ -18,6 +18,8 @@ export interface DistributionSite {
admin_username?: string
admin_nickname?: string
member_type?: string
+ user_count?: number
+ order_user_count?: number
created_at: string
}
diff --git a/frontend/src/features/admin/views/AdminDistributionSitesView.vue b/frontend/src/features/admin/views/AdminDistributionSitesView.vue
index 2bab2e8..84ade9f 100644
--- a/frontend/src/features/admin/views/AdminDistributionSitesView.vue
+++ b/frontend/src/features/admin/views/AdminDistributionSitesView.vue
@@ -701,6 +701,18 @@ onMounted(() => {
未关联
+
+
+
+
+ {{ row.user_count || 0 }} 注册
+
+
+ {{ row.order_user_count || 0 }} 成交客户
+
+
+
+