feat(adtracking): 新增推广链接追踪与数据大盘,支持自定义短码、注册与下单归因及二维码物料生成
1. 数据库新增 000064_ad_tracking.sql 迁移,创建 ad_tracking_links 和 ad_tracking_clicks 表,并在 users、rental_orders 增加归因字段 2. 后端新增 adtracking 模块,挂载 GET /r/:code 302 智能重定向、点击流水异步记录、转化率/GMV聚合统计及管理接口 3. 用户认证 (auth) 与订单创建 (order) 链路贯通,实现注册用户与订单成交推广短码全链路归因 4. 前端管理后台左侧新增【推广追踪】目录,支持大盘指标总览、推广链接 CRUD、状态启停、一键复制短链及二维码下载 (PNG)
This commit is contained in:
@@ -0,0 +1,110 @@
|
||||
import { apiClient } from '@/shared/api/client'
|
||||
import type { ApiResponse } from '@/shared/types/types'
|
||||
|
||||
export interface TrackingLinkItem {
|
||||
id: number
|
||||
name: string
|
||||
code: string
|
||||
channel_type: string
|
||||
target_url: string
|
||||
short_url: string
|
||||
status: 'active' | 'disabled'
|
||||
cost_cent: number
|
||||
notes: string
|
||||
created_by: number
|
||||
created_at: string
|
||||
updated_at: string
|
||||
|
||||
click_pv: number
|
||||
click_uv: number
|
||||
click_ip: number
|
||||
register_count: number
|
||||
order_count: number
|
||||
paid_order_count: number
|
||||
paid_gmv_cent: number
|
||||
conversion_rate: number
|
||||
}
|
||||
|
||||
export interface TrackingOverviewStats {
|
||||
total_links: number
|
||||
active_links: number
|
||||
total_pv: number
|
||||
total_uv: number
|
||||
total_ip: number
|
||||
total_registers: number
|
||||
total_orders: number
|
||||
total_paid_orders: number
|
||||
total_paid_gmv_cent: number
|
||||
|
||||
today_pv: number
|
||||
today_uv: number
|
||||
today_registers: number
|
||||
today_orders: number
|
||||
today_paid_gmv_cent: number
|
||||
}
|
||||
|
||||
export interface ListTrackingLinksParams {
|
||||
keyword?: string
|
||||
channel_type?: string
|
||||
status?: string
|
||||
page?: number
|
||||
page_size?: number
|
||||
}
|
||||
|
||||
export interface CreateTrackingLinkPayload {
|
||||
name: string
|
||||
code: string
|
||||
channel_type?: string
|
||||
target_url?: string
|
||||
cost_cent?: number
|
||||
notes?: string
|
||||
}
|
||||
|
||||
export interface UpdateTrackingLinkPayload {
|
||||
name: string
|
||||
channel_type?: string
|
||||
target_url?: string
|
||||
cost_cent?: number
|
||||
notes?: string
|
||||
}
|
||||
|
||||
export async function fetchTrackingOverview(): Promise<TrackingOverviewStats> {
|
||||
const { data } = await apiClient.get<ApiResponse<TrackingOverviewStats>>(
|
||||
'/admin/tracking-links/overview'
|
||||
)
|
||||
return data.data
|
||||
}
|
||||
|
||||
export async function fetchTrackingLinks(
|
||||
params: ListTrackingLinksParams
|
||||
): Promise<{ items: TrackingLinkItem[]; total: number }> {
|
||||
const { data } = await apiClient.get<ApiResponse<{ items: TrackingLinkItem[]; total: number }>>(
|
||||
'/admin/tracking-links',
|
||||
{ params }
|
||||
)
|
||||
return data.data
|
||||
}
|
||||
|
||||
export async function createTrackingLink(
|
||||
payload: CreateTrackingLinkPayload
|
||||
): Promise<TrackingLinkItem> {
|
||||
const { data } = await apiClient.post<ApiResponse<TrackingLinkItem>>(
|
||||
'/admin/tracking-links',
|
||||
payload
|
||||
)
|
||||
return data.data
|
||||
}
|
||||
|
||||
export async function updateTrackingLink(
|
||||
id: number,
|
||||
payload: UpdateTrackingLinkPayload
|
||||
): Promise<void> {
|
||||
await apiClient.put(`/admin/tracking-links/${id}`, payload)
|
||||
}
|
||||
|
||||
export async function updateTrackingLinkStatus(
|
||||
id: number,
|
||||
status: 'active' | 'disabled'
|
||||
): Promise<void> {
|
||||
await apiClient.put(`/admin/tracking-links/${id}/status`, { status })
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,7 @@
|
||||
import { apiClient } from '@/shared/api/client'
|
||||
import type { ApiResponse } from '@/shared/types/types'
|
||||
import type { RealnameStatusValue, RiskStatus, UserStatus } from '@/shared/types/status'
|
||||
import { getTrackCode } from '@/shared/utils/trackCode'
|
||||
|
||||
export interface AuthUser {
|
||||
id: number
|
||||
@@ -51,7 +52,12 @@ export async function sendSmsCode(phone: string, captchaId: string, captchaCode:
|
||||
}
|
||||
|
||||
export async function loginWithSms(phone: string, code: string) {
|
||||
const { data } = await apiClient.post<ApiResponse<LoginData>>('/auth/sms/login', { phone, code })
|
||||
const trackCode = getTrackCode()
|
||||
const payload: Record<string, unknown> = { phone, code }
|
||||
if (trackCode) {
|
||||
payload.track_code = trackCode
|
||||
}
|
||||
const { data } = await apiClient.post<ApiResponse<LoginData>>('/auth/sms/login', payload)
|
||||
return data.data
|
||||
}
|
||||
|
||||
@@ -64,11 +70,12 @@ export async function loginWithPassword(phone: string, password: string) {
|
||||
}
|
||||
|
||||
export async function registerWithPassword(phone: string, code: string, password: string) {
|
||||
const { data } = await apiClient.post<ApiResponse<LoginData>>('/auth/password/register', {
|
||||
phone,
|
||||
code,
|
||||
password,
|
||||
})
|
||||
const trackCode = getTrackCode()
|
||||
const payload: Record<string, unknown> = { phone, code, password }
|
||||
if (trackCode) {
|
||||
payload.track_code = trackCode
|
||||
}
|
||||
const { data } = await apiClient.post<ApiResponse<LoginData>>('/auth/password/register', payload)
|
||||
return data.data
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import { apiClient } from '@/shared/api/client'
|
||||
import type { ApiResponse, PaginatedResult } from '@/shared/types/types'
|
||||
import type { HandoffStatus, OrderStatus, SettlementStatus } from '@/shared/types/status'
|
||||
import { yuanToCent } from '@/shared/utils/money'
|
||||
import { getTrackCode } from '@/shared/utils/trackCode'
|
||||
|
||||
export interface Order {
|
||||
id: number
|
||||
@@ -280,7 +281,12 @@ export async function fetchOrder(id: string | number) {
|
||||
}
|
||||
|
||||
export async function createOrder(listingId: number) {
|
||||
const { data } = await apiClient.post<ApiResponse<Order>>('/orders', { listing_id: listingId })
|
||||
const trackCode = getTrackCode()
|
||||
const payload: Record<string, unknown> = { listing_id: listingId }
|
||||
if (trackCode) {
|
||||
payload.track_code = trackCode
|
||||
}
|
||||
const { data } = await apiClient.post<ApiResponse<Order>>('/orders', payload)
|
||||
return data.data
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user