fix(api): 替换所有接口请求的域名为 guilixu-api.websoft.top

- 将获取 STS Token 的接口地址由 paopao-api 改为 guilixu-api
- 更新图片上传接口地址为新的 guilixu-api 域名
- 修改用户推广页面中邀请码链接和二维码接口的域名
- 更改注册页微信登录接口请求的域名为 guilixu-api
This commit is contained in:
2026-06-16 17:15:59 +08:00
commit f3886664f7
617 changed files with 77059 additions and 0 deletions

195
src/pages/users.tsx Normal file
View File

@@ -0,0 +1,195 @@
import React, { useEffect, useState, useCallback } from 'react'
import { View, Text, ScrollView } from '@tarojs/components'
import NavBar from '@/components/NavBar'
import { pageShopUserReferee } from '@/api/shop/shopUserReferee'
import { pageShopOrder } from '@/api/shop/shopOrder'
definePageConfig({
navigationBarTitleText: '用户分析',
})
type TimeRange = 'week' | 'month'
interface UserStat {
date: string
newUsers: number
}
const UserAnalysisPage: React.FC = () => {
const [timeRange, setTimeRange] = useState<TimeRange>('week')
const [loading, setLoading] = useState(true)
const [totalNewUsers, setTotalNewUsers] = useState(0)
const [totalBuyUsers, setTotalBuyUsers] = useState(0)
const [dayStats, setDayStats] = useState<UserStat[]>([])
const [conversionRate, setConversionRate] = useState(0)
const formatDateKey = (date: Date) => {
const m = String(date.getMonth() + 1).padStart(2, '0')
const d = String(date.getDate()).padStart(2, '0')
return `${m}-${d}`
}
const formatDateFull = (date: Date) => {
const y = date.getFullYear()
const m = String(date.getMonth() + 1).padStart(2, '0')
const d = String(date.getDate()).padStart(2, '0')
return `${y}-${m}-${d}`
}
const loadData = useCallback(async (range: TimeRange) => {
setLoading(true)
try {
const now = new Date()
const days = range === 'week' ? 7 : 30
const startDate = new Date(now.getTime() - (days - 1) * 24 * 60 * 60 * 1000)
const startStr = `${formatDateFull(startDate)} 00:00:00`
const endStr = `${formatDateFull(now)} 23:59:59`
const [refereeRes, orderRes] = await Promise.all([
pageShopUserReferee({ page: 1, limit: 500, startTime: startStr, endTime: endStr } as any).catch(() => null),
pageShopOrder({ page: 1, limit: 500, startTime: startStr, endTime: endStr } as any).catch(() => null),
])
// 按天分组用户
const dayMap: Record<string, number> = {}
for (let i = 0; i < days; i++) {
const d = new Date(startDate.getTime() + i * 24 * 60 * 60 * 1000)
dayMap[formatDateKey(d)] = 0
}
const refereeList = refereeRes?.list || []
refereeList.forEach((r: any) => {
if (!r.createTime) return
const key = r.createTime.substring(5, 10)
if (dayMap.hasOwnProperty(key)) {
dayMap[key]++
}
})
const stats: UserStat[] = Object.entries(dayMap).map(([date, newUsers]) => ({ date, newUsers }))
setDayStats(stats)
setTotalNewUsers(refereeList.length)
// 计算购买用户数(通过订单去重)
const orderList = orderRes?.list || []
const buyerSet = new Set(orderList.map((o: any) => o.userId).filter(Boolean))
setTotalBuyUsers(buyerSet.size)
setConversionRate(refereeList.length > 0 ? (buyerSet.size / refereeList.length) * 100 : 0)
} catch (e) {
console.error('加载用户数据失败', e)
} finally {
setLoading(false)
}
}, [])
useEffect(() => {
loadData(timeRange)
}, [timeRange, loadData])
const maxNewUsers = Math.max(...dayStats.map(d => d.newUsers), 1)
return (
<View className="min-h-screen bg-gray-100">
<NavBar title="用户分析" />
{/* 时间范围选择 */}
<View className="bg-white flex">
{([{ key: 'week', label: '近7天' }, { key: 'month', label: '近30天' }] as const).map(option => (
<View
key={option.key}
className="flex-1 py-3 text-center relative"
style={{ color: timeRange === option.key ? '#ef4444' : '#6b7280' }}
onClick={() => setTimeRange(option.key)}
>
<Text className={timeRange === option.key ? 'font-medium' : ''}>{option.label}</Text>
{timeRange === option.key && (
<View className="absolute bottom-0 left-0 right-0 flex justify-center">
<View className="h-1 rounded-t" style={{ width: '32px', backgroundColor: '#ef4444' }} />
</View>
)}
</View>
))}
</View>
<ScrollView scrollY>
{loading ? (
<View className="p-8 text-center">
<Text className="text-gray-400 text-sm">...</Text>
</View>
) : (
<View className="p-4">
{/* 核心指标 */}
<View className="bg-white rounded-lg p-4 mb-4">
<View className="grid grid-cols-2 gap-3">
<View className="text-center p-3 rounded-lg" style={{ backgroundColor: '#eff6ff' }}>
<Text className="text-2xl font-bold block" style={{ color: '#3b82f6' }}>{totalNewUsers}</Text>
<Text className="text-xs text-gray-500"></Text>
</View>
<View className="text-center p-3 rounded-lg" style={{ backgroundColor: '#f0fdf4' }}>
<Text className="text-2xl font-bold block" style={{ color: '#10b981' }}>{totalBuyUsers}</Text>
<Text className="text-xs text-gray-500"></Text>
</View>
<View className="text-center p-3 rounded-lg" style={{ backgroundColor: '#fff7ed' }}>
<Text className="text-2xl font-bold block" style={{ color: '#f59e0b' }}>
{conversionRate.toFixed(1)}%
</Text>
<Text className="text-xs text-gray-500"></Text>
</View>
<View className="text-center p-3 rounded-lg" style={{ backgroundColor: '#fdf4ff' }}>
<Text className="text-2xl font-bold block" style={{ color: '#a855f7' }}>
{totalNewUsers > 0 ? (totalNewUsers / (timeRange === 'week' ? 7 : 30)).toFixed(1) : '0.0'}
</Text>
<Text className="text-xs text-gray-500"></Text>
</View>
</View>
</View>
{/* 新增用户趋势 */}
<View className="bg-white rounded-lg p-4 mb-4">
<Text className="text-base font-medium mb-4 block"></Text>
{dayStats.every(d => d.newUsers === 0) ? (
<View className="py-8 text-center">
<Text className="text-gray-400 text-sm"></Text>
</View>
) : (
dayStats.map((item, index) => (
<View key={index} className={`flex items-center mb-3 ${index === dayStats.length - 1 ? 'mb-0' : ''}`}>
<Text className="text-xs text-gray-500" style={{ width: '48px' }}>{item.date}</Text>
<View className="flex-1 mx-3 rounded-full" style={{ backgroundColor: '#e5e7eb', height: '24px' }}>
<View
className="rounded-full flex items-center justify-end"
style={{
width: `${Math.max((item.newUsers / maxNewUsers) * 100, item.newUsers > 0 ? 8 : 0)}%`,
backgroundColor: '#3b82f6',
height: '24px',
paddingRight: '8px',
minWidth: item.newUsers > 0 ? '32px' : '0',
}}
>
{item.newUsers > 0 && (
<Text className="text-xs text-white">{item.newUsers}</Text>
)}
</View>
</View>
</View>
))
)}
</View>
{/* 数据说明 */}
<View className="bg-white rounded-lg p-4">
<Text className="text-base font-medium mb-3 block"></Text>
<View className="flex flex-col" style={{ gap: '8px' }}>
<Text className="text-sm text-gray-500 block"> /</Text>
<Text className="text-sm text-gray-500 block"> </Text>
<Text className="text-sm text-gray-500 block"> / × 100%</Text>
</View>
</View>
</View>
)}
</ScrollView>
</View>
)
}
export default UserAnalysisPage